最近请假两天回去了一趟,结果刚到家同事就反馈一个接口不对,心里顿时一惊,不应该出问题啊,那个接口可是测过多次的,甚至怀疑自己测试时是不是恍惚了……

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对象。

带预检请求的请求过程:
_1534736093603.png

上面讲的是浏览器为什么发送options请求,可是我发送的请求哪里导致这个问题的呢?因为我传送的参数是一个对象,而请求库使用的是axios,看了下源码,有这么一段:
default.js

  1. function setContentTypeIfUnset(headers, value) {
  2. if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
  3. headers['Content-Type'] = value;
  4. }
  5. }
  6. ...
  7. transformRequest: [function transformRequest(data, headers) {
  8. normalizeHeaderName(headers, 'Accept');
  9. normalizeHeaderName(headers, 'Content-Type');
  10. if (utils.isFormData(data) ||
  11. utils.isArrayBuffer(data) ||
  12. utils.isBuffer(data) ||
  13. utils.isStream(data) ||
  14. utils.isFile(data) ||
  15. utils.isBlob(data)
  16. ) {
  17. return data;
  18. }
  19. if (utils.isArrayBufferView(data)) {
  20. return data.buffer;
  21. }
  22. if (utils.isURLSearchParams(data)) {
  23. setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
  24. return data.toString();
  25. }
  26. if (utils.isObject(data)) {
  27. setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
  28. return JSON.stringify(data);
  29. }
  30. return data;
  31. }],

所以,当参数为一个对象时,默认的Content-Typeapplication/json;charset=utf-8,这样会导致发送options类型的预检请求,然而后端并没有处理options方法,导致前端请求虽然发送了,但是请求却没有真正成功。

2. 问题解决

可以通过两种方式解决:

  • 前端将请求设置为简单请求,如设置Content-Type为text/plain
  • 后端允许跨域访问options请求,可通过Nginx配置CORS允许跨域,参考:跨域资源共享 CORS及Nginx配置