前言:开发时设置手机代理抓接口请求时,打开H5页面,总是会看到OPTIONS请求,特此学习记录一下。
1. 作用
-
请求服务器返回所支持的所有HTTP请求方法;
示例:
$ curl -X OPTIONS http://example.org -i
HTTP/1.1 200 OK
Allow: OPTIONS, GET, HEAD, POST #
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Thu, 14 Jul 2022 06:15:30 GMT
Expires: Thu, 21 Jul 2022 06:15:30 GMT
Server: EOS (vny/0453)
Content-Length: 0
响应报文包含一个 Allow
首部字段,该字段的值表明了服务器支持的所有 HTTP 方法。
-
跨域请求的预检请求;
下面👇🏻会详细介绍同源策略、跨域请求、OPTIONS请求。
2. 跨域请求
2.1 同源策略
如果两个URL的协议、主机、端口都相同,则称这两个URL同源。以http://music.javaswing.cn/home/index.html为例:
作用:这种同源策略就是浏览器的一个安全机制,主要是限制html或者它加载的js在没有明确授权的情况下,不能读写其它源origin的资源,它能帮助阻隔恶意html,减少可能被攻击的媒介。但是实际开发时,会有需要跨域的业务,就有了CORS的出现。
2.2 CORS(Cross Origin Resource Sharing) 跨域资源共享
CORS 跨域资源共享是一个W3C标准,允许浏览器向跨源服务器发送XMLHttpRequest请求。它使用额外的HTTP头告诉浏览器,让运行在一个origin上的web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议、或端口请求一个资源时,资源会发起一个跨域HTTP请求。
简单来说,就是CORS允许在https://www.aa.com/a/a.html的网页中,去请求https://www.bb.com/bb的接口获取数据。
浏览器将CORS请求分成两类:简单请求和非简单请求。
简单请求:
- 请求方法为:HEAD/GET/POST
- HTTP的头信息不超过以下几个字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(只限于三个值application/x-www-form-urlencoded、mutipart/form-data、text/plain)
非简单请求:
凡是不同时满足上面两个条件的,就属于非简单请求。
浏览器对这两种请求的处理是不一样的:
简单请求:
- 在请求中需要附加一个额外的Origin头部,其中包含请求页面的源信息(协议、域名、端口号),便于服务器根据这个头部信息决定是否给予响应;
- 如果服务器认为这个请求可以接口,就在Access-Control-Allow-Origin头部中回发相同的源信息(如果是公共资源,可以设置 *);
- 没有这个头部或者源信息不匹配,浏览器就会驳回请求,正常情况下,浏览器会处理请求。注意:请求和响应都不包含cookie信息;
- 如果需要包含cookie信息,ajax请求需要设置xhr的属性withCredentials为true,服务器需要设置响应头部 Access-Control-Allow-Credentials: true;
非简单请求:
浏览器在发送真正的请求之前,会先发送一个Preflight请求给服务器,这种请求使用OPTIONS方法,下面会展开进行介绍。
3. OPTIONS预检请求
3.1 OPTIONS预检请求的触发条件
上面了解了同源策略和CORS,终于到了OPTIONS请求。在CORS机制下一个域名A要访问域名B的服务,在使用非简单请求之前,会先进行一个预检请求(浏览器自动发起),检查B服务是否允许跨域请求,服务确认之后,才会发起真正的HTTP请求。
同时,在预检请求的返回中,服务端也可以通知客户端,是否需要携带身份凭证(包括Cookies和HTTP认证相关数据)。
3.2 OPTIONS预检请求结构
3.2.1 OPTIONS预检请求头
OPTIONS预检请求会携带几个关键的Request Header:
Request Header | 作用 |
---|---|
Access-Control-Request-Method | 告诉服务器实际请求所使用的 HTTP 方法 |
Access-Control-Request-Headers | (可选)告诉服务器实际请求所携带的自定义首部字段 |
Origin | 发起请求的域名 (协议、域名、端口号),便于服务器根据这个头部信息决定是否给予响应 |
{
"Host": "app.aaa.com",
"Origin": "https://m.bbb.com",
"Access-Control-Request-Method": "GET",
"Access-Control-Request-Headers": "csrf-token",
"Connection": "keep-alive",
"Accept": "*/*",
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 zzhunter",
"Referer": "https://m.caihuoxia.com/",
"Accept-Language": "zh-CN,zh-Hans;q=0.9",
"Accept-Encoding": "gzip"
}
3.2.2 OPTIONS预检响应头
当收到一个预检请求之后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送如下头部信息与浏览器进行沟通。预检响应头的关键字段:
response header | 作用 |
---|---|
Access-Control-Allow-Methods | 返回了服务端允许的请求,包含 GET/HEAD/PUT/PATCH/POST/DELETE |
Access-Control-Allow-Credentials | 允许跨域携带 cookie(跨域请求要携带 cookie 必须设置为 true) |
Access-Control-Allow-Origin | 允许跨域请求的域名,这个可以在服务端配置一些信任的域名白名单 |
Access-Control-Allow-Headers | 客户端请求所携带的自定义首部字段 |
Access-Control-Max-Age | 应该将这个 Preflight 请求缓存多长时间(以秒表示) |
{
"Server": "Tengine",
"Date": "Thu, 14 Jul 2022 06:25:07 GMT",
"Content-Type": "text/plain; charset=utf-8",
"Content-Length": "0",
"Connection": "keep-alive",
"Access-Control-Allow-Origin": "https://m.bbb.com",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Headers": "PPU,t,tk,v,uid,Content-Type,Csrf-Token",
"Access-Control-Allow-Methods": "GET,POST,OPTIONS",
"Access-Control-Max-Age": "1728000",
"Set-Cookie": [
"id58=c5/nR2LPtsOJbsB8a+YHAg==; expires=Sat, 13-Jul-24 06:25:07 GMT; domain=caihuoxia.com; path=/"
],
"P3P": "policyref=\"/w3c/p3p.xml\", CP=\"CUR ADM OUR NOR STA NID\""
}
一旦服务器通过Preflight 请求允许该请求之后,以后每次浏览器正常的CORS请求,都跟简单请求一样了。
4. OPTIONS预检请求优化
上面了解到,跨域请求会触发两次请求:OPTIONS预检请求、真正的请求。可以通过缓存OPTIONS预检请求的结果,来减少请求的数量。
Access-Control-Max-Age
这个响应首部表示预检请求的返回结果(即 Access-Control-Allow-Methods
和 Access-Control-Allow-Headers
提供的信息) 可以被缓存的最长时间,单位是秒。
如果值为 -1,则表示禁用缓存,每一次请求都需要提供预检请求,即用 OPTIONS 请求进行检测。
5. 总结
OPTIONS预检请求,可用于检测服务器允许的http方法。另外,当发起跨域请求时,非简单请求会导致浏览器自动发起一次OPTIONS预检请求,服务器接受该跨域请求之后,浏览器才继续发起正式请求。
CORS的优点:
- CORS通信与同源的AJAX通信没有差别,代码完全一样,容易维护;
- 支持所有类型的HTTP请求;
CORS的缺点:
- 存在兼容性问题,特别是IE10以下的浏览器;
- 第一次发送非简单请求时会多一次请求;