记一次GET请求之前出现OPTIONS请求

6,011 阅读4分钟

本次遇到 get 请求之前出现 options 请求,导致请求失败

代码示例

var ajax = new XMLHttpRequest()

ajax.open('get', '//api.abc.cn/card/getCode')
ajax.setRequestHeader('Content-Type', 'application/json;')
ajax.send()
ajax.onreadystatechange = function() {
  if (ajax.readyState == 4 && ajax.status == 200) {
    console.log(ajax.responseText)
  }
}

chrome 会出现 options 和 get 两次请求

概念阐述

跨域资源共享(Cross-Origin Resource Sharing, CORS)是为解决 Ajax 技术难实现跨域问题而提出的一个规范,这个规范试着从根本上解决安全的跨域资源共享问题。在此之前,解决此类问题的途径往往是服务器代理、JSONP 等,治标不治本。目前基本所有浏览器都已经支持该规范。

  • 什么是 options 请求?

它是一种探测性的请求,通过这个方法,客户端可以在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务器的性能。

在 ajax 中出现 options 请求,也是一种提前探测的情况,ajax 跨域请求时,如果请求的是 json,就属于复杂请求,因此需要提前发出一次 options 请求,用以检查请求是否是可靠安全的,如果 options 获得的回应是拒绝性质的,比如 404\403\500 等 http 状态,就会停止 post、put 等请求的发出。

  • 导致出现 options 请求原因

    是浏览器对简单跨域请求和复杂跨域请求的处理区别

XMLHttpRequest 会遵守同源策略(same-origin policy). 也即脚本只能访问相同协议/相同主机名/相同端口的资源, 如果要突破这个限制, 那就是所谓的跨域, 此时需要遵守 CORS(Cross-Origin Resource Sharing)机制。

那么, 允许跨域, 不就是服务端设置 Access-Control-Allow-Origin: *就可以了吗? 普通的请求才是这样子的, 除此之外, 还一种叫请求叫 preflighted request。

注意,为了安全,标准里不允许 Access-Control-Allow-Origin: *,必须指定明确的、与请求网页一致的域名

preflighted request 在发送真正的请求前, 会先发送一个方法为 OPTIONS 的预请求(preflight request), 用于试探服务端是否能接受真正的请求,如果 options 获得的回应是拒绝性质的,比如 404\403\500 等 http 状态,就会停止 post、put 等请求的发出。

  • 那么, 什么情况下请求会变成 preflighted request 呢?

    1. 请求方法不是 GET/HEAD/POST
    2. POST 请求的 Content-Type 并非 application/x-www-form-urlencoded, multipart/form-data, 或 text/plain
    3. 请求设置了自定义的 header 字段

解决方案

前端绕不过去

  1. 要么后端代码 cors 处理,
  2. 要么 nginx 配置

nginx 跨域配置

server {
  listen       80;
  server_name  api.abc.com;

  # 是否允许请求带有验证信息
  add_header Access-Control-Allow-Credentials true;
  # 允许跨域访问的域名,可以是一个域的列表,空格隔开,也可以是通配符*(不建议)
  add_header Access-Control-Allow-Origin  http://card.abc.com;
  # 允许使用的请求方法,以逗号隔开,可以用 *
  add_header Access-Control-Allow-Methods 'POST,GET,OPTIONS,PUT,DELETE';
  # 预检命令的缓存,如果不缓存每次会发送两次请求,单位为秒。
  # 第一次是浏览器使用OPTIONS方法发起一个预检请求,第二次才是真正的异步请求
  add_header Access-Control-Max-Age 3600;

  # 允许脚本访问的返回头
  add_header Access-Control-Allow-Headers 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,X-Requested-With';
  # 允许自定义的头部,以逗号隔开,大小写不敏感
  add_header Access-Control-Expose-Headers 'WWW-Authenticate,Server-Authorization';

  # OPTIONS类的请求,是跨域先验请求
  if ($request_method = 'OPTIONS') {
     return 204; # http状态码 204 (无内容) 服务器成功处理了请求,但没有返回任何内容。可以返回 200
  }
  location / {
    # proxy_pass http://127.0.0.1:3000;
  }
  location /card {
    proxy_pass http://127.0.0.1:3001;
  }
  location /music {
    proxy_pass http://127.0.0.1:3002;
  }
}

CORS 对比反向代理

二者适合的业务场景不同

反向代理

  • 将一台服务器作为网关和代理服务器,负责将请求转发到子系统(其它服务器)
  • 从用户的角度看,我只访问了一个域名,但其实可能你访问了很多服务器
  • 是在访问其它服务器的服务器上配置(要转发到的服务器)

跨域资源共享

  • 从 ajax 里拿其它服务器的资源。
  • 可以看到,这个主要是为了解决前端人员的问题。但 ajax 里其实就有一个 url 参数,所以反向代理也能解决这个问题。
  • 是在被访问的服务器上配置(允许访问其他的服务器)。

参考资料