CORS知识点总结

1,337 阅读5分钟

1.前言

我们都知道,由于浏览器的同源策略,所以在前后端分离的开发过程中,难免出现跨域的问题。具体的解决方法有CORSJSONPwebpack-dev-servernginx等等。本文重点讨论下CORS。后端服务暂以原生node为例,另外编辑器使用vscode,浏览器为chrome81

2.启动项目服务

先使用yarn init -y创建一个初始项目。然后安装axios以及node

yarn add axios
yarn add node
2-1.前端
  • 创建一个http.js文件。这个文件会书写XMLHttpRequest以及axios来请求后端的接口。
  • 创建index.html。引入node_modules中的dist目录下的axios。以及之前的http.js。注意script标签的先后顺序。
  • 利用vscodelive server启动前端html服务。live server可以配置前端项目启动的ip以及端口。这里我设置成http://localhost:8082
2-2.后端

下面是node的代码: 这里我们先直接设置Access-Control-Allow-Origin*

const http = require('http')

http.createServer(function (request, res) {
  res.setHeader('Access-Control-Allow-Origin', '*')
  res.writeHead(200, {'Content-Type': 'text/plain'})
  res.end('Hello World')
}).listen(8081)

console.log('Server running at http://127.0.0.1:8081/')

可利用vscodecode run直接运行上面代码。这样,node服务就会启动在http://127.0.0.1:8081

3.前端发起请求

这里我们把请求分成两类,一类是原生XMLHttpRequest以及$.ajax。另一类就是我们实际项目经常用的axios。因为$.ajax实际上就是封装的XMLHttpRequest,所以归为一种。另外之所以这样分,是为了下面更好的区别一些东西。具体原因请继续看。以post请求为例,这里对请求地址加了标识,但是由于路由的原因,如果成功的话,都会返回hello world

3-1.XMLHttpRequest

由于请求路径是http://127.0.0.1:8081/api/xmlhttp。本文以下的xmlhttp请求均代指此次请求。

const xhr = new XMLHttpRequest()

// 监听事件
xhr.onreadystatechange = function () {
    // 请求完成
    if (this.readyState === 4) {
        if (this.status === 200) {
            //响应成功
            const httpResponseText = this.responseText
            console.log({httpResponseText})
        }
        else {
            // 响应失败 打印下状态码
            console.log(this.status)
        }
    }
    // 请求未完成
    else {
        console.log(this.readyState)
    }
}

// 发送请求
xhr.open('post', 'http://127.0.0.1:8081/api/xmlhttp')
xhr.setRequestHeader('content-type', 'application/x-www-urlencoded')
xhr.send()
3-2.axios

同上,axios1代表下面第一种请求,axios2代表下面第二种请求。

axios({
    method: 'post',
    url: 'http://127.0.0.1:8081/api/axios1'
}).then(res => {
    console.log({res})
}).catch(err => {
    console.warn(err)
})

axios.post('http://127.0.0.1:8081/api/axios2', {name: 'axios2'}).then(res => {
    console.log({res})
}).catch(err => {
    console.warn(err)
})

4.发起请求

至此,我们一共发起了3种请求。而且我们的node服务器已经配置了Access-Control-Allow-Origin。这3种请求是不是都能成功?你是不是觉得都能成功?我们看下实际发送请求后的控制台。

可以看到。axios2这个请求(就是用axios.post那个)失败了。失败原因的话,如下:

我来翻译下🤣。本次请求已经CORS策略被阻止了。原因是因为请求头字段'content-type'没有被预检响应的'Access-Control-Allow-Headers'所允许。 是不是觉得翻译拗口,我真的尽力了。😂别怕,等下就知道出现的原因以及解决方式了。而且到现在才刚刚到本文想要尽力讲清楚的地方。

在接触CORS的具体策略之前,要明白的一件事是axios2请求与axios1xmlhttp请求的不同之处。

  • axios2post请求,默认的content-typeapplication/json。这是因为json在前后端分离的实际开发中,已经逐渐被大众接受。于是axios封装的post请求默认已经按照这种MIME类型。
  • axiospost请求,在有post请求参数的时候,才会将content-type设置为application/json。如果没有参数,请求头默认是没有content-type。这也是为什么axios1没有出错、请求成功的原因。
  • XMLHttpRequest请求,我们手动设置了请求头中的content-typeapplication/x-www-form-urlencoded

5.揭开CORS的面纱

在日常实际跨域的时候,分两种请求:简单请求预检请求

5-1.简单请求

若请求满足下述所有条件,则该请求可视为简单请求

  1. 使用下列方法之一
    • GET
    • POST
    • HEAD
  2. 能人为的在请求头中添加的字段只包含以下:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(该字段有额外的限制,请看下面第3项)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  3. Content-Type的值仅限于下列三者之一:
    • text/plain
    • application/x-www-form-urlencoded
    • multipart/form-data
  4. 请求中的任意XMLHttpRequestUpload对象均没有注册任何事件监听器。XMLHttpRequestUpload对象可以使用XMLHttpRequest.upload属性访问。
  5. 请求中没有使用ReadableStream对象。

浏览器会在简单请求中的请求头中自动设定origin属性。而且由于其他条件都已经满足,如content-type等,所以后端服务只设置Access-Control-Allow-Origin: '*'即可。正如我之前在node服务器中设置的,因而xmlhttp以及axios1直接成功了。

5-2.预检请求

当某个跨域请求不满足上述简单请求的条件,这时的浏览器会自动发送一条options方法的请求。这条请求,我们称之为预检请求。假如使用的是谷歌chrome浏览器v76版本及以上。这条options请求在控制台是默认隐藏的。而firefox目前是可以看见options预检请求。

chrome之所以这样做,是为了隐藏CORS的一些请求。但是这种方式对于开发者来说,确实不够友好。具体缘由请参见该链接以及该链接中的其他链接。click me~

打开方式是在chrome地址栏直接访问chrome://flags/#out-of-blink-cors。将页面上的Out of blink CORS选项,改为Disabled。然后relaunch浏览器,设置即可生效。下面是重启之后的chrome控制台。跟之前对比的话,可以看到失败的post请求已经变为options请求。

6.解决预检请求的跨域问题

CORS的整体流程如下:

对应本文上述axios2的问题,由此可知: 只要在node服务设置Access-Control-Allow-Headers: content-type;即可。

重新启动服务器后,再看下chrome控制台,就会发现axios2options请求后的post请求也已经成功了。

7.总结

之前在使用第三方的设置过CORS的后端接口时,我使用axiospost方式总是请求失败。测试Jquery$.ajax反而成功了。当时还是蛮懵的,排查不出具体原因。于是记录了下问题,前俩天遇见了CORS简单请求以及预检请求这俩概念,于是一切恍然大悟。