jsonp-反向代理-CORS解决JS跨域问题的我的总结

jsonp-反向代理-CORS解决JS跨域问题的我的总结

网上说了不少不少,可是看完以后仍是很混乱,因此我本身从新总结一下。

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

  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:html

  • CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。优势是正统,符合标准,缺点是:前端

    • 可是须要服务器端配合,比较麻烦。
  • JSONP 优势是对旧式浏览器支持较好,缺点是:java

    • 可是只支持 get 请求。
    • 有安全问题(请求代码中可能存在安全隐患)。
    • 要肯定jsonp请求是否失败并不容易
  • 反向代理都可以兼容以上的肯定,可是仅仅做为前端开发模式的时候使用,在正式上线环境较少用到。node

    • 由于开发环境的域名跟线上环境不同才须要这样处理。
    • 若是线上环境太复杂,自己也是多域(后面说到的同源策略问题,多子域,或者多端口问题),那么须要采用 jsonp 或者 CORS 来处理。
这里主要说明这三种方式。其余方式暂不说明。

1、什么是跨域问题

跨域问题通常只出如今前端开发中使用 javascript 进行网络请求的时候,浏览器为了安全访问网络请求的数据而进行的限制。jquery

提示的错误大体以下:ios

No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://XXXXXX' is therefore not allowed access.

clipboard.png

2、为何会出现跨域问题

由于浏览器收到同源策略的限制,当前域名的js只能读取同域下的窗口属性。git

2.1 同源策略

同源指的是三个源头同时相同:github

  • 协议相同
  • 域名相同
  • 端口相同

举例来讲,http://www.example.com/dir/page.html这个网址,web

协议是 http://
域名是 www.example.com
端口是80 

//它的同源状况以下:
http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不一样源(域名不一样)
http://v2.www.example.com/dir/other.html:不一样源(域名不一样)
http://www.example.com:81/dir/other.html:不一样源(端口不一样)

同源策略限制了如下行为:

  • Cookie、LocalStorage 和 IndexDB 没法读取
  • DOM 和 JS 对象没法获取
  • Ajax请求发送不出去
大概能够知道跨域其实就是同源策略致使的,而且知道同源策略的原理。

详细的同源策略相关,能够参考http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html

3、解决跨域问题

3.1 反向代理方式

反向代理和正向代理的区别:

  • 正向代理(Forward Proxy),一般都被简称为代理,就是在用户没法正常访问外部资源,比方说受到GFW的影响没法访问twitter的时候,咱们能够经过代理的方式,让用户绕过防火墙,从而链接到目标网络或者服务。
  • 反向代理(Reverse Proxy)是指以代理服务器来接受 Internet 上的链接请求,而后将请求转发给内部网络上的服务器,并将从服务器上获得的结果返回给 Internet 请求链接的客户端,此时,代理服务器对外就表现为一个服务器。

那么咱们能够理解为反向代理

如何使用反向代理服务器来达到跨域问题解决:

  • 前端ajax请求的是本地反向代理服务器
  • 本地反向代理服务器接收到后:

    • 修改请求的 http-header 信息,例如 referer,host,端口等
    • 修改后将请求发送到实际的服务器
  • 实际的服务器会觉得是同源(参考同源策略)的请求而做出处理

如今前端开发通常使用 nodejs来作本地反向代理服务器

// 在 express 以后引入路由
var app = express();

var apiRoutes = express.Router();

app.use(bodyParser.urlencoded({extended:false}))

// 自定义 api 路由
apiRoutes.get("/lyric", function (req, res) {
  var url = "https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg";

  axios.get(url, {
    headers: { // 修改 header
      referer: "https://c.y.qq.com/",
      host: "c.y.qq.com"
    },
    params: req.query
  }).then((response) => {
    var ret = response.data
    if (typeof ret === "string") {
      var reg = /^\w+\(({[^()]+})\)$/;
      var matches = ret.match(reg);
      if (matches) {
        ret = JSON.parse(matches[1])
      }
    }
    res.json(ret)
  }).catch((e) => {
    console.log(e)
  })
});

// 使用这个路由
app.use("/api", apiRoutes);

3.2 JSONP 方式

JSONP有些文章会叫动态建立script,由于他确实是动态写入 script 标签的内容从而达到跨域的效果:

  • AJAX 没法跨域是受到“同源政策”的限制,可是带有src属性的标签(例如<script>、<img>、<iframe>)是不受该政策限制的,所以咱们能够经过向页面中动态添加<script>标签来完成对跨域资源的访问,这也是 JSONP 方案最核心的原理,换句话理解,就是利用了【前端请求静态资源的时候不存在跨域问题】这个思路。
  • JSONP(JSON with Padding)是数据格式JSON的一种“使用模式”。
  • JSONP 只能用 get 方式。

实现 jsonp 的方式:

clipboard.png

引用来自http://www.javashuo.com/article/p-ayiskmas-bm.html的图

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

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

这是一个实例 demo:

服务器端文件ip.js

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

客户端文件 jsonp.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script>
        // 动态插入 script 标签到 html 中
        function addScriptTag(src) {
          var script = document.createElement('script');
          script.setAttribute("type","text/javascript");
          script.src = src;
          document.body.appendChild(script);
        }
        // 获取 jsonp 文件
        window.onload = function () {
          addScriptTag('http://example.com/ip?callback=foo');
        }
        // 执行本地的 js 逻辑,这个要跟获取到的 jsonp 文件的函数要一致
        function foo(data) {
          console.log('Your public IP address is: ' + data.ip);
        };
    </script>
</head>
<body>
</body>
</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字段

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对应服务器端的Access-Control-Allow-Origin设置,因此通常来讲须要在服务器端加上这个Access-Control-Allow-Origin 指定域名|*

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


参考文档:

相关文章
相关标签/搜索