1. 简介
在网页中通过 JavaScript 发起 HTTP 请求时,有时会在实际请求之前多出一个 OPTIONS
请求。这个行为常常让人疑惑:为什么会有这样一个“多余”的请求?它到底起到了什么作用?
本文将解释 OPTIONS
请求出现的原因,以及它是如何与 CORS(跨域资源共享) 机制相关联的。同时我们也会探讨如何避免或减少这种被称为 preflight request(预检请求) 的请求。
2. 背景知识
浏览器在发送某些跨域请求之前,会先发送一个 OPTIONS
请求,这就是所谓的 预检请求(preflight request)。它属于 CORS(Cross-Origin Resource Sharing) 机制的一部分。
CORS 是一种浏览器安全机制,用于控制跨域请求的访问权限。当网页试图访问与当前域名、协议或端口不同的服务器时,就会触发 CORS 检查。
预检请求的作用是让浏览器在真正发送请求前,先询问服务器是否允许当前请求。只有服务器明确允许,浏览器才会继续发送实际请求。
举个例子:假设网页在 foo.baeldung.com
,要向 www.baeldung.com/demo
发送一个 PUT
请求,那么浏览器会先发送如下 OPTIONS
请求:
OPTIONS /demo HTTP/1.1
Host: www.baeldung.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36
Accept: */*
Accept-language: nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7
Connection: keep-alive
Origin: http://foo.baeldung.com
Access-Control-Request-Method: PUT
⚠️ 即使某些请求不需要预检,服务器也必须返回 Access-Control-Allow-Origin
头,否则浏览器会拦截响应。
3. 请求类型分类
CORS 将请求分为两类:
- ✅ 简单请求(Simple Request):无需预检的请求
- ❌ 非简单请求(Preflighted Request):需要先发送
OPTIONS
预检
3.1 简单请求的条件
要成为简单请求,必须满足以下所有条件:
- 请求方法为:
GET
、POST
或HEAD
- 请求头只能是以下几种:
Accept
Accept-Language
Content-Language
Content-Type
(且值只能是以下三种之一):application/x-www-form-urlencoded
multipart/form-data
text/plain
- 请求中不能使用
XMLHttpRequestUpload
的事件监听器 - 请求中不能使用
ReadableStream
3.2 非简单请求
不符合上述条件的请求,都会触发预检请求。例如:
- 使用了
PUT
、DELETE
、PATCH
方法 - 自定义了请求头,如
Authorization
、X-Requested-With
- 使用了
application/json
的Content-Type
- 使用了
fetch
API 的body
流
4. 如何避免发送 OPTIONS 请求
4.1 改为同源请求
首先确认是否真的需要跨域请求。比如:
- 是否误用了不同协议(HTTP vs HTTPS)?
- 是否域名配置错误?
- 是否可以通过反向代理将 API 接口和网页放在同一个域名下?
✅ 推荐做法:使用反向代理(如 Nginx、Spring Cloud Gateway)将前后端接口统一在同一个域名下,从根本上避免跨域问题。
4.2 改为简单请求
如果你控制前端代码,可以尝试将请求改写为符合简单请求的格式:
- 使用
GET
或POST
方法 - 设置
Content-Type
为application/x-www-form-urlencoded
示例代码如下:
var request = new XMLHttpRequest();
request.open("POST", 'http://localhost:8080/with-valid-cors-header');
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
request.send("a=b&c=d");
⚠️ 如果你尝试发送 application/json
类型的 POST 请求,就会触发预检请求。
5. 缓存 OPTIONS 请求
如果你无法避免预检请求,可以通过缓存 OPTIONS
响应来减少请求次数。
5.1 使用 Access-Control-Max-Age
头
服务器可以在响应 OPTIONS
请求时设置以下头信息:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, PUT, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 300
其中:
Access-Control-Allow-Origin
:允许的源Access-Control-Allow-Methods
:允许的请求方法Access-Control-Allow-Headers
:允许的请求头Access-Control-Max-Age
:预检结果缓存时间(单位:秒)
设置 Access-Control-Max-Age: 300
后,浏览器在 5 分钟内不会再发送 OPTIONS
请求,而是直接使用缓存的策略。
6. 总结
OPTIONS
请求是浏览器在跨域请求前发起的预检请求,属于 CORS 机制的一部分- 只有非简单请求才会触发预检
- 避免预检的最有效方式是改为同源请求或简单请求
- 若无法避免预检,可通过设置
Access-Control-Max-Age
缓存策略减少请求次数
✅ 小贴士:
- 发现请求前多了一个
OPTIONS
?先确认是否真的需要跨域 - 遇到 CORS 报错?优先检查响应头是否包含
Access-Control-Allow-Origin
- 不想频繁触发
OPTIONS
?记得设置Access-Control-Max-Age
缓存时间
通过理解浏览器行为和 CORS 规范,我们可以更高效地调试接口、减少不必要的网络请求,提升用户体验。