最近请假两天回去了一趟,结果刚到家同事就反馈一个接口不对,心里顿时一惊,不应该出问题啊,那个接口可是测过多次的,甚至怀疑自己测试时是不是恍惚了……
1. 问题定位
自己明明使用的get
方法,怎么会变成options
呢?没有手动设置,那肯定是浏览器根据某种条件设置的呀,于是去了解为什么浏览器会自动使用options
方法,一看MDN上的说明就了解了。
规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。
上面说的产生副作用的请求,其实就是简单请求(Simple Request)
。
“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。”预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
当请求满足下述任一条件时,即应首先发送预检请求:
- 使用下列方法之外的方法
get
post
head
人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:
- Accept
- Accept-Language
- Content-Language
- Content-Type (but note the additional
- requirements below)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
Content-Type
不属于下列之一:- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- 请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器。
- 请求中使用了ReadableStream对象。
带预检请求的请求过程:
上面讲的是浏览器为什么发送options
请求,可是我发送的请求哪里导致这个问题的呢?因为我传送的参数是一个对象,而请求库使用的是axios,看了下源码,有这么一段:default.js
function setContentTypeIfUnset(headers, value) {
if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
headers['Content-Type'] = value;
}
}
...
transformRequest: [function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
return data;
}],
所以,当参数为一个对象时,默认的Content-Type
是application/json;charset=utf-8
,这样会导致发送options
类型的预检请求,然而后端并没有处理options
方法,导致前端请求虽然发送了,但是请求却没有真正成功。
2. 问题解决
可以通过两种方式解决:
- 前端将请求设置为简单请求,如设置Content-Type为text/plain
- 后端允许跨域访问options请求,可通过Nginx配置CORS允许跨域,参考:跨域资源共享 CORS及Nginx配置