在 web 开发时遇到 preflight CORS 跨域错误,如下图所示
所谓 preflight CORS,就是在使用跨域请求时,浏览器会先发送一个预检请求,也被称为 OPTIONS
请求,来确认服务器是否支持特定的请求方法、请求头、以及是否允许跨域请求。
本文来分析分析这个预检请求到底有啥用,以及为什么会出现跨域失败的情况。
什么是预检请求?
预检请求是浏览器在发送跨域请求时,在正式请求之前,向服务器发送的一个 HTTP OPTIONS 请求。预检请求的主要目的是为了确定服务器是否支持特定的请求方法、请求头、以及是否允许跨域请求。预检请求的响应中包含了支持的请求方法、请求头以及是否允许跨域请求的信息,如果服务器支持这些请求,则浏览器才会发送真正的请求。
所以,如果出现预检请求跨域失败的情况,一般可以通过以下几个方面进行排查:
- 服务端是否支持预检请求;
- 服务端是否支持特定的请求方法、请求头、以及是否允许跨域请求。
- 服务端是否正确返回了预检请求的响应。
为什么要有预检请求?
在 Web 开发中,由于浏览器的同源策略限制,跨域请求是一个很常见的问题。如果没有预检请求,浏览器就不能确定服务器是否支持特定的请求方法、请求头、以及是否允许跨域请求。这时浏览器会发出一个简单请求(比如 GET
或 POST
请求),服务器会拒绝该请求并返回错误信息。为了解决这个问题,引入了预检请求机制,用来确认服务器是否支持跨域请求,并根据响应信息决定是否发送真正的请求。
触发预检请求的条件
当满足以下条件时,浏览器会触发预检请求:
1、请求方法不是 GET
、HEAD
、POST
中的一种;
2、只能使用安全的请求头;
请求头 | 说明 |
---|---|
Accept |
指定客户端能够接收的内容类型 |
Accept-Language |
指定客户端能够接收的语言 |
Content-Language |
指定请求体中的语言 |
DPR |
指定设备的像素比 |
Downlink |
指定设备的下行速度 |
Save-Data |
指定是否启用节省数据模式 |
Viewport-Width |
指定设备的视口宽度 |
Width |
指定设备的屏幕宽度 |
Content-Type |
指定请求体的内容类型,只能使用 application/x-www-form-urlencoded 、multipart/form-data 、text/plain 中的一种 |
3、发送的请求数据是不可序列化的数据类型,比如 Blob
、File
等。
预检请求的响应
当浏览器发送预检请求时,服务器需要返回以下信息:
Access-Control-Allow-Origin
:指定允许跨域访问的域名;Access-Control-Allow-Methods
:指定允许的 HTTP 方法;Access-Control-Allow-Headers
:指定允许的请求头;Access-Control-Allow-Credentials
:指定是否允许发送 Cookie;Access-Control-Max-Age
:指定响应缓存时间。
如果服务器返回的信息不符合要求,浏览器将不会发送真正的请求,而是返回一个错误信息。
预检请求的缓存
由于预检请求的响应信息是不变的,所以可以进行缓存。服务器可以在响应中添加 Access-Control-Max-Age
头信息,来指定响应的缓存时间,避免重复发送预检请求。
预检请求的限制
预检请求可能会对性能产生一定的影响,因为每次跨域请求都需要发送一个额外的预检请求。为了避免这个影响,可以通过以下几个方面进行优化:
- 使用简单请求:对于满足条件的请求(请求方法为
GET
、HEAD
、POST
,没有自定义请求头),可以使用简单请求来避免预检请求。 - 减少跨域请求:尽量减少跨域请求的次数,可以通过将静态资源放在同一域名下,或者使用 CDN 来提高性能。
- 缓存预检请求的响应:可以使用
Access-Control-Max-Age
头信息来缓存预检请求的响应,避免重复发送预检请求。 - 减少请求头的使用:尽量减少使用自定义请求头,避免触发预检请求。
结语
预检请求是解决跨域请求的一个重要机制,它可以确保浏览器发送的请求是安全的,并且服务器也能够正确地处理这些请求。预检请求虽然会对性能产生一定的影响,但可以通过减少跨域请求、缓存预检请求的响应、减少请求头的使用等方式进行优化,提高性能。合理的使用预检请求,可以帮助我们更好地解决跨域请求的问题。