0%

浏览器Fetch图片触发CORS

最近推进项目POC时, 前端开发报过来一个很奇怪的跨域问题, 在调用接口时经常发生CORS报错, 但是后端已经为所有的域名/方法/header配置了CORS, 理论上不应该发生问题。

问题复现

该问题会发生在一个需要上传的文件的接口, 用户可以从预上传的图片列表中选择其一作为参数调用接口, 而当选择某些图片时便会发生CORS报错(会导致报错的图片会随时间变化并不固定)。

当用户disable cache后所有的图片都不会报错, 并且在此后的一段时间也不会报错, 经过较长时间后, 且没有disbale cache, 某些图片又会开始报错。

在报错时, 可以看到前端发起了一次网络请求尝试下载图片, 但该请求被cache并发生报错。问题在于后端一直使用CORS, 即便是cache也会将CORS header一起缓存, 没有道理缓存到没有CORS的图片。

后端配置

在后端已经通过fast api配置了CORS, 经过自己检查没有发现任何问题

1
2
3
4
5
6
7
8
9
10
11
12
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:3000",
"http://127.0.0.1:3000",
# ...
], # 允许所有来源
allow_credentials=True, # 允许携带凭证
allow_methods=["*"], # 允许所有HTTP方法
allow_headers=["*"], # 允许所有HTTP头
)

前端报错逻辑

前端的报错逻辑非常简单, 尝试下载图片时报错

1
const imgResponse = await fetch(selectedImageUrl);

调查思路

pass

问题原因

经过一系列debug, 终于找到问题的原因。

首先需要知道, 浏览器的img标签并不检查跨域, 因此img标签加载图片时, 请求header中只有refer而没有origin。

而根据规范, 只有在origin header存在时才会设置CORS header。

至此我们知道报错发生的过程如下:

  1. 浏览器通过img标签依次加载列表中的图片以便用户选择
  2. 某些图片的缓存过期, 浏览器得到没有CORS的response, 并缓存
  3. 用户选择图片, 前端代码尝试fetch图片, 命中缓存
  4. 缓存response没有CORS, 报错

解决方案

1. 为 标签添加 crossOrigin 属性

1
<img src="your-image-url" crossOrigin="anonymous" />

2. 在 fetch 请求中禁用缓存

1
2
3
const imgResponse = await fetch(selectedImageUrl, {
cache: 'no-cache' // 或 'reload'
});

因为只是POC, 最后选用方案2解决

no-cache vs reload

no-cache: 若服务器返回 304 Not Modified(资源未变化),则使用缓存。

reload: 浏览器完全跳过缓存检查,直接向服务器请求最新资源

模式 是否验证缓存 是否可能复用缓存 网络请求次数
no-cache ✓ (304 响应时) 至少 1 次
reload 强制 1 次