1.前言
我们都知道,由于浏览器的同源策略,所以在前后端分离的开发过程中,难免出现跨域的问题。具体的解决方法有CORS
、JSONP
、webpack-dev-server
、nginx
等等。本文重点讨论下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
标签的先后顺序。 - 利用
vscode
的live 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/')
可利用vscode
的code 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
请求与axios1
、xmlhttp
请求的不同之处。
axios2
的post
请求,默认的content-type
是application/json
。这是因为json
在前后端分离的实际开发中,已经逐渐被大众接受。于是axios
封装的post
请求默认已经按照这种MIME类型。axios
的post
请求,在有post请求参数的时候,才会将content-type
设置为application/json
。如果没有参数,请求头默认是没有content-type
。这也是为什么axios1
没有出错、请求成功的原因。XMLHttpRequest
请求,我们手动设置了请求头中的content-type
为application/x-www-form-urlencoded
。
5.揭开CORS的面纱
在日常实际跨域的时候,分两种请求:简单请求和预检请求。
5-1.简单请求
若请求满足下述所有条件,则该请求可视为简单请求。
- 使用下列方法之一
GET
POST
HEAD
- 能人为的在请求头中添加的字段只包含以下:
Accept
Accept-Language
Content-Language
Content-Type
(该字段有额外的限制,请看下面第3项)DPR
Downlink
Save-Data
Viewport-Width
Width
Content-Type
的值仅限于下列三者之一:text/plain
application/x-www-form-urlencoded
multipart/form-data
- 请求中的任意
XMLHttpRequestUpload
对象均没有注册任何事件监听器。XMLHttpRequestUpload
对象可以使用XMLHttpRequest.upload
属性访问。 - 请求中没有使用
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控制台,就会发现axios2
的options
请求后的post
请求也已经成功了。

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