客户端在请求 CORS 接口时,根据请求方式和请求头的不同,可以将 CORS 的请求分为两大类,分别是: 简单请求和预检请求
一、简单请求
同时满足以下两大条件的请求,就属于简单请求:
请求方式:GET、POST、HEAD 三者之一
请求头(仅包含安全的字段,常见的安全字段如下):
Accept
Accept-Language
Content-Language
DPR
Downlink
Save-Data
Viewport-Width
Width
Content-Type
Content-Type:只有三个值(来自chatgpt4.0)
-
application/x-www-form-urlencoded
: 这是最常见的 POST 提交数据的方式。我们通过表单输入数据,提交表单,那么浏览器会把表单中的数据按照 key1=val1&key2=val2 的方式进行编码,然后发送给服务器。 -
multipart/form-data
: 这种类型主要用于上传文件。在浏览器中,multipart/form-data_encode方式往往结合着 form 的使用,它假设表单中的每个fields都是一部分,一般在上传文件的时候使用。 -
text/plain
: 文本格式。虽然这种格式在POST请求中很少见,但是在一个POST请求需要被发送为一个没被格式化的字符串形式的时候,这种Content-Type就派上用场了。
二、预检请求(看文章最后的图片)
只要符合以下任何一个条件的请求,都需要进行预检请求:
- 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
- 请求头中包含自定义头部字段
- 向服务器发送了 application/json 格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为“预检请求”。
服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
三、简单请求和预检请求的区别:
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求。
四、简单请求过程:
当浏览器判定某个ajax跨域请求是简单请求时,会发生以下的事情
4.1请求头中会自动添加Origin
字段
比如,在页面http://my.com/index.html中发起了以下跨域请求:
axios("http://crossdomain.com/api/news");
请求发出后,请求头会是下面的格式:
GET /api/news/ HTTP/1.1 Host: crossdomain.com
Connection: keep-alive
Referer: http://my.com/index.html
Origin: http://my.com
Origin
字段会告诉服务器,是哪个源地址在跨域请求
注意:如果使用file协议打开网页,则orgin为null
4.2服务器响应头中应包含Access-Control-Allow-Origin
当服务器收到请求后,如果允许该请求跨域访问,需要在响应头中添加
Access-Control-Allow-Origin
字段。该字段的值可以是:
- *:表示全部放行
- 具体的源:比如
http://my.com
,表示我就允许你访问
假设服务器做出了以下的响应:
HTTP/1.1 200 OK Date: Tue, 21 Apr 2020 08:03:35 GMT
Access-Control-Allow-Origin: http://my.com
消息体中的数据
当浏览器看到服务器允许自己访问后,于是,它就把响应顺利的交给js,以完成后续的操作。否则,你会就看到如下图片:
五、预检请求过程:
简单的请求对服务器的威胁不大,所以允许使用上述的简单交互即可完成。
如果浏览器不认为这是一种简单请求,就会按照下面的流程进行:
- 浏览器发送预检请求,询问服务器是否允许
- 服务器允许
- 浏览器发送真实请求
- 服务器完成真实的响应
比如,在页面http://my.com/index.html
中有以下代码造成了跨域
axios("http://crossdomain.com/api/user",{
method:"POST",
headers:{// 设置请求头
a: 1,
b: 2,
content-type: "application/json"
},
body: JSON.stringify({ name: "john", age: 20 })
});
浏览器发现它不是一个简单请求,则会按照下面的流程与服务器交互
5.1 浏览器发送预检请求,询问服务器是否允许
OPTIONS /api/user HTTP/1.1 Host: crossdomain.com
Origin: http://my.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: a, b, content-type
可以看出,这并非我们想要发出的真实请求,请求中不包含我们的响应头,也没有消息体。
这是一个预检请求,它的目的是询问服务器,是否允许后续的真实请求。
预检请求没有请求体,它包含了后续真实请求要做的事情
预检请求有以下特征:
- 请求方法为
OPTIONS
- 没有请求体
-
请求头中包含
Origin
:请求的源,和简单请求的含义一致Access-Control-Request-Method
:后续的真实请求将使用的请求方法Access-Control-Request-Headers
:后续的真实请求会改动的请求头
5.2服务器允许
服务器收到预检请求后,可以检查预检请求中包含的信息,如果允许这样的请求,需要响应下面的消息格式
HTTP/1.1 200 OK Date: Tue, 21 Apr 2020 08:03:35 GMT
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: a, b, content-type
Access-Control-Max-Age: 86400
对于预检请求,不需要响应任何的消息体,只需要在响应头中添加:
Access-Control-Allow-Origin
:和简单请求一样,表示允许的源Access-Control-Allow-Methods
:表示允许的后续真实的请求方法Access-Control-Allow-Headers
:表示允许改动的请求头-
Access-Control-Max-Age
:告诉浏览器,多少秒内,对于同样的请求源、方法、头,都不需要再发送预检请求了
5.3浏览器发送真实请求
预检被服务器允许后,浏览器就会发送真实请求了,上面的代码会发生下面的请求数据
POST /api/user HTTP/1.1 Host: crossdomain.com
Connection: keep-alive
Referer: http://my.com/index.html
Origin: http://my.com
{"name": "john", "age": 20 }
5.4 服务器响应真实请求
HTTP/1.1 200 OK Date: Tue, 21 Apr 2020 08:03:35 GMT
Access-Control-Allow-Origin: http://my.com
可以看出,当完成预检之后,后续的处理与简单请求相同。下图为预检请求,请求过程图。