写在前面的话:网上一些文章标题为 "前端解决跨域的N种方案",这类文章都是胡扯!!!
他们那样写就是在欺负前端不懂后端,胡说八道,因为解决跨域100%是服务端的事情,跟前端没一点关系,所谓的N种方案都是需要你去修改或创建一个服务端。
注:上面提到的 后端 具体来说是指 服务端,即 http 服务。
关于什么是跨域,以及为什么浏览器要有跨域安全机制,这个不再啰嗦。
我只是讲一下最近我遇到的一个关于跨域,之前忽略的知识点。
最近遇到的一个跨域场景:
图片资源存放在 腾讯云 上,且管理员 “宣称” 已经添加了 CORS 规则,该规则设定如下:
- 来源 Origin:
*
- 被允许的请求方法:PUT、GET、POST、DELETE、HEAD
- Allow-Headers:
*
- Expose-Headers:ETag、Content-Length、x-cos-request-id
- 超时 Max-Age:0
- 返回 Vary:Origin:已勾选
从上面的配置项来看,这已经是最大自由度的 CORS 规则了,Origin 都已经设置为 *
了,那真的是任何限制都没有,不应该出现跨域问题了。
但是...
目前在做的项目中,使用了 Three.js 的 纹理加载器(TextureLoader),它的内部会请求加载某些图片资源。
原本不应该出现图片跨域问题不光出现了,并且还很诡异:
- 我自己的电脑(谷歌浏览器) 从来没有 出现过跨域问题
- 有的同事电脑(谷歌浏览器) 每一次 都出现跨域
- 有的同事电脑(谷歌浏览器) 偶尔 会出现跨域
为什么???
特别强调一下,本文以下内容提到的跨域都是指通过 Three.js 纹理加载器加载图片资源遇到的跨域问题,并不是通过
<img>
标签的 src 属性出现的跨域问题。
从Three.js中找原因
前面也提到了,当使用 Three.js 的纹理加载器(TextureLoader) 出现了跨域,那我第一时间就去是看文档。
TextureLoader 实际上继承于 Loader,在 Loader 代码中 .crossOrigin 属性默认值为 anonymous。
与此同时,网上搜到了一些关于纹理加载器跨域解决相关文章,几乎都是在说需要把 .crossOrigin 的值设置为 空字符串。
const loader = new THREE.TextureLoader();
loader.setCrossOrigin('')
或
THREE.ImageUtils.crossOrigin = ''
经过试验发现完全没有用。
这些文章都比较旧,他们或许是为了解决很早之前的 Three.js 版本。
通过查阅 CORS 相关文章:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Attributes/crossorigin
可以知道 crossOrigin 一共可以设置为以下 3 个值:
-
"anonymous":对此元素的 CORS 请求将不设置凭据标志
换句话说限制最小、自由度最大。
Three.js 中 Loader .crossOrigin 的默认值就是 "anonymous"
-
"use-credentials":对此元素的 CORS 请求将设置凭证标志
这意味着请求将提供凭据
-
"":空字符串,其作用等同于 "anonymous"
那问题究竟出在哪里呢?
后来看了这篇文章,才突然意识到问题。
作者 雨打梨花
在知乎上写的这篇文章:"科普一下 CORS 以及如何节省一次 OPTIONS 请求"
https://zhuanlan.zhihu.com/p/70032617
这篇文章中提到了 2 个词:简单请求、非简单请求
经过查询 MDN 上关于CORS 的文章:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
可以知道,所谓的 “非简单请求” 更加官方的叫法应该是 “预检请求”。
终于要讲解本文的主题了,CORS 的 2 种请求:简单请求、预检请求
- 跨域的 简单请求:说直白点就是 一次正常、标准的资源请求
- 跨域的 预检请求:说直白点就是 浏览器发现资源缓存尚可用,于是使用 OPTIONS 方法向服务器发送一个资源预检的请求,以获知服务器是否允许该实际请求。
具体详细的介绍,可自己查看 MDN 的那篇文章。
我们直接提炼出以上 2 篇文章的几个关键点:
- 若缓存可用,例如 极短时间内连续访问同一个资源 可能触发 预检请求
- 预检请求规定两次对比的 URL 和 header 必须相同,无法针对整个域或者模糊匹配的 URL
原因找到了:之前腾讯云配置的 CORS 规则中 Origin 为 *
,这就导致:
- 对于 简单请求 而言,是符合这条规则的,因此第一次加载某图片是没有问题的。
- 但对于 预检请求 而言,由于预检请求不支持 模糊匹配,所以 预检请求 无法通过该规则,所以就会出现跨域情况了。
于是乎,只需要在腾讯云 CORS 规则中新增一条
- Origin 不再设为为
*
,而是具体的 协议 + IP + 端口 - 超时 Max-Age 也不设置 0,而是给他一些超时时间,例如 60 秒
经过这样设置之后,纹理加载器加载图片就再也没有跨域报错了。
等一等,虽然上面讲述了问题原因和解决办法,但是还么有解释之前跨域的诡异表现:
- 我电脑浏览器上 一直 没有出现跨域问题
- 有的同事电脑浏览器上 每一次 都出现跨域问题
- 有的同事电脑浏览器上 偶尔 出现跨域问题
当我们知道存在 图片缓存 会触发 预检请求,那么上面那些所谓的 诡异 都可以解释了。
为什么我的电脑浏览器上一直没有出现跨域问题?
答:因为我在开发过程中,习惯性将浏览器调试工具打开,同时将 网络 > 停用缓存 给勾选上了。
这就导致我的浏览器根本就没有 缓存 一说,也就是说每一次都走的是 简单请求,所以我这里始终都没出现跨域问题。
为什么我同事电脑上会有时出现,有时又不出现呢?
答:尽管之前腾讯云的 CORS 中 Max-Age 设置为 0,这似乎也是配置 缓存立即失效,不存在缓存。
但在实际测试过程中,由于代码业务逻辑原因,是有可能在 极短 的时间连续请求 2 次图片资源的。
当然也有一种可能是因为 CDN 的缘故
简单来说,不要以为把 Max-Age 设置为 0 就一定不会出现缓存了。
总之,在某些情况下确实发生了 缓存,触发了 预检请求,进而出现跨域问题。