【小哥哥, 跨域要不要了解下】CORS 进阶篇

系列文章:前端

预检请求的诞生

前一篇文章结尾, 咱们发现使用 CORS 方式实现跨域, 有时候会发送两个请求 一个 OPTIONS 一个正常请求, 这个 OPTIONS 是个什么鬼呢?node

下面贴一段 MDN 的解释 git

2018-12-08-09-17-13

众所周知, 后端 API 设计比较流行的范式就是 restful(到 2018 年 12 月 8 日). 在 restful 中分别用不一样的 HTTP METHOD 标识后端的 CURD, 对于使用这些可能会更新后端数据的 HTTP METHOD 发出的跨域请求, 浏览器要首先和服务器商定一下当前的域名是否是有执行对应的 CURD 的权限. 因而这个 OPTIONS 类型的 预检请求 就诞生了. 那么问题来了 可能对服务器数据产生反作用的 HTTP 请求方法 是有那些咧? 不知道么有关系, TIM 队长为咱们探探路 😄程序员

2018-12-08-09-32-33

简单请求 VS 复杂请求

在 CORS 机制中, 把请求分为了 简单请求复杂请求, 一个 HTTP 请求若想要让本身成为一个简单请求就要知足如下条件:github

  • 首先, 请求方式的限制: 请求方式(method) 只能是 GET POST HEAD 三者中的一个
  • 其次就是请求头字段的限制: 请求头字段必须包含在如下集合中, 包括: Accept Accept-Language Content-Language Content-Type DPR Downlink Save-Data Viewport-Width Width.
  • 其次就是请求头值的限制: 当请求头中包含 Content-Type 的时候, 其值必须为 text/plain multipart/form-data application/x-www-form-urlencoded(这个是 form 提交默认的 Content-Type) 三者中的一个.

综上, 只要前端发出的请求知足以上三个条件, 你发出的请求就是简单请求. 那么什么事复杂请求呢? 答: 只要不是简单请求就是复杂请求. 本来觉得很难理清的概念, 竟然只有三个条件搞定 ^_^.web

2018-12-08-09-58-34

再告诉你们一个秘密, 全部的简单请求跨域访问都是不会触发预检请求的哟. 那是复杂请求的专利...ajax

预检请求都干了啥 😳

对于复杂请求发生跨域访问前, 老是要经过预检请求进行鉴权. 那么鉴权的过程究竟是啥么样子的呢? 这一步咱们一块儿来研究一下.json

  • 首先, 打开上一节的代码
  • 分别执行 node ./be/cors/index.js live-server ./fe/cors 启动后端服务和前端的 web 容器.
  • 浏览器自动打开后打开控制台, 切换到 Network tab 并刷新浏览器. 不出意外的话, 看到的是这个样子的
    2018-12-08-10-09-38
  • 点击一下第一个 localhost 请求并查看详情
    2018-12-08-10-22-25
    不难发现, 响应头里标注的几个字段, 就是咱们的后端项目里边写的几个.
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Access-Control-Allow-Methods', 'PUT');
response.setHeader('Access-Control-Allow-Headers', 'token');
response.setHeader('Access-Control-Max-Age', 5);
复制代码

一一对应, 绝非偶然 😄. 那么请求头中标注的两个又是什么意思呢?后端

浏览器在接受到咱们发送的跨域请求的指令时, 会自动判断咱们的请求是否属于跨域请求, 若是是的话便会发出预检请求, 预检请求的请求头信息也是浏览器根据咱们的请求信息自动添加的. 示例项目中, 由于咱们的请求是 PUT 类型的, 因此在预检请求的时候会添加 Access-Control-Allow-Methods: PUT 来咨询服务器本身是否能够向它发送这种类型的请求. 同理, 因为咱们的请求中有自定义请求头 token 因此, 在预检请求中, 浏览器要和服务器作是否能够添加自定义请求头的协商. 只有当浏览器和服务器之间的预检请求协商经过了, 浏览器才会继续发送真正的 AJAX 请求.api

老板说, 我不想看到多余的请求

在工做中, 老板每每是不懂技术的. 能看控制台的老板通常是高手了. 面对这种一个 api 发两次请求的状况可能一个程序员笑笑也就过去了, 可是老板就不这么认为了, 一个接口他就要一次请求. 你要把 圈圈的圈 跨域文章推荐给老板, 让小哥哥也了解下? 估计你会被 fire 掉. 那肿么办呢?

