前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >科普一下 CORS 以及如何节省一次 OPTIONS 请求

科普一下 CORS 以及如何节省一次 OPTIONS 请求

作者头像
腾讯NEXT学位
发布2019-07-02 12:10:52
1.9K0
发布2019-07-02 12:10:52
举报
文章被收录于专栏:腾讯NEXT学位腾讯NEXT学位

相信做前端开发的同学对同源策略都比较熟悉,而如何解决跨域问题基本上也是前端面试必考题之一。

CORS标准协议

为了解决跨域资源共享问题,浏览器厂商和标准组织在 HTTP 协议的基础上,提出了 CORS 标准协议。CORS 协议由一组 HTTP Header 构成,用于标识某个资源是否可以被跨域访问。

这里只是简单介绍一下 CORS 标准,更详细的内容可以直接看规范文档:Fetch Standard

当前端使用 XHR 或者 fetch 等其他方法请求一个跨域资源时,如果是非简单请求(后面会解释),浏览器会自动帮你先发出一个叫做预检(cors-preflight-request)的请求, 对应的 HTTP Request Method 为 OPTIONS。这个请求对服务器是安全的,也就是说不会对服务器的资源做任何改变,仅仅用于确认 header 响应。

该请求 header 中会包含以下两个字段:

· Access-Control-Request-Method:该字段的值对应当前请求类型,例如 GET、POST、PUT等等。浏览器会自动处理。

· Access-Control-Request-Headers:该字段的值对应当前请求可能会携带的额外的自定义 header 字段名,多个字段用逗号分割。浏览器会自动处理,将请求中非简单的 header 字段全部列出来,例如标识请求流水的 x-request-id,用于 Auth 鉴权的 Authorization 字段。

对于 OPTIONS 请求,按照规范实现的服务端会响应一组HTTP header,但不会返回任何实体内容。如果服务端支持该跨域请求,建议返回 204 状态码(返回 200 也可以)。如果不支持,建议返回 403 状态码(返回 404 或其他错误状态码也可以)。

响应的 header 可以包含以下字段:

· Access-Control-Allow-Origin:允许哪些域被允许跨域,例如 http://qq.com 或 https://qq.com,或者设置为 * ,即允许所有域访问(通常见于 CDN )

· Access-Control-Allow-Credentials:是否携带票据访问(对应 fetch 方法中 credentials),当该值为 true 时,Access-Control-Allow-Origin 不允许设置为 *

· Access-Control-Allow-Methods:标识该资源支持哪些方法,例如:POST, GET, PUT, DELETE

· Access-Control-Allow-Headers:标识允许哪些额外的自定义 header 字段和非简单值的字段(这个后面会解释)

· Access-Control-Max-Age:表示可以缓存 Access-Control-Allow-Methods 和 Access-Control-Allow-Headers 提供的信息多长时间,单位秒,一般为10分钟。

· Access-Control-Expose-Headers:通过该字段指出哪些额外的 header 可以被支持。

对于 CORS 的服务端实现,前端同学可以随便看一下 koa/cors 的源码,一目了然,地址在这里:https://github.com/koajs/cors/blob/master/index.js

跨域的请求流程

OK,原理部分就这么多,梳理一下跨域的请求流程:

1. 当我们发起跨域请求时,如果是非简单请求,浏览器会帮我们自动触发预检请求,也就是 OPTIONS 请求,用于确认目标资源是否支持跨域。如果是简单请求,则不会触发预检,直接发出正常请求。

2. 浏览器会根据服务端响应的 header 自动处理剩余的请求,如果响应支持跨域,则继续发出正常请求,如果不支持,则在控制台显示错误。

由此可见,当触发预检时,一次 AJAX 请求会消耗掉两个 TTL,严重影响性能。

那么如何节省掉 OPTIONS 请求来提升性能呢?从上文可以看出,有两个方案:

1. 发出简单请求。

