跨域请求CORS和jsonp

跨域请求CORS

当我们在一个页面中使用Ajax发送异步请求时,该请求访问的域不在当前域下,浏览器将会对其进行限制,只有ajax请求访问的服务端告诉浏览器该请求的数据可以被该域使用,该ajax访问才能正确的得到数据。

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的浏览器上仍然可以使用。

posted @ 2020-06-09 21:07  没有想象力  阅读(677)  评论(0编辑  收藏  举报