jsonp-反向代理-CORS解决JS跨域问题的个人总结(更新 v2.0)

网上说了很多很多,但是看完之后还是很混乱,所以我自己重新总结一下。

解决 js 跨域问题一共有8种方法:

  1. jsonp(只支持 get)
  2. 反向代理
  3. CORS
  4. document.domain + iframe 跨域
  5. window.name + iframe 跨域
  6. window.postMessage
  7. location.hash + iframe
  8. web sockets

各个方法都有各自的优缺点,但是目前前端开发方面比较常用的是 jsonp,反向代理,CORS:

  • CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。

    • 优点是:正统,符合标准,
    • 缺点是:需要服务器端配合,比较麻烦。
  • JSONP的核心则是动态添加 //服务器上的代码 // 服务器返回的数据是一段 js 代码 getData( // 这是 js 的函数写法 { // 这是参数,参数是一个对象 "name":"jiavan", "age": 20 } )

    先在本地定义了一个函数,这是用来处理来自服务器上数据的函数,下面用一个script标签,并且向服务器发起了一个GET请求,并且指定了处理数据的回调函数,即上方的getData,服务器收到请求后返回了getData('{"name": "jiavan", "age": 20}');,即使一段js代码,将数据传入到回调函数中处理,这样便完成了跨域。

    参考:https://segmentfault.com/a/1190000004761698

    复杂一点的例子:

    引用来自https://segmentfault.com/a/1190000012469713的图

    • 客户端和服务器端约定一个参数名是代表 jsonp 请求的,例如约定 callback 这个参数名。
    • 然后服务器端准备好针对之前约定的 callback 参数请求的 javascript 文件,这个文件里面要有一个函数名,要跟客户端请求的时候的函数名要保持一致。(如下面例子:ip.js
    • 然后客户端注册一个本地运行的函数,并且函数的名字要跟去请求服务器进行 callback 回调的函数的名字要一致。(如下面例子:foo 函数跟请求时候callback=foo的名字是一致的)
    • 然后客户端对服务器端进行 jsonp 的方式请求。
    • 服务器端返回刚才配置好的js 文件(ip.js)到客户端
    • 客户端浏览器,解析script标签,并执行返回的javascript文件,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里。

      • 相当于本地执行注册好foo 函数,然后获取了一个foo 函数,并且这个获取的 foo 函数里面包含了传入的参数(例如 foo({XXXXX})

    服务器端文件ip.js

    foo({
      "ip": "8.8.8.8"
    });

    客户端文件 jsonp.html

    
    
    
        
        
    
    
    
    

    3.3 CORS 方式

    CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

    • CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
    • 整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
    因此,实现CORS通信的关键是服务器端。只要服务器端实现了CORS接口,就可以跨源通信。

    3.3.1 CORS的请求分为两类

    • 简单请求
    • 非简单请求

    只要同时满足以下两大条件,就属于简单请求。

    (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

    凡是不同时满足上面两个条件,就属于非简单请求。

    3.3.2 对简单请求处理

    如果是简单请求的话,会自动在头信息之中,添加一个Origin字段,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

    GET /cors HTTP/1.1
    Origin: http://api.bob.com 
    Host: api.alice.com
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0...

    如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequestonerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

    这个Origin对应服务器端的Access-Control-Allow-Origin设置,所以一般来说需要在服务器端加上这个Access-Control-Allow-Origin 即可,类似这种:

    Access-Control-Allow-Origin: http://api.bob.com
    Content-Type: text/html; charset=utf-8

    3.3.3 非简单请求

    如果是非简单请求的话,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)

    浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

    需要注意这里是会发送2次请求,第一次是预检请求,第二次才是真正的请求!

    首先发出预检请求:

    // 预检请求
    OPTIONS /cors HTTP/1.1
    Origin: http://api.bob.com
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: X-Custom-Header
    Host: api.alice.com
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0..

    除了Origin字段,"预检"请求的头信息包括两个特殊字段。

    (1)Access-Control-Request-Method

    该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。

    (2)Access-Control-Request-Headers

    该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。

    然后服务器收到"预检"请求以后:

    检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

    // 预检请求的回应
    HTTP/1.1 200 OK
    Date: Mon, 01 Dec 2008 01:15:39 GMT
    Server: Apache/2.0.61 (Unix)
    Access-Control-Allow-Origin: http://api.bob.com
    Access-Control-Allow-Methods: GET, POST, PUT
    Access-Control-Allow-Headers: X-Custom-Header
    Content-Type: text/html; charset=utf-8
    Content-Encoding: gzip
    Content-Length: 0
    Keep-Alive: timeout=2, max=100
    Connection: Keep-Alive
    Content-Type: text/plain

    最后一旦服务器通过了"预检"请求:

    以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

    // 以后的请求,就像拿到了通行证之后,就不需要再做预检请求了。
    PUT /cors HTTP/1.1
    Origin: http://api.bob.com
    Host: api.alice.com
    X-Custom-Header: value
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0...

    详情参考这里http://www.ruanyifeng.com/blog/2016/04/cors.html

    总的来说,只需要知道2个地方即可,其他的可以触类旁通:

    • CORS 需要服务器那边加一个Access-Control-XXX的处理,目的是为了处理请求的来源判别。
    • CORS 对于非简单请求会增加一次 OPTIONS 的请求。

    参考文档:

    • 前端解决跨域问题的8种方案
    • 浏览器同源政策及其规避方法
    • https://tonghuashuo.github.io/blog/jsonp.html
    • http://www.cnblogs.com/yuzhongwusan/archive/2012/12/11/2812849.html
    • http://www.cnblogs.com/dowinning/archive/2012/04/19/json-jsonp-jquery.html
    • https://segmentfault.com/a/1190000002438126

你可能感兴趣的:(跨域,javascript)