本次遇到 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 呢?
- 请求方法不是 GET/HEAD/POST
- POST 请求的 Content-Type 并非 application/x-www-form-urlencoded, multipart/form-data, 或 text/plain
- 请求设置了自定义的 header 字段
解决方案
前端绕不过去
- 要么后端代码 cors 处理,
- 要么 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 参数,所以反向代理也能解决这个问题。
- 是在被访问的服务器上配置(允许访问其他的服务器)。