2. 服务器端设置 Access-Control-Max-Age 字段,那么当第一次请求该URL时会发出 OPTIONS 请求,浏览器会根据返回的 Access-Control-Max-Age 字段缓存该请求的OPTIONS预检请求的响应结果(具体缓存时间还取决于浏览器的支持的默认最大值,取两者最小值,一般为 10分钟)。在缓存有效期内,该资源的请求(URL和header字段都相同的情况下)不会再触发预检。(chrome 打开控制台可以看到,当服务器响应 Access-Control-Max-Age 时只有第一次请求会有预检,后面不会了。注意要开启缓存,去掉 disable cache 勾选。)

但是要注意的是,该缓存只针对这一个请求 URL 和相同的 header,无法针对整个域或者模糊匹配 URL 做缓存。

可以看到方案2 虽然可以设置缓存,但很局限,只限于缓存一个 URL 地址,并不适用于频繁跨域调用后台的各个接口(当然也可以考虑封装一下,固定一个接口地址,传不同的body内容)。

那方案一中,什么是简单请求呢?规范规定了,当请求同时满足以下所有情况时,才会被浏览器认为是一个简单请求:

· 请求方法必须是以下之一:GET、HEAD、POST,也就是说 PUT、PATCH 等方法必然会触发预检。

· 只有以下 header 字段允许被修改或被设置,否则必然触发预检。

· Accept、Accept-Language、Content-language、Content-Type(但有限定值)、DPR、Downlink、Save-Data、Viewport-Width、Width

· Content-Type 的值只被允许设置为以下三个之一:application_x-www-form-urlencoded、multipart_form-data、text/plain。也就是说,如果请求的 Content-Type 被设置为 application/json;charset=utf-8 时也必然会触发预检。

· 添加任何额外的自定义的 header 都会触发预检,例如 x-request-id,但服务端可以设置缓存这一个请求的OPTIONS 响应。

· XMLHttpRequestUpload 在请求中使用的任何对象上都没有注册事件侦听器。这个比较少见。详细可以参考:XMLHttpRequest.upload - Web APIs | MDN

· ReadableStream 请求中未使用任何对象。这个比较少见,应该是指 Fetch API 中的 Request 中的 Body,本人没有去验证。

当满足以上条件时,就不会触发预检了。例如使用script标签加载跨域的 CDN 的资源就是很常见的普通 GET 请求,不会触发预检,有兴趣的同学可以打开 chrome 控制台,看一下 CDN 资源返回的 header。

顺便说一句题外话,当 CDN 设置了 Access-Control-Allow-Origin响应头允许跨域时,我们可以给script标签添加crossOrigin属性,从而可以使用 window.onerror 捕获 CDN 上的 js 运行时导致的详细错误信息,包括堆栈等。

如果不设置crossOrigin属性,则可能只会捕获到script error,无法获取额外的堆栈信息。

crossOrigin属性的值默认为anonymous,即不携带 cookie,如果设置为use-credentials,则会携带 cookie 和客户端证书等票据。

示例:

代码语言:javascript
复制
<script src="https://qq.com/a.js" crossOrigin="anonymous"></script>

结束语

全文到这里就结束了,如果你的生产环境存在这个性能问题,那么首先把本地的AJAX请求中的自定义 header 去掉,同时可以理直气壮的拿这篇文章发给后端同学,让其在支持CORS的同时,针对性优化,避免触发 OPTIONS 请求,提升性能。

原文作者:腾讯前端架构师 吴颖

原文链接:

https://zhuanlan.zhihu.com/p/70032617

  -前端好课-  

【Web前端从小白到大师】全新升级

更新比例高达50%,你值得拥有

若需了解更多,请扫码添加小助手咨询~

也可直接查找微信号:TencentNext

▲ NEXT学院 官方课程助教 ▲

点击阅读原文,0元学习

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯NEXT学院 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
内容分发网络 CDN
内容分发网络(Content Delivery Network,CDN)通过将站点内容发布至遍布全球的海量加速节点,使其用户可就近获取所需内容,避免因网络拥堵、跨运营商、跨地域、跨境等因素带来的网络不稳定、访问延迟高等问题,有效提升下载速度、降低响应时间,提供流畅的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档