浏览器 跨域 问题在互联网中似乎是非常普遍的问题,但是我是最近才遇到需要自己亲自解决这种场景。先说结论:可以在 Nginx 层或者后端服务中间件层针对 Options 的 Http Method 进行设置,可以解决掉这个问题。我这边是通过 Nginx 层和 Golang echo 框架两个地方合力解决的,同时和云服务器的负载均衡也有一个很关键的关联。从实际出发,将遇到的问题做一个小结,归纳
细说从头,最近开发一个文件上传的服务,大批量的商品文件信息需要从客户端上传到我们后端系统中。但是我们业务系统带宽和磁盘空间不太够,所以购买了阿里云 OSS 服务,要将文件上传到 OSS 中。仅将部分文件信息如文件名,访问地址链接,文件类型,上传记录等信息保存在我们业务系统中。
去听了需求设计方案,说是要用直接上传的方案。我研究了一下有两套常用的 Web 上传 OSS 方案。不过最后采用的是短平快的 A 方案,文章结尾还有一个最优的 B 方案。
一顿操作猛如虎,CURD 完了之后,单元测试一遍,集成测试也走一遍,在 Postman 上模拟了所有相关的接口请求,如预期所愿。Perfect! 程序员最大都欣慰就是:我的程序测试都过了,跑起来没问题。
接着就开始部署到测试环境联调。我还在高兴地转手去写几行 Rust 代码,然后联调到业务方同事就发来高能预警,说不能访问,请求文件上传的接口没有返回。要来 URL 地址和请求参数,我自己在本地重现。F12 查看网络请求,发起POST 请求之前,浏览器会多发一个Options 的请求。【 跨域 】 的幺蛾子问题就这样冒出来了。
Referrer Policy: strict-origin-when-cross-originAccess-Control-Allow-Origin…
我是 a.test.com 的域名,而发起文件上传的服务域名是 b.test.com。当 b.test.com 向 a.test.com 发起文件上传的请求,浏览器会出发跨域机制。
第一阶段,常规操作运维修改 Nginx 配置,但是没有起作用。
第二阶段,运维不修改配置,单纯后端 Go echo 框架中间件处理。按照 Echo 官网的 Cors 做了配置,如下
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ AllowOrigins: []string{"https://a.test.com", "https://b.test.com"}, AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept}, AllowMethods: []string{ http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
}))
AllowOrigins 设置成 []string{"*"}, 还是没起作用。
于是在 dev 环境终于把跨域的问题解决了。但是当我们上线,发布到正式生产环境时,还是发生了古怪的事情。
- Nginx 配置了 access 和 error 日志, 但是所有的请求进来,这两个文件都没有日志。
- pro 的生产环境再次出现了跨域的问题。运维直接 copy 的配置过去(除了 servename不一样)。
又从头开始排查,然后找前端比对请求头。首先是请求头发生了变化,我在后端多设置了有关有关鉴权的2个 appid 和 token 的Header 参数。Header 层只要有一个 Header 不相同,就可以视为请求头存在跨域。(这个地方还不是很明确哈)。于是重新设置了这个 Header 。
最后一个问题是日志的问题,阿里云的 SLB 需要特殊设置,并且也要对 Options 做 204 的设置。这个是运维的原话,可惜我没有权限深入这一块了。这是另外一个话题。但是就是通过 阿里云的负载均衡设置,文件上传服务器 Nginx 设置(header头额外增加 app 和 token 参数),以及 golang echo framework 框架做中间件设置,三管齐下,最后跨域的问题解决掉了。
再者,其实 OSS 服务端签名直传并设置上传回调 的方案 B 才是官方推荐的。这个也是我开设计方案会议时,提议的方案。但是被否掉了,这个方案前端需要额外的工作量。最后没有采用这个方案,仅仅是完成了文件上传的功能,文件上传的性能没有发挥到极致,好可惜。
因为这个方案能直传OSS,不会占用文件上传服务器的宽带资源。既然最后文件都是要到 OSS 去的,那么就没有必要将文件先传到文件上传服务器中转,再转 OSS。如果采用服务端签名直传并设置上传回调的方案B,我们也就不需要自己再做跨域的特殊设置了。
不过,最后通过这个亲身经历,解决了【 跨域 】的问题。还是非常高兴的。与其整天研究一些不着调的技术,不如靠踏实地解决眼前的问题。