处理跨域:手动实现CORS和JSONP流程

准备

首先明白一件事:
跨域是浏览器的限制html

也就是说不是你真的不能访问,而是浏览器出于他本身的种种担忧拦截了你的访问。前端

至于浏览器在担忧什么(什么是跨域,为何会有跨域),参考:百度node

其实cors的原理就是,告诉浏览器:"你别担忧啦,这个请求我ok的,让他访问吧"。
而jsonp的原理是,使用浏览器不担忧的方式去请求:script标签中的src属性(其余带有src属性的标签也能够,好比img)。ajax

先来模拟出跨域的场景,在本地启动一个最简单的node服务,返回查询参数。json

// 新建一个server.js文件,固然前提要安装node

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  const query = querystring.parse(req.url.split('?')[1]);
  const queryStr = JSON.stringify(query);
  res.writeHead(200, {
    "Content-type": "text/plain; charset=utf-8"
  });
  res.end(queryStr);
});

server.listen(9999);
console.log('server run at 9999');
复制代码

启动服务后端

node server.js
复制代码

先在浏览器里直接访问一下 http://127.0.0.1:9999/?name=无用书生&age=25 看到返回没有问题跨域

而后就在当前页面(你如今正在阅读文章的掘金页面)f12打开控制台,在console里建立一个ajax请求浏览器

var xhr = new XMLHttpRequest();
xhr.open('get', 'http://127.0.0.1:9999/?name=无用书生&age=25');
xhr.send();
复制代码

执行之后就出现跨域报错了bash

CORS

首先来看一下cors的跨域原理,其实报错里就写的很明白,咱们访问的资源没有设置对掘金这个访问源的头。markdown

在cors的规则中,请求分为简单请求和非简单请求,咱们上面发送的就是一个简单请求。

关于简单请求和非简单请求,参考:CORS跨域原理解析

对于简单请求

只要在响应头中(response header)指明容许哪些访问源访问就能够了。

在server.js中给响应头添加 Access-Control-Allow-Origin

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  const query = querystring.parse(req.url.split('?')[1]);
  const queryStr = JSON.stringify(query);
  res.writeHead(200, {
    "Content-type": "text/plain; charset=utf-8",
    "Access-Control-Allow-Origin": "*"    // * 表明容许全部的源访问
  });
  res.end(queryStr);
});

server.listen(9999);
console.log('server run at 9999');
复制代码

重启node服务,再试一次

报错没有了,打开network能够看到 Access-Control-Allow-Origin 已经生效,数据也成功获取到

对于非简单请求

将上面请求的方法从get改为put,再次请求 (put方法就属于非简单请求)

var xhr = new XMLHttpRequest();
xhr.open('put', 'http://127.0.0.1:9999/?name=无用书生&age=25');
xhr.send();
复制代码

能够看到跨域报错又出现了

刚才设置的响应头依然存在,却不起做用了。
另外这里能够看到咱们原本发送的是put请求,请求方法那里写的倒是options。缘由就是对于非简单请求浏览器会先发送一次预检,预检经过才会发送真正的请求,这个options就是预检请求,由于没有经过,因此也就没有发送真正的请求

其实报错中也写的很明白,咱们访问的资源没有设置容许对PUT这个方法的访问

在server.js中给响应头添加 Access-Control-Allow-Methods,设置容许put方法的请求

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  const query = querystring.parse(req.url.split('?')[1]);
  const queryStr = JSON.stringify(query);
  res.writeHead(200, {
    "Content-type": "text/plain; charset=utf-8",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT",
  });
  res.end(queryStr);
});

server.listen(9999);
console.log('server run at 9999');
复制代码

重启服务之后再次请求,能够看到跨域报错就消失了

还能够看到依然先进行了一次预检请求,此次预检请求经过了,继续发送了put请求

非简单请求还对请求头的信息有所限制,原理仍是同样的,经过Access-Control-Allow-Headers在返回头中设置容许的访问头就ok了,好比

var xhr = new XMLHttpRequest();
xhr.open('put', 'http://127.0.0.1:9999/?name=无用书生&age=25');
xhr.setRequestHeader("X-Corx-Test", "aabbcc");
xhr.send();
复制代码

设置容许 X-Corx-Test这个请求头

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  const query = querystring.parse(req.url.split('?')[1]);
  const queryStr = JSON.stringify(query);
  res.writeHead(200, {
    "Content-type": "text/plain; charset=utf-8",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT",
    "Access-Control-Allow-Headers": "X-Corx-Test"
  });

  res.end(queryStr);
});

server.listen(9999);
console.log('server run at 9999');
复制代码

JSONP

因为掘金作了csp处理,没法测试jsonp,用百度进行演示。

什么是csp,参考:阮一峰博客

去掉node服务中对cors的配置

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  const query = querystring.parse(req.url.split('?')[1]);
  const queryStr = JSON.stringify(query);
  res.writeHead(200, {
    "Content-type": "text/plain; charset=utf-8"
  });

  res.end(queryStr);
});

server.listen(9999);
console.log('server run at 9999');
复制代码

在百度首页f12打开控制台,这时候若是再使用ajax请求咱们的服务又会报跨域的错误

上面说过jsonp的原理就是使用script标签的src不受浏览器跨域限制的原理
在console中建立一个jsonp请求

var script = document.createElement('script');
script.src = 'http://127.0.0.1:9999/?name=无用书生&age=25';
document.head.appendChild(script);
复制代码

执行之后看到这时候就没有报错了

在network中能够看到这是一个成功的get请求,注意jsonp只能发送get请求

这时候请求虽然成功了,还没拿到返回的数据
获取数据的方法就是在前端定义一个接收数据的函数,而后后端返回的js中执行这个函数,并把要返回的数据做为参数传入
好比在前端定义一个叫作 getData 的函数

var script = document.createElement('script');
script.src = 'http://127.0.0.1:9999/?name=无用书生&age=25';
document.head.appendChild(script);
function getData(res) {
  console.log(res);
}
复制代码

在后端返回的内容中调用这个函数,把数据传进去

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  const query = querystring.parse(req.url.split('?')[1]);
  const queryStr = JSON.stringify(query);
  res.writeHead(200, {
    "Content-type": "text/plain; charset=utf-8",
  });
  let jsonpStr = `getData(${queryStr})`;
  res.end(jsonpStr);
});

server.listen(9999);
console.log('server run at 9999');
复制代码

重启服务之后,执行前端代码,数据就能够取到了

这个函数名字要先后端约定一致,另外获取完数据之后最好移除一下script标签

var script = document.createElement('script');
script.src = 'http://127.0.0.1:9999/?name=无用书生&age=25';
document.head.appendChild(script);
function getData(res) {
  console.log(res);
  document.head.removeChild(script);
}
复制代码