2018-12-08-10-42-18

面对这种状况, 有两种解决方案.

  • 第一种, 能够和后端小哥哥商量一下. 把接口改为简单请求, 预检请求的问题就迎刃而解了.
  • 然而, 有时候写好的代码谁都不肯意去改. 后端小哥哥不听话. 这种状况下 Access-Control-Max-Age 就派上用场了. 这个响应头的意思是预检请求的有效期. 在指定时间内再次跨域访问接口, 是不须要预检请求的, 单位是 . 若是咱们把有效时间写的很是的长, 那么四不四看上去就像删除了预检请求了呢 ^_^.
  • 附加状况, 你老板不懂技术瞎 J8 指挥. 小爷我不干了. 固然这种处理方案比较不推荐.

PS: 使用 Access-Control-Max-Age 机制和缓存相似, 因此给老板演示的时候千万不要清理缓存. 不要勾选 Network 下的 disable cache. 不说啦, 都是泪...

2018-12-08-11-13-21

我们不能容许全部的人都访问呀

经过 Access-Control-Allow-Origin, 能够在后端设置能够跨域访问咱们的域名列表, * 表明全部的域名均可以跨域访问咱们的后端, 这样实际上是有隐患的. 为了安全起见, 咱们把能够跨域访问的域名限制为咱们已知的域名. 老规矩.

2018-12-08-11-24-04

后端代码

// 修改一行代码, 必定要添加协议哟
response.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8080');
复制代码

修改之后浏览器访问 http://127.0.0.1:8080

2018-12-08-11-27-57

若是想要开放多个域名的跨域访问咋办咧?

若是咱们有多个业务域名须要跨域访问同一个服务器, 能够把容许的域名列表保存到一个数组里. 接到请求以后先判断当前请求域名是否在咱们容许的域名列表里, 若是在的话直接添加到响应头 Access-Control-Allow-Origin 下.

后端代码

const http = require('http');

const PORT = 8888;

// 协议名必填, 若是同时存在 http 和 https 就写两条s
const allowOrigin = ['http://127.0.0.1:8080', 'https://www.baidu.com'];

// 建立一个 http 服务
const server = http.createServer((request, response) => {
  const { headers: { origin } } = request;
  if (allowOrigin.includes(origin)) {
    response.setHeader('Access-Control-Allow-Origin', origin);
  }
  response.setHeader('Access-Control-Allow-Methods', 'PUT');
  response.setHeader('Access-Control-Allow-Headers', 'token');
  response.setHeader('Access-Control-Max-Age', 5);
  response.end("{name: 'quanquan', friend: 'guiling'}");
});

// 启动服务, 监听端口
server.listen(PORT, () => {
  console.log('服务启动成功, 正在监听: ', PORT);
});
复制代码

此时代码, 首先访问http://127.0.0.1:8080

2018-12-08-11-46-03
响应结果成功打印, 没有任何问题.

其次访问 www.baidu.com, 打开控制台, 执行

xhr = new XMLHttpRequest()
xhr.open('GET', 'http://localhost:8888')
xhr.onreadystatechange = () => {
    xhr.status === 200 && xhr.readyState === 4 && console.log(xhr.responseText)
}
xhr.send()
复制代码

2018-12-08-11-54-13

没有任何报错, 返回结果成功打印. 成功...

你的请求怎么没有携带 Cookie

通常状况下, 前端发出的跨域的 ajax OR fetch 请求是不会携带 Cookie 的. 可是, 后端小哥哥还要. 咋弄咧? 加上呗.

2018-12-08-12-05-05

前端代码:

// 在 xhr.send 以前添加这一行
xhr.withCredentials = true;
复制代码

添加完之后, 刷新浏览器.

2018-12-08-12-07-24

对于这个报错, 不知道你有没有啥好说的, 反正我是没啥话了...

后端代码:

const http = require('http');

const PORT = 8888;

// 协议名必填, 若是同时存在 http 和 https 就写两条s
const allowOrigin = ['http://127.0.0.1:8080', 'http://localhost:8080', 'https://www.baidu.com'];

