Nginx在浏览器上的缓存行为
1、Nginx默认配置下的缓存行为
以下演示用的 Nginx 版本都是 1.12.2,默认的demo项目目录如下:
index.html 和 test.html 文件内容基本一样,只是为了测试浏览器会不会对文件名采取不同缓存行为。
文件内容如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>index.html缓存测试</title> <link rel="stylesheet" href="./index.css"> <link rel="stylesheet" href="./wentest.css"> </head> <body> <h1>index.html缓存测试</h1> <img src="./imgtest.png" alt=""> <img src="./index.jpg" alt=""> <img src="./test02.jpg" alt=""> <script src="./index.js"></script> <script src="./wentest.js"></script> </body> </html>
以默认的 Nginx 配置为准,具体如下。(下面的 Nginx 配置跟默认配置基本保持一致,只是修改了访问时指向的资源路径,未做任何缓存配置。)
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 80; server_name localhost; location =/ { root /usr/local/nginx/html/mytest; index index.html; } location ~* \.(html|gif|jpg|jpeg|css|js|png|ico|eot|ttf|woff|svg)$ { root /usr/local/nginx/html/mytest; } } }
分别访问 index.html 和 test.html,可以发现,两者的缓存行为是一样的,可以证明,浏览器的缓存行为跟文件名无关。
1.1、Chrome浏览器缓存表现
以下用的Chrome浏览器版本都是127.0.6533.100(正式版本) (64 位)
1.1.1、首次访问
第一次访问结果如下,可以看到第一次访问状态码都是 200,且都是没有缓存记录的,
通过查看 Nginx 日志可以看到访问记录,如下:
html 文件和其他文件的请求头和响应头分别如下:
- html 文件:
- JS、CSS 和图片文件:
可以看出,首次访问时都不会命中缓存,而且 Chrome 浏览器默认都会往请求头中添加 Cache-Control: no-cache,即告诉服务器浏览器需要通过协商缓存来判定是否使用浏览器缓存。而服务器默认会返回 ETag 和 Last-modified 响应头信息,以此告诉浏览器关于缓存的信息。
1.1.2、再次访问
在进行首次访问后,多次刷新浏览器(F5键)或间隔长时间再刷新浏览器,可以看到,此时浏览器对资源进行了缓存,并且浏览器对 html 文件的缓存行为和其他资源的不一样。
查看 Nginx 日志可以看到Nginx 只接收到了 html 的请求,浏览器对于其他资源是直接取的浏览器缓存,并没有往服务器发起请求。如下,状态是 304,文件大小 0
html 文件和 其他文件的请求头和响应头分别如下:
- html 文件:
- JS、CSS 和图片文件:
1.1.3、结论
Nginx 默认配置,在 Chrome 浏览器中:
- 首次访问,永远不会使用缓存(因为浏览器此时还没有缓存该资源),但是浏览器会在请求头中会带上强制缓存标识 Cache-Control: no-cache(这实际上也是告诉服务器使用协商缓存),而 Nginx 服务器也会在资源的响应头加上协商缓存的标识( 包括ETag 和 Last-Modified )。
- 再次访问时或间隔较长时间再访问,此时全部都会使用缓存,不同的是,html 文件会使用协商缓存,而其他资源都使用强缓存。
所以说,Nginx 默认配置下,在 Chrome 浏览器中,首次访问,资源不会使用缓存;再次访问,html 文件会使用协商缓存,而其他资源都使用强缓存;
1.2、Firefox 浏览器缓存表现
Firefox 浏览器(124.0.2 (64 位))缓存行为其实跟 Chrome 浏览器一样。首次访问时不会命中缓存,短时间内再次访问时 html 文件会通过协商缓存判断是否取浏览器缓存,而其他资源也是直接取浏览器缓存。
- 首次访问
- 再次访问
1.3、更新服务器资源时的缓存表现(模拟生产部署)
我们更新服务器上的 index.html、index.js、index.css、index.jpg 文件,模拟生产上的部署,更新完后不重启 Nginx(实际上重启 Nginx 并不会影响缓存效果),刷新浏览器,第一次请求如下:
- index.html 响应信息
- index.js 响应信息
可以看到,index.html 能正常请求到最新资源,并且last-modified 已经发生了改变;但是 index.js、index.css 等资源并无法请求最新资源,还是命中缓存且 last-modified 没变。
再次请求如下:
可以看到又回到初始状态,html 文件命中协商缓存,其他资源使用强制缓存。
2、Nginx生产配置下的缓存行为
生存上关于 Nginx 的缓存的配置一般也不会太负载,一般只是会配置下静态资源缓存短暂时间。如下基本还原了真实的 Nginx 生产配置:
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 80; server_name localhost; location =/ { root /usr/local/nginx/html/mytest; index index.html; } location ~* \.(html|gif|jpg|jpeg|css|js|png|ico|eot|ttf|woff|svg)$ { root /usr/local/nginx/html/mytest; expires 1m; } } }
如上,真实的生产配置往往只会往静态资源添加短暂时间的强制缓存(如上例1分钟)。更新 Nginx 配置,重启 Nginx,访问浏览器可以发现:
- 首次访问时,浏览器如果没有缓存则不会命中缓存,资源响应全都是 200 响应码
- 一分钟内多次访问时,html 文件仍然是 304 即会使用协商缓存,但其他资源会依照 Nginx 命中强制缓存
- 相隔一分钟之后再次访问,html 文件仍然是 304 使用协商缓存,其他资源也变成 304 响应码,即会依照 Nginx 配置命中协商缓存
可以证明,在 Nginx 中配置了静态资源的强制缓存后,除 html 文件外其他资源在配置的时间内是能命中强制缓存的,超过配置时间后会通过协商缓存来获取资源。但是强制缓存对 html 文件是没用的(至少验证了在 Chrome、Firefox 浏览器中是这样的),即不管是否配置了强制缓存,浏览器都会对 html 文件使用协商缓存。浏览器的这一行为往往也符合真实的场景和真实的需要。
3、最佳实践
最佳实践配置示例如下:
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 80; server_name localhost; location =/ { root /usr/local/nginx/html/mytest; index index.html; } location ~* \.(html|gif|jpg|jpeg|css|js|png|ico|eot|ttf|woff|svg)$ { root /usr/local/nginx/html/mytest; expires 1m; } } }
说明:
- 最佳配置说明:给所有除 html 外的静态资源配置短时间的强制缓存,如1分钟。(或者也包含 html 文件,因为上面的实践表明,即使给 html 配置了强制缓存,浏览器针对 html 文件使用的永远都是协商缓存)
- 原因说明:首次访问后,在1分钟内浏览器都会命中强制缓存(html除外),不会往 Nginx 发送请求,这可以提高页面访问速度。在1分钟之后,强制缓存失效,浏览器发出请求,由服务器决定是否使用缓存。所以如果服务器资源有更新的话那么浏览器也能在1分钟之后获取到最新的资源。
实际现在很多前端项目都会需要 build 编译,编译过后 js、css、图片等资源名称会加上时间戳后缀(一般编译后 html 文件名不会加后缀),即名称已改变,而 html 文件一直都是协商缓存,所以一旦重新部署更新了 html 文件,浏览器可以立即获取到最新的 html 文件,而最新的 html 文件里引入的其他资源名称已经发生了改变,所以也可以获取到最新的其他资源文件。这种情况下也可以保证用户能一直获取到最新资源,即使不配置任何缓存配置。