最近推进项目POC时, 前端开发报过来一个很奇怪的跨域问题, 在调用接口时经常发生CORS报错, 但是后端已经为所有的域名/方法/header配置了CORS, 理论上不应该发生问题。
问题复现
该问题会发生在一个需要上传的文件的接口, 用户可以从预上传的图片列表中选择其一作为参数调用接口, 而当选择某些图片时便会发生CORS报错(会导致报错的图片会随时间变化并不固定)。
当用户disable cache后所有的图片都不会报错, 并且在此后的一段时间也不会报错, 经过较长时间后, 且没有disbale cache, 某些图片又会开始报错。
在报错时, 可以看到前端发起了一次网络请求尝试下载图片, 但该请求被cache并发生报错。问题在于后端一直使用CORS, 即便是cache也会将CORS header一起缓存, 没有道理缓存到没有CORS的图片。
后端配置
在后端已经通过fast api配置了CORS, 经过自己检查没有发现任何问题
1 | app.add_middleware( |
前端报错逻辑
前端的报错逻辑非常简单, 尝试下载图片时报错
1 | const imgResponse = await fetch(selectedImageUrl); |
调查思路
pass
问题原因
经过一系列debug, 终于找到问题的原因。
首先需要知道, 浏览器的img标签并不检查跨域, 因此img标签加载图片时, 请求header中只有refer而没有origin。
而根据规范, 只有在origin header存在时才会设置CORS header。
至此我们知道报错发生的过程如下:
- 浏览器通过img标签依次加载列表中的图片以便用户选择
- 某些图片的缓存过期, 浏览器得到没有CORS的response, 并缓存
- 用户选择图片, 前端代码尝试fetch图片, 命中缓存
- 缓存response没有CORS, 报错
解决方案
1. 为
标签添加 crossOrigin 属性
1 | <img src="your-image-url" crossOrigin="anonymous" /> |
2. 在 fetch 请求中禁用缓存
1 | const imgResponse = await fetch(selectedImageUrl, { |
因为只是POC, 最后选用方案2解决
no-cache vs reload
no-cache: 若服务器返回 304 Not Modified(资源未变化),则使用缓存。
reload: 浏览器完全跳过缓存检查,直接向服务器请求最新资源
模式 | 是否验证缓存 | 是否可能复用缓存 | 网络请求次数 |
---|---|---|---|
no-cache | ✓ | ✓ (304 响应时) | 至少 1 次 |
reload | ✗ | ✗ | 强制 1 次 |