跨域请求CORS和jsonp
跨域请求CORS
CORS是一个W3C标准,全称是"跨域资源共享,它允许浏览器异步向跨源服务器发出XMLHttpRequest请求,从而克服浏览器访问。
CORS需要浏览器和服务器同时支持,浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,这主要根据不同的请求类型而定。有以下两种类型的请求。
简单请求
简单请求的判定
只要同时满足以下两大条件,就属于简单请求
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
简单请求的验证过程
当使用ajax发送一个简单请求,会在请求头中增加一个Origin头信息,指定该请求发送方的域, 用于提交给服务器判断是否接受该源跨域请求,如果服务器同意该跨域访问,正常返回数据,并在响应头信息中携带添加Access-Control-Allow-Origin
头信息,值为Origin也就是发送方的域信息,这个头信息旨在告诉浏览器服务器同意了其他域访问自己,浏览器可以将该返回值返回给请求方而不要拦截。如果服务器返回的信息中没有该字段信息,这个数据将会被拦截,无法到达请求方,浏览器并会报错。
这是错误是浏览器检测到数据没有Access-Control-Allow-Origin
请求头或者值不满足条件做的安全操作,本次跨域请求是成功访问了服务器并且以返回了数据,可以看出相应状态码一切正常,但是信息没有被浏览器同意。
Access-Control-Allow-Origin
头的值一般为发送方的Origin,表示同意该域,或者指定为"*",表示任意域都同意。
还有两个可选择的字段
- Access-Control-Allow-Credentials:它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可
- Access-Control-Expose-Headers:CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
非简单请求
非简单请求的判定
不满足上面简单请求规则的请求都是非简单请求。比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
示例:使用ajax发送一个put的跨域请求,到自己的django后端程序。前端jquery程序如下
<script type="text/javascript" src="jquery.min.js"></script> <button class="pretect">非简单请求</button> $( "button.pretect").click( function(event){ $.ajax({ type: "PUT", headers:{ }, url: "http://localhost:8000/users/cors", data: JSON.stringify({a:100, b:200}), success: function(msg){ console.log("success", msg) }, contentType:"application/json" }) } )
预检请求
当浏览器检测到需要发送一个非简单的跨域请求时,浏览器会拦截该请求,发送一个预检请求到该请求的接受方,旨在询问接受放是否同意这个非简单的跨域请求,接收方通过返回指定内容表示是否同意。
点击上面测试的button,jquery将发送一个非简单的跨域请求,浏览器事先拦截被发送预检请求,预检请求头信息如下
Request URL: http://localhost:8000/users/cors
Request Method: OPTIONS # 预检请求使用option方法
Access-Control-Request-Method: PUT # 正式跨域请求为PUT方法,交给服务端询问是否同意
Origin: http://127.0.0.1:8000 # 请求方的域
Access-Control-Request-Headers: X-Custom-Header # 请方自定义了 X-Custom-Header的头信息
Referer: http://127.0.0.1:8000/users/cors
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36
服务器没有返回约定的头信息,浏览器控制台报错如下:
预检请求的响应
服务器响应预检请求的头信息中,可以包含以下几个头信息。
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
- Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支 持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
- Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
- Access-Control-Allow-Credentials
该字段与简单请求时的含义相同。
- Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。
预检通过
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
JSONP
原理
利用src和href标签属性可以直接通过GET请求跨域访问其他站点的特点,而诞生了jsonp的使用方法。
直接访问服务器得到数据并直接调用,因为src不能返回后调用,只能GET数据,这个数据为一个函数调用的字符串,数据返回后在本页面找到指定函数直接调用,避开了预检请求实现跨域问题。
同一个网页的<script>等标签中可以使用从不同域GET回来的文件,这些文件中的全局变量在该HTML文件中均可以被使用。由此,我们可以跨域获取一个函数定义,在本域中调用该函数。这将会在这个页面内直接执行这个函数获得结果。
利用该请求的特点,开始使用这样方式去解决跨域的问题,逐渐形成了一种非正式传输协议,人们把它称作」SONP,该协议的一个要点就是允许用户传递一个callback参数(参数值及为回调函数名)给服务端,然后服务端返回数据时会将这个 callback参数作为函数名来包裹住服务端的数据,产生一个类似于callback(data)
的函数调用字符串返回,这样客户端的到数据后就直接执行该字符串。而字符串中的函数客户端已定义,可以成功的被调用。
jQuery实现jsonp
前端请求构建
使用jquery访问构建一个Ajax,在访问127.0.0.1:5000的域中,使用ajax跨域访问localhost:5000服务。
<script> function callbackfunc1(params={}){ // 定义函数 ... } </script> <script> $(function(){ $.ajax({ type:'GET', // 只能使用get方法 url:"http://localhost:5000/", dataType:'jsonp', // 必须指定这个jsonp的数据格式,服务器返回的数据类型。 jsonpCallback:"callbackfunc1" // 这个值将用来取代jQuery自动生成的随机函数名, success:function(data, status){} // callbackfunc1调用后才会被调用 }) }) </script>
上面请求发出后,访问的url地址格式为http:localhost:5000/?callback=callbackfunc1
后端处理
JSONP需要服务端的支持,具体表现为返回的数据为callback(json_data)的形式,即要返回回调函数包裹JSON数据的形式,而不是直接返回JSON数据。否则,虽然前端可以获取到请求数据,但不会执行ajax的成功回调函数,而是执行请求失败的回调函数。
后端使用一个flask视图函数进行处理并返回结果
from flask import request, jsonify
@route("/")
def jsonptest():
# 提取callback名字
functionname = request.args.get("callback") # callbackfunc1
# 获取数据,得到数据后使用使用函数名拼接为函数调用的字符串返回。
data = {"a":100, "b":"abc"}
return "{}({})".format(functionname, data) # "fucntionname(data)"字符串返回
比较
在同源策略的安全模式下为,cors和jsonp都是为了实现异步访问跨域访问资源的目的,cors的方式是通过 服务器和浏览器之间使用请求头信息来允许资源被跨域访问,他需要浏览器和服务器支持这种协议规定,否则无法使用。
jsonp是通过<script>标签的中的src和href支持跨域访问资源功能,向服务器提供访问的资源的URI和资源的回调函数名,服务器获取资源后,返回一串javascript函数的调用代码的字符串,该字符串在前端直接背执行,前端获取到执行的结果。
cors使用比jsonp更加的强大,可以支持各种方法,并可以直接返回数据。jsonp的href和src只能通过get方法去获取数据。他的优势在于不需要协议,在一些不支持cors的浏览器上仍然可以使用。