推荐阅读:

​HTTP工作原理、请求方法、请求/响应步骤、Request/Response示例​


什么是OPTIONS请求

用于获取目的资源所支持的通信选项。客户端可以对特定的 URL 使用 OPTIONS 方法,也可以对整站(通过将 URL 设置为“*”)使用该方法;

简单来说,就是可以用 OPTIONS 请求去嗅探某个请求在对应的服务器中都支持哪种请求方法;


OPTIONS请求特点

HTTP 的 OPTIONS 预检请求简介、特点、触发和优化_OPTIONS预检请求

OPTIONS请求是用于请求服务器对于某些接口等资源的支持情况的,包括各种请求方法、头部的支持情况,仅作查询使用。

一个示例

->>> curl -X OPTIONS https://xxxx.com/getShareRecord -i

HTTP/1.1 200 OK
Server: nginx/1.13.3
Date: Mon, 30 Jul 2018 12:50:08 GMT
Content-Length: 0
Connection: keep-alive
Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: 0
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: X-Requested-With

通过curl来发送一个http请求,在响应头中可以发现服务器上这个接口对请求方法以及一些header的使用允许情况,也就是上面说的获取服务器对于某些资源的选项、支持情况。

扩展阅读:​​Linux curl命令详解​


OPTIONS触发

浏览器自主发起的行为称之为:“浏览器行为”。

在某些情况下,普通的 GET 或者 POST 请求会自动触发一次 OPTIONS 请求,当 OPTIONS 请求成功返回后,真正的 AJAX 请求才会再次发起。

前端一般不会主动发起这个请求,但是通过F12 debug页面,一般可以看到同一次请求中会有两个请求方法,其中一次的 Request Method 是 OPTIONS;

HTTP 的 OPTIONS 预检请求简介、特点、触发和优化_OPTIONS预检请求_02

提示:上图是做AJAX的POST请求时,使用自定义请求头时触发 OPTIONS 预检请求方法;

某些情况都是什么情况

1)跨域请求,非跨域请求不会出现options请求;

扩展阅读:​​HTTP访问控制(CORS)、同源策略、跨域和预检请求等问题​

2)自定义请求头,如:

xhr.setRequestHeader('name', 'Jacky')

3)请求头中的 Content-Type 出现了以下三种之外的格式;

Content-Type: application/x-www-form-urlencoded   表单提交

Content-Type: multipart/form-data   文件上传

Content-Type: text/plain  文本

如:

Content-Type:application/json;charset=utf-8

扩展阅读:​​HTTP Content-Type​​ 

当满足条件12或者13的时候,AJAX 请求就会出现 OPTIONS 请求,OPTIONS请求的意义在于先得到服务器端的确认,才能继续下一步的操作,这也是为什么 OPTIONS 请求也被叫做“预检”请求的原因。

即:当浏览器发起"复杂请求"时会主动发起 OPTIONS预检请求。

跨域共享标准规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法,浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。可以理解为:服务器确认允许之后,才发起实际的 HTTP 请求。

注意:很多人以为同源策略是浏览器不让请求发出去、或者后端拒绝返回数据。实际情况是,请求能正常发出,后端接口正常响应,只是数据到了浏览器后被丢弃了(或被拦截了)

预检请求可能会失败报错

HTTP 的 OPTIONS 预检请求简介、特点、触发和优化_OPTIONS预检请求_03

意思是说:从A到B的AJAX请求已被 CORS策略阻止:出现了不允许(预定义之外)的请求头字段;

CORS 机制,使用额外的 HTTP 头来告诉浏览器让运行在一个 Origin (domain) 上的 Web 应用被准许访问来自不同源服务器上的指定资源。这意味着正确设置 CORS 头信息,使用这些API的Web应用就可以跨域了。


简单请求/复杂请求

某些请求不会触发 CORS 预检请求,这样的请求一般称为"简单请求",而会触发预检的请求则称为"复杂请求"。


简单请求

1)请求方法为GET、HEAD、POST时发的请求;

2)人为设置了规范集合之内的头部字段(预定义),如:

Accept

Accept-Language

Content-Language

Content-Type(三个限制值)

DPR

Downlink

Save-Data

Viewport-Width

Width

3)Content-Type 的值仅限于下列三者之一;

Content-Type: application/x-www-form-urlencoded

Content-Type: multipart/form-data

Content-Type: text/plain

4)请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;

5)请求中没有使用 ReadableStream 对象。


复杂请求

1)使用了下面任一 HTTP 方法;

PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH

2)人为设置了规范集合(预定义)之外的头部字段,即简单请求外的字段;

任何自定义头部字段都会是复杂请求

3)Content-Type 的值设置了以下三种之外的时;

Content-Type: application/x-www-form-urlencoded

Content-Type: multipart/form-data

Content-Type: text/plain

如果设置 ​​Content-Type:application/json;charset=utf-8​​ 时,也是复杂请求,必然触发预检。


OPTIONS关键的头信息

Request header 的关键字段

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

Access-Control-Request-Method

告知服务器,实际请求将使用 POST 方法

Access-Control-Request-Headers

告知服务器,实际请求将携带的自定义请求首部字段

Response header 的关键字段

Access-Control-Allow-Methods

表明服务器允许客户端使用什么方法发起请求

Access-Control-Allow-Origin

允许跨域请求的域名,如果要允许所有域名则设置为 *

Access-Control-Request-Headers

将实际请求所携带的首部字段告诉服务器

Access-Control-Max-Age

指定了预检请求的结果能够被缓存多久,单位秒(如,10分钟是600)


OPTIONS请求优化

当我们发起跨域请求时,如果是简单请求,那么只会发出一次请求,如果是复杂请求则会先发出 OPTIONS 请求,用于确认目标资源是否支持跨域,然后浏览器会根据服务端响应的 header 自动处理剩余的请求,如果响应支持跨域,则继续发出正常请求,如果不支持,则在控制台显示错误。

由此可见,当触发预检时,跨域请求便会发送 2 次请求,既增加了请求数,也延迟了请求真正发起的时间,影响性能,可以优化 OPTIONS 请求。

1、转为简单请求,如用 JSONP 做跨域请求;

2、对 OPTIONS 请求进行缓存;

服务器端设置 ​Access-Control-Max-Age​​ 字段,当第一次请求该 URL 时会发出 OPTIONS 请求,浏览器会根据返回的 ​Access-Control-Max-Age​ 字段缓存该请求的 OPTIONS 预检请求的响应结果(具体缓存时间还取决于浏览器的支持的默认最大值,一般为 10 分钟)。

在缓存有效期内,该资源的请求(URL 和 header 字段都相同的情况下)不会再触发预检。(chrome 打开控制台可以看到,当服务器响应 Access-Control-Max-Age 时只有第一次请求会有预检,后面不会了。注意要开启缓存,去掉 disable cache 勾选。)


OPTIONS请求总结

OPTIONS 请求就是预检请求,可用于检测服务器允许的 HTTP 方法。当发起跨域请求时,由于安全原因,触发一定条件时浏览器会在正式请求之前自动先发起 OPTIONS 请求,即 CORS 预检请求,服务器若接受该跨域请求,浏览器才继续发起正式请求。