在浏览器的 HTTP 请求中,当我们使用 fetch
API 或者 XMLHttpRequest
来进行跨域请求时,浏览器有时会发送一种称为 Preflight
的请求。这种请求是浏览器在实际发送跨域请求前,先与目标服务器进行的一次 “探测” 请求,以确认服务器是否允许这样的请求方式。Preflight
请求的存在是为了保障浏览器的安全性,确保跨域请求不会在没有服务器允许的情况下进行。
跨域资源共享(CORS)机制规定了当浏览器发起跨域请求时,某些条件下需要发送 Preflight
请求。这种探测性的请求使用 OPTIONS
方法发出,目标是向服务器询问,客户端接下来想要发送的实际请求是否被允许。
下面是一个例子:
Preflight
请求的触发条件
不是所有的跨域请求都会触发 Preflight
请求,浏览器会根据请求的类型和头部信息来决定是否需要预检。Preflight
请求通常在以下几种情况下触发:
- 当请求方法不是
GET
、POST
或HEAD
,例如PUT
、DELETE
。 - 当请求包含非标准的 HTTP 头部字段,比如自定义的
Authorization
头部,或者Content-Type
不是application/x-www-form-urlencoded
、multipart/form-data
、text/plain
。
下面是自定义头部字段触发 preflight 请求的一个例子:
- 请求中涉及跨域资源时,尤其是涉及到敏感的操作时,浏览器会通过
Preflight
请求来确保服务器允许这些操作。
这种设计的初衷是为了防止跨域请求滥用,尤其是在涉及敏感数据的场景下,确保浏览器与服务器之间的交互安全。
Preflight
请求的流程
当浏览器决定某个跨域请求需要进行 Preflight
,它会先向目标服务器发出一个 OPTIONS
请求,携带一些必要的头部信息,如 Access-Control-Request-Method
和 Access-Control-Request-Headers
,用于告知服务器,客户端即将发送的请求的具体方法和头部信息。服务器通过响应来告知浏览器,是否允许这样的请求。
请求示例:
假设我们要向 https://api.example.com/data
发送一个跨域的 PUT
请求,并且需要携带自定义头部 X-Custom-Header
。在实际发出 PUT
请求之前,浏览器会自动生成如下的 Preflight
请求:
OPTIONS /data HTTP/1.1
Host: api.example.com
Origin: https://client.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
其中,Access-Control-Request-Method
头部用来告诉服务器接下来会使用 PUT
方法,而 Access-Control-Request-Headers
则表明请求中会携带 X-Custom-Header
这个自定义头部。
服务器响应:
服务器接收到 Preflight
请求后,必须返回一个响应来告诉浏览器是否允许此类请求:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://client.example.com
Access-Control-Allow-Methods: GET, PUT, POST, DELETE
Access-Control-Allow-Headers: X-Custom-Header
服务器通过 Access-Control-Allow-Origin
来指定哪些源是允许访问的(这里是 https://client.example.com
),通过 Access-Control-Allow-Methods
来列出允许的 HTTP 方法,并通过 Access-Control-Allow-Headers
来列出允许的头部字段。
下面是 Preflight 请求来自服务器端的响应的一个例子:
Preflight
请求的使用场合
Preflight
请求主要用于跨域场景下,特别是那些涉及到更复杂请求的场合,比如非 GET
或 POST
方法,或者请求中包含了额外的自定义头部。
常见的使用场景包括:
-
RESTful API 请求:当前端应用需要与其他域名下的 REST API 进行交互时,尤其是对资源进行
PUT
或DELETE
操作时,往往会触发Preflight
请求。这些操作可能会修改服务器上的数据,因此需要确保安全。例如,一个前端应用需要向远程服务器发送数据更新请求,使用
PUT
方法更新用户信息。在这种场景下,Preflight
请求就会确保目标服务器允许跨域的PUT
请求。 -
上传文件的操作:在表单上传文件时,如果使用
fetch
API 或XMLHttpRequest
并携带了非标准的头部,比如自定义的认证信息,通常会触发Preflight
请求。浏览器需要确保服务器允许上传操作以及这些自定义的头部字段。 -
自定义认证头部的请求:很多应用在发起跨域请求时,需要在头部中携带如
Authorization
或Token
的自定义认证信息。由于这些头部字段并非标准字段,浏览器会先发送Preflight
请求来探测服务器是否允许使用这些自定义头部。
Preflight
请求的实际案例
在实际开发中,有一个典型的例子是前端应用需要向第三方服务发送请求并带有认证信息。这种场景下的跨域请求常常会触发 Preflight
请求。
假设我们开发了一个电子商务应用,这个应用的后端服务托管在 https://api.shop.com
,而前端页面托管在 https://shop.com
。用户在购物时,前端需要向后端发送带有用户身份认证的请求,如以下场景:
- 用户在购物车页面点击结账,前端应用需要向后端 API 发送包含用户认证信息的请求,以确认用户是否已登录,且是否有购买权限。
- 请求方法为
POST
,同时头部中带有Authorization: Bearer token123
来验证用户身份。
此时,由于 Authorization
头部是非标准字段,且前端和后端的域名不同,这个请求会触发 Preflight
检查。
请求步骤:
-
Preflight 请求:
浏览器会自动先发送一个OPTIONS
请求,探测服务器是否允许发送带有Authorization
头部的跨域请求:OPTIONS /checkout HTTP/1.1 Host: api.shop.com Origin: https://shop.com Access-Control-Request-Method: POST Access-Control-Request-Headers: Authorization
-
服务器响应:
后端服务器接收到该请求后,会检查是否允许该跨域请求,并返回允许的结果:HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://shop.com Access-Control-Allow-Methods: POST Access-Control-Allow-Headers: Authorization
响应中,服务器明确告知浏览器允许该域名
https://shop.com
发起POST
请求,并且允许使用Authorization
头部。 -
实际请求:
在确认服务器允许跨域请求后,浏览器会继续发送实际的POST
请求,包括认证信息:POST /checkout HTTP/1.1 Host: api.shop.com Origin: https://shop.com Authorization: Bearer token123
使用 Preflight
请求的优化
尽管 Preflight
请求保障了安全性,但在一些频繁的跨域请求场景下,这会带来额外的网络开销。为此,可以采用一些优化策略:
-
服务器缓存
Preflight
响应:通过在响应中设置Access-Control-Max-Age
头部,服务器可以告知浏览器在一定时间内不需要重复进行Preflight
请求。这能显著减少不必要的OPTIONS
请求,提升应用的性能。例如,服务器可以返回这样的响应,告知浏览器在未来 10 分钟内不需要重新发起
Preflight
请求:HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://shop.com Access-Control-Allow-Methods: POST Access-Control-Allow-Headers: Authorization Access-Control-Max-Age: 600
-
减少复杂的请求:避免不必要的自定义头部字段,或者尽量使用简单的
GET
、POST
请求,能够有效减少Preflight
请求的触发。对于一些轻量级的操作,使用标准的请求方法和头部可以避免Preflight
请求,从而提升效率。
结语
Preflight
请求作为 CORS 机制的一部分,主要作用是确保跨域请求的安全性,尤其是在涉及非标准请求时。通过 Preflight
请求,浏览器与服务器能够就请求的合法性达成共识,保护用户的数据安全。在实际应用中,理解 Preflight
请求的工作原理,并在合适的场景下进行优化,能够大大提升 Web 应用的性能和用户体验。