跨域请求的三种处理方式JSONP,代理,CROS

跨域请求

场景:跨域请求报错:

Failed to load http://localhost:3000/crossdomain/cors: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘null’ is therefore not allowed access.

  • jsonp
  • cors cross origin resource sharing
  • 服务器代理

1.JSONP:

只要说到跨域,就必须聊到 JSONP,JSONP 全称为:JSON with padding,可用于解决老版本浏览器的跨域数据访问问题。

由于 web 页面上调用 js 文件不受浏览器同源策略的影响,所以通过 script 标签可以进行跨域请求:

  1. 首先前端需要先设置好回调函数,并将其作为 url 的参数。
  2. 服务端接收到请求后,通过该参数获取到回调函数名,并将数据放在参数中将其返回
  3. 收到结果后因为是 script 标签,所以浏览器会当做是脚本进行运行,从而达到跨域获取数据的目的

jsonp 之所以能够跨域的关键在于页面调用 JS 脚本是不受同源策略的影响,相当于向后端发起一条 http 请求,跟后端约定好函数名,后端拿到函数名,动态计算出返回结果并返回给前端执行 JS 脚本,相当于是一种 “动态 JS 脚本”

接下来我们通过一个实例来尝试:

后端逻辑:

// jsonp/server.js
const url = require("url");

require("http")
  .createServer((req, res) => {
    const data = {
      x: 10
    };
    // 拿到回调函数名
    const callback = url.parse(req.url, true).query.callback;
    console.log(callback);
    res.writeHead(200);
    res.end(`${callback}(${JSON.stringify(data)})`);
  })
  .listen(3000, "127.0.0.1");

console.log("启动服务,监听 127.0.0.1:3000");

前端逻辑:

// jsonp/index.html
<script>
    function jsonpCallback(data) {
        alert('获得 X 数据:' + data.x);
    }
</script>
<script src="http://127.0.0.1:3000?callback=jsonpCallback"></script>

然后在终端开启服务:

之所以能用脚本指令,是因为我在 package.json 里面设置好了脚本命令:

{
  // 输入 yarn jsonp 等于 "node ./jsonp/server.js & http-server ./jsonp"
  "scripts": {
    "jsonp": "node ./jsonp/server.js & http-server ./jsonp",
    "cors": "node ./cors/server.js & http-server ./cors",
    "proxy": "node ./serverProxy/server.js",
    "hash": "http-server ./hash/client/ -p 8080 & http-server ./hash/server/ -p 8081",
    "name": "http-server ./name/client/ -p 8080 & http-server ./name/server/ -p 8081",
    "postMessage": "http-server ./postMessage/client/ -p 8080 & http-server ./postMessage/server/ -p 8081",
    "domain": "http-server ./domain/client/ -p 8080 & http-server ./domain/server/ -p 8081"
  },
  // ...
}
yarn jsonp
// 因为端口 3000 和 8080 分别属于不同域名下
// 在 localhost:3000 查看效果,即可收到后台返回的数据 10

至此,通过 JSONP 跨域获取数据已经成功了,但是通过这种方式也存在着一定的优缺点:

优点:

  1. 它不像 XMLHttpRequest 对象实现 Ajax 请求那样受到同源策略的限
  2. 兼容性很好,在古老的浏览器也能很好的运行
  3. 不需要 XMLHttpRequest 或 ActiveX 的支持;并且在请求完毕后可以通过调用 callback 的方式回传结果。

缺点:

  1. 它支持 GET 请求而不支持 POST 等其它类行的 HTTP 请求。
  2. 它只支持跨域 HTTP 请求这种情况,不能解决不同域的两个页面或 iframe 之间进行数据通信的问题
  3. 无法捕获 Jsonp 请求时的连接异常,只能通过超时进行处理

扩展:关于jsonp的误区

  1. 动态请求就会有跨域的问题

    跨域只存在于浏览器端,不存在于安卓/ios/Node.js/python/ java等其它环境

  2. 跨域就是请求发不出去了

    跨域的请求是可以发送出去的,并且服务端可以接收到请求并返回结果,只是结果被浏览器给拦截了。

2.CORS:

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

