为什么有时候浏览器会发送一个奇怪的 OPTIONS 请求,有时候又不会?

深入浅出地解释浏览器的CORS预检机制,带你理解简单请求与非简单请求的区别,以及OPTIONS预检的触发条件。

当我们进行跨域请求时,就会出现标题所述的问题,这背后其实是浏览器的一套“安全检查”机制,它把跨域请求分成了两种类型:“简单请求”和“非简单请求”。

把这个过程想象成去一个需要门禁卡的公司拜访:

  • 简单请求:你只是去前台拿个快递,保安看了一眼觉得没啥风险,直接让你进去了。
  • 非简单请求:你想去参观核心机房,保安会拦住你,先打电话给内部员工(这就是预检),确认你的身份、目的、有没有权限,得到许可后,才放你进去。

一、什么是“简单请求” (Simple Request)?

浏览器规定,只有同时满足以下所有条件的请求,才能被称为“简单请求”。它就像个“乖孩子”,浏览器认为它足够安全,可以直接发送,无需“预检”。

“简单请求”的三个硬性标准

  1. 请求方法 (Method) 必须是以下三者之一
    • GET
    • HEAD
    • POST
  2. HTTP 请求头 (Headers) 不能超出以下范围: - Accept - Accept-Language - Content-Language - Content-Type (有额外限制,见下一条) - 以及一些浏览器会自动添加的、我们无法控制的头(如 Connection, User-Agent 等)。

    简单说,就是你不能手动添加像 Authorization, X-Token 这样的自定义请求头。

  3. Content-Type 的值必须是以下三者之一: - text/plain - multipart/form-data (文件上传的表单) - application/x-www-form-urlencoded (普通 form 表单提交)

    注意:我们前后端分离最常用的 Content-Type: application/json 不属于简单请求的范围!

只要你的请求不满足以上任何一条规则,它就会被浏览器认定为“非简单请求”。

二、什么是非简单请求 (Non-Simple Request) 与预检 (Preflight)?

所有不是“简单请求”的请求,都是“非简单请求”。

由于这些请求可能会对服务器数据产生修改(如 PUT, DELETE),或者携带了特殊信息(如 application/json 数据或自定义 Token),浏览器觉得“这事儿不简单”,必须先进行一次“预检”(Preflight)。

预检的流程(两步走):

第 1 步:预检请求 (Preflight Request)

  • 在发送真正的业务数据之前,浏览器会自动发送一个 OPTIONS 方法的请求到服务器。
  • 这个 OPTIONS 请求就像是在问路:“嗨,服务器,我待会儿想用 PUT 方法,带着 Content-Type: application/json 和一个 Authorization 的头来请求你,你那边允许吗?”
  • 注意:这个 OPTIONS 请求本身不包含任何业务数据(比如 POST 的 body)。

第 2 步:实际请求 (Actual Request)

  • 服务器收到 OPTIONS 请求后,如果我们的 Nginx 配置正确,会返回带有 Access-Control-Allow-Methods, Access-Control-Allow-Headers 等 CORS 头的响应。
  • 浏览器检查这些响应头,发现服务器允许 PUT 方法和这些请求头,于是“预检”通过。
  • 然后,浏览器才会发送真正的、带有业务数据的 PUT 请求。
  • 如果预检不通过,浏览器就会在控制台报一个 CORS 错误,真正的业务请求根本不会被发送出去。

三、这对我的 Nginx 配置意味着什么?

  • 对于简单请求:浏览器直接发出 GET 或 POST 请求。Nginx 只需要在处理这个请求时,在返回的响应里加上 Access-Control-Allow-Origin 就行了。
  • 对于非简单请求:浏览器会先发 OPTIONS 请求。因此,你的 Nginx 配置里需要有一段专门处理 OPTIONS 请求的逻辑,用来响应这个“预检”。

四、总结:一张图看懂区别

特性简单请求 (Simple Request)非简单请求 (Non-Simple Request)
通信次数1 次 (直接发送业务请求)2 次 (先发 OPTIONS 预检,再发业务请求)
触发条件必须同时满足方法、头、 Content-Type 三个严格标准只要有一个标准不满足即可触发
常见例子传统的 <a> 链接点击、普通 <form> 表单提交发送 JSON ( application/json) 的 POST 请求;所有 PUT, DELETE, PATCH 请求;带自定义 Authorization Token 的请求
Nginx 配置核心保证业务响应本身带有 Allow-Origin正确响应 OPTIONS 请求

现在,当你再遇到 CORS 错误时,可以先看看浏览器的 Network 面板,确认是简单请求还是非简单请求(有没有 OPTIONS 请求),这样就能更快地定位问题所在了。

Licensed under CC BY-NC-SA 4.0