最近开发完成了一个前端页面的功能,代码已经在本地测试成功,然后发布到云端服务器。但奇怪的是,所有人的浏览器打开这个页面时,第一次访问时页面上却还是显示为老样子,强制刷新后则变为新提交的逻辑。

开始 Debug

  1. 检查云端代码是否为最新 : git log 查看当前最新的 commit ID,与我本地代码库比较,是一致的。不是代码的问题
  2. 找出现过问题的同事,在他的电脑上重新打开本页面,问题没有复现。找还没有测试过本页的同事的机器,百分百复现 :证明经过刷新后就可以永久解决,可能跟缓存有关系
  3. 登录云服务器,在相关文件加入一行 console.log("hello") 并保存,立即打开浏览器测试本页面,查看 console,没有出现该日志,刷新页面后,查看 console,出现 hello 字样: 证明此处逻辑固定,跟是什么样的代码无关,只要是这样的操作流,即可百分百复现问题
  4. 换一种测试方式,改代码并保存后,首先打开一个标签页没有出现 hello,关闭该标签页新开标签页依然没有出现 hello ,在新开的标签页上刷新后 可以出现 hello
  5. 换浏览器测试,均出现该问题: 跟浏览器种类无关

经过以上5个步骤后,问题逐渐变得清晰起来,总结一下我们能得到的信息:

问题的起因是服务器上更新了前端相关代码,浏览器(无论是什么浏览器)立刻访问该页面不能即时加载最新内容,强制刷新后则可以。

解决思路:

  1. 因为我们的云端服务器配置了 nginx + express 的方式,而本地开发机只有 express,问题只在云端服务器上出现,变量就在 nginx 上,所以从 nginx 入手
  2. 首先用浏览器打开一个任意的页面,用调试工具查看该页面的 network 请求,可以清楚的看到 304 请求,而且 request headerIf-Modified-Sinceresponse header 中的 Last-Modified 时间都显示的是当前时间,这是正常的加载现象,现在我们来做改动。
  3. 服务器端改动该页面代码,加入一行 console.log("hello") 。命令行查看文件修改时间,modify 时间显示正确。然后浏览器新开标签页打开这个页面,查看 network 请求:304!(这意味着前端向后端请求该文件时,后端告知该文件没有变动!怎么可能呢,刚刚后端自己的文件系统已经标记了最新的修改时间了!)于是查看 network 中的 response header 里的 Last-Modified,果然没猜错,这里的时间根本就没有更新,还是上一步中显示的时间。If-Modified-Since 也是如此。
  4. 刷新当前标签页,果然页面逻辑正常了,能看到 hello 输出,而两个时间也变成文件最近修改的时间了。

这四步过程中,一直在后端开着 log 查看 nginx 的输出,其中在第2步的时候,nginx 确实向前端返回304,但第3步的时候却没收到任何请求,第4步的时候因为页面强制刷新而且检测到文件改动返回200.问题就出在第3步,nginx 根本没收到任何请求,浏览器“自作主张”为这次前端请求返回了304,导致页面逻辑不能及时更新而导致了这次 bug.

查阅了 http status code 相关文档后,逐渐明白了问题的缘由。因为对浏览器内部机制还不甚清楚(后续会继续研究),所以目前大致是这样猜测的:

根据 http status code 文档,即使是 304 Code,也应该是服务端返回的而不应该是浏览器“伪造”的,所以猜测浏览器从服务端收到过一个文件的304 code后,会比较“粗暴”的缓存住这个文件以便提升加载性能,除非用户进行强制刷新操作。

目前关于这个问题应该如何妥善解决还在研究之中,工作中也在与同事们一起了解更多关于浏览器机制和缓存的问题。哦,对了,@song940 提出过一个 307 code 的猜想,我抽时间会去验证一下。

References:

有关 http status code 的文档
有关 http header field 的文档