CORS 需要浏览器和服务器同时支持才可以生效,对于开发者来说,CORS 通信与同源的 ajax 通信没有差别,代码完全一样。浏览器一旦发现 ajax 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。

前端逻辑很简单,只要正常发起 ajax 请求即可:

// cors/index.html
<script>
	const xhr = new XMLHttpRequest();
	xhr.open('GET', 'http://127.0.0.1:3000', true);
	xhr.onreadystatechange = function() {
		if(xhr.readyState === 4 && xhr.status === 200) {
			alert(xhr.responseText);
		}
	}
	xhr.send(null);
</script>

这似乎跟一次正常的异步 ajax 请求没有什么区别,关键是在服务端收到请求后的处理:

// cors/server.js
require("http")
  .createServer((req, res) => {
    res.writeHead(200, {
      "Access-Control-Allow-Origin": "http://localhost:8080",
      "Content-Type": "text/html;charset=utf-8"
    });
    res.end("这是你要的数据:1111");
  })
  .listen(3000, "127.0.0.1");

console.log("启动服务,监听 127.0.0.1:3000");

成功的关键在于 Access-Control-Allow-Origin 是否包含请求页面的域名,如果不包含的话,浏览器将认为这是一次失败的异步请求,将会调用 xhr.onerror 中的函数。

CORS 的优缺点:

  1. 使用简单方便,更为安全
  2. 支持 POST 请求方式
  3. CORS 是一种新型的跨域问题的解决方案,存在兼容问题,仅支持 IE 10 以上

扩展CORS:预检请求

CORS把请求分为两种,一种是简单请求,另一种是复杂请求(需要触发预检请求),这两者是相对的,怎样才算“不简单”?只要属于下面的其中一种就不是简单请求:

(1)使用了除GET/POST/HEAD之外的请求方式,如PUT/DELETE

(2)使用了除Content-Type/Accept等几个常用的http头

预检请求使用OPTIONS方式去检查当前请求是否安全,请求如下:

full 204 xhr
full 200 xhr

服务端响应response headers如下:

Access-Control-Allow-Headers DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,body 允许的请求头
Access-Control-Allow-Methods GET, POST, OPTIONS, PUT, DELETE, PATCH 允许的请求方式
Access-Control-Allow-Max-Age 17280000 预检请求有效期:发送OPTIONS的时间间隔

如果在预检请求检测到当前请求不符合服务端设定的要求,则不会发出去了直接抛异常,这个时候就不用去发“复杂”的请求了。

为了支持CORS,nginx可以这么配:

location / {
     if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204;
     }
     add_header 'Access-Control-Allow-Origin' '*';
     add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
     add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
     add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
}

3. 服务端代理:

服务器代理,顾名思义,当你需要有跨域的请求操作时发送请求给后端,让后端帮你代为请求,然后最后将获取的结果发送给你。

假设有这样的一个场景,你的页面需要获取 CNode:Node.js 专业中文社区 论坛上一些数据,如通过 https://cnodejs.org/api/v1/topics,当时因为不同域,所以你可以将请求后端,让其对该请求代为转发

代码如下:

// serverProxy/server.js
const url = require("url");
const http = require("http");
const https = require("https");

const server = http
  .createServer((req, res) => {
    const path = url.parse(req.url).path.slice(1);
    if (path === "topics") {
      https.get("https://cnodejs.org/api/v1/topics", resp => {
        let data = "";
        resp.on("data", chunk => {
          data += chunk;
        });
        resp.on("end", () => {
          res.writeHead(200, {
            "Content-Type": "application/json; charset=utf-8"
          });
          res.end(data);
        });
      });
    }
  })
  .listen(3000, "127.0.0.1");
console.log("启动服务,监听 127.0.0.1:3000");

通过代码你可以看出,当你访问 http://127.0.0.1:3000/topics 的时候,服务器收到请求,会代你发送请求 https://cnodejs.org/api/v1/topics 最后将获取到的数据发送给浏览器。

遗留思考问题:

  1. cookie token 具体的用处
  2. 网路安全
  3. 跨域网络攻击。

你可能感兴趣的:(javascript,网络)