// 建立一个 http 服务
const server = http.createServer((request, response) => {
  const { method, headers: { origin, cookie } } = request;
  if (allowOrigin.includes(origin)) {
    response.setHeader('Access-Control-Allow-Origin', origin);
  }
  response.setHeader('Access-Control-Allow-Methods', 'PUT');
  // 容许前端请求携带 Cookie
  response.setHeader('Access-Control-Allow-Credentials', true);
  response.setHeader('Access-Control-Allow-Headers', 'token');
  if (method === 'OPTIONS') {
    console.log('预检请求');
  } else if (!cookie) {
    // 若是不存在 Cookie 就设置 Cookie
    response.setHeader('Set-Cookie', 'quanquan=fe');
  }
  response.end("{name: 'quanquan', friend: 'guiling'}");
});

// 启动服务, 监听端口
server.listen(PORT, () => {
  console.log('服务启动成功, 正在监听: ', PORT);
});
复制代码

此时代码, 再次到浏览器看一下.

Cookie 中多了一条

2018-12-08-12-37-50

请求中携带了 Cookie

2018-12-08-12-37-26

经过下边的动图能够看出, 咱们先后端 Cookie 传递很是的通畅.

cookie-2134567

我在响应头上给你返回了 Token, 你取出来放在请求头上

工做中经常遇到后端把一些标识放在响应头上返回给前端的 case, 好比用户登陆, 后端返回用户的惟一标识放在响应头上. 须要前端获取, 后续的请求都须要把这个标识放在请求头, 用于验证用户的身份.

咱们首先修改后端代码:

// 在 response.end() 前添加这一行
response.setHeader('token', 'quanquan');
复制代码

修改前端代码:

xhr.onreadystatechange = function() {
  if(xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.responseText)
    // 打印响应数据时同时打印全部响应头
    console.log(xhr.getAllResponseHeaders())
  }
}
复制代码

修改完成后代码, 浏览器看一下.

console.log 打印出了空行

2018-12-08-13-26-21

可是在 Network Tab 下后端确实返回了响应头 token 字段. 懵逼了...

2018-12-08-13-28-41

原来, Access-Control- 系列还有一个响应头 Access-Control-Expose-Headers, 咱们在后端代码 response.end(...) 以前加上 response.setHeader('Access-Control-Expose-Headers', 'token');再次会浏览器查看

2018-12-08-13-31-40

成功了 😄.

预检请求不返回内容把

咱们的响应结果原本应该是在正式的请求中才须要返回的, 可是咱们看下预检请求的返回详情发现

2018-12-08-13-34-09

预检请求只是浏览器层面的解析, 前端代码根本拿不到. 这里的内容仅仅是浪费带宽和用户的流量. 因此咱们改造一下.预检请求再也不返回内容.

后端代码:

const http = require('http');

const PORT = 8888;

// 协议名必填, 若是同时存在 http 和 https 就写两条s
const allowOrigin = ['http://127.0.0.1:8080', 'http://localhost:8080', 'https://www.baidu.com'];

// 建立一个 http 服务
const server = http.createServer((request, response) => {
  const { method, headers: { origin, cookie } } = request;
  if (allowOrigin.includes(origin)) {
    response.setHeader('Access-Control-Allow-Origin', origin);
  }
  response.setHeader('Access-Control-Allow-Methods', 'PUT');
  response.setHeader('Access-Control-Allow-Credentials', true);
  response.setHeader('Access-Control-Allow-Headers', 'token');
  response.setHeader('Access-Control-Expose-Headers', 'token');
  response.setHeader('token', 'quanquan');
  if (method === 'OPTIONS') {
    response.writeHead(204);
    response.end('');
  } else if (!cookie) {
    response.setHeader('Set-Cookie', 'quanquan=fe');
  }
  response.end("{name: 'quanquan', friend: 'guiling'}");
});

// 启动服务, 监听端口
server.listen(PORT, () => {
  console.log('服务启动成功, 正在监听: ', PORT);
});
复制代码

此时代码, 验证, 就不验证了吧. 好使 😄

下基预告: 前两种跨域方案就算是讲完了, 很多小伙伴吐槽, jsonp 太老, cors 太麻烦.... 那么下一节咱们尝试一下 反向代理, See you

相关文章
相关标签/搜索