马克思前端学习札记程序员

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

2018-08-18  本文已影响22人  dnaEMx

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

解决 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:

这里主要说明这三种方式。其他方式暂不说明。

一、什么是跨域问题

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

提示的错误大致如下:

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

二、为什么会出现跨域问题

因为浏览器受到同源策略的限制,当前域名的js只能读取同域下的窗口属性。

换句话来说,就是跨越了浏览器的同源策略限制的时候,就会触发了我们所说的“跨域”问题。

2.1 什么是同源策略

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

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

协议是 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:不同源(端口不同)
image

总的来说,只要不是三者同时相同,那么就不是同源,那么就会触发同源策略限制。

2.2 同源策略限制了什么

限制了:

这就是我们平常所说的“跨域问题”。

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

三、解决跨域问题

3.1 使用反向代理方式

3.1.1 什么是反向代理?

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

image image

那么可以利用反向代理的原理,我们通过一个中间代理服务器(反向代理服务器),将客户端网络请求的一些 host,domain,port 和协议等东西进行改写,使其模拟为可以访问目标服务器的请求,模拟成不触犯同源策略的请求去请求目标服务器。

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

image

现在前端开发一般使用 nodejs来做本地反向代理服务器

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

var apiRoutes = express.Router();

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

// 访问反向代理的路由地址
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);

这段代码的执行原理是:

  1. node js 作为反向代理服务器,然后在它上面使用express实现路由功能,
  2. 在 nodejs 里面加入一条负责源端请求的路由映射,将它映射到目标服务器的 api 接口上,并且在这条路由里面将实现请求的改写,模拟目标服务器 api 接口的同源策略所需的要求。
  3. 源端会先请求 nodejs 反向代理服务器的之前设置的那条路由,会将参数传给他,然后nodejs 反向代理会将它的请求进行改写,然后转发到目标服务器。

3.2 使用JSONP方式

3.2.1 什么是 JSONP

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

3.2.1 如何使用JSONP来解决跨域问题:

简单一点的例子:

通过不受同源策略限制的标签,例如 script,将一段js代码间接地从外部引入。通过script标签向目标源发起一个GET请求,服务器根据请求的参数返回包含js的代码。

image
//本地代码
<script>
    // 这个函数名字跟服务器返回的那段 js 的函数名字是一样的,所以能够实现调用
    function getData(obj) { // 参数是一个对象
        var data = JSON.parse(obj);
        console.log(data.name);//jiavan
        console.log(data.age);//20
    }
</script>
<script src="http://cv.jiavan.com/test/data.php?callback=getData"></script>

//服务器上的代码
<?php
    $func = $_GET['callback'];
    $data = '{"name": "jiavan", "age": 20}';
    echo $func."(".$data.");";
?>

// 服务器返回的数据是一段 js 代码
getData( // 这是 js 的函数写法
    {  // 这是参数,参数是一个对象
        "name":"jiavan",
        "age": 20
    }
)

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

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

复杂一点的例子:

[图片上传失败...(image-afc47f-1534607373428)]

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

服务器端文件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通信的关键是服务器端。只要服务器端实现了CORS接口,就可以跨源通信。

3.3.1 CORS的请求分为两类

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

(1) 请求方法是以下三种方法之一:

(2)HTTP的头信息不超出以下几种字段:

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

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个地方即可,其他的可以触类旁通:


参考文档:

上一篇下一篇

猜你喜欢

热点阅读