vue SPA项目,浏览器和nginx反向代理缓存问题解决实方案

13,271 阅读4分钟
  1. 问题背景
    **a.**浏览器端在每次发布新的版本时候,总会出现因为单页面项目中index.html文件(200 ok from disk cache 不发送请求,直接取用了本地磁盘缓存)和服务端版本不一致的问题,导致用户不能及时更新,需要通过手动刷新来强制从服务端更新文件。

    **b.**补充一下,服务端和浏览器之间架构。

  2. 三级缓存问题
    上述,我们看到浏览器没有发出请求直接从磁盘中取出index.html文件,这其实是二级缓存。 
    当浏览器访问的服务器没有设置缓存时,浏览器先200 OK from memory cache,如果浏览器缓存中没有该文件,那么浏览器会200 OK from disk cache 从磁盘中找改文件,如果还找不到,浏览器会从服务端请求该文件下载使用。

  3. Last-Modified/If-Modified-Since
    **a.**当浏览器第一次请求一个url时,服务器端的返回状态码为200,同时HTTP响应头会有一个Last-Modified标记着文件在服务器端最后被修改的时间。

    **b.**浏览器第二次请求上次请求过的url时,浏览器会在HTTP请求头添加一个If-Modified-Since的标记,用来询问服务器该时间之后文件是否被修改过。

  4. Etag/If-None-Match
    **a.**当浏览器第一次请求一个url时,服务器端的返回状态码为200,同时HTTP响应头会有一个Etag,存放着服务器端生成的一个序列值。

    **b.**浏览器第二次请求上一次请求过的url时,当浏览器第一次请求一个url时,服务器端的返回状态码为200,同时HTTP响应头会有一个Last-Modified标记着文件在服务器端最后被修改的时间。

  5. **Etag 主要为了解决 Last-Modified 无法解决的一些问题:
    **
    **a.**Etag和Last-Modified特点:

    1).它们都属于协商缓存,对内容的有效性进行验证。
    2).Etag的值通常为文件内容的哈希值;而Last-Modified为最后修改的时间。
    3).Last-Modified只能精确到秒,秒之内的内容更新Etag才能检测。
    4).文件有时会定时重新生成相同内容,Last-Modified不能很好辨别,某些服务器甚至不能精确的得到文件的最后修改时间。
    5).Etag每次服务端生成都需要进行读写操作,而Last-Modified只需要读取操作,Etag的消耗是更大的。

    Etag更像是Last-Modified的一种补充、完善。

  6. **Expires
    **Expires是RFC 2616(HTTP/1.0)协议中和网页缓存相关字段。用来控制缓存的失效日期,要注意的是,HTTP/1.0有一个功能比较弱的缓存控制机制:Pragma,使用HTTP/1.0的缓存将忽略Expires和Cache-Control头。
    可以在nginx中设置 expires:-1; 如下
    **
    其它配置:

    location ~ \.(wma|wmv|asf|mp3|mmf|zip|rar|swf|flv)$ {
                   root /var/www/upload/;
                   expires max;
           }
    

    **

    expires 指令可以控制 HTTP 应答中的“ Expires ”和“ Cache-Control ”的头标(起到控制页面缓存的作用)
    语法:expires [time|epoch|max|pff]
    默认值:off
    expires指令控制HTTP应答中的“Expires”和“Cache-Control”Header头部信息,启动控制页面缓存的作用
    time:可以使用正数或负数。“Expires”头标的值将通过当前系统时间加上设定time值来设定。
    time值还控制"Cache-Control"的值:
    负数表示no-cache
    正数或零表示max-age=time
    
    epoch:指定“Expires”的值为 1 January,1970,00:00:01 GMT
    max:指定“Expires”的值为31 December2037 23:59:59GMT,"Cache-Control"的值为10年。
    -1:指定“Expires”的值为当前服务器时间-1s,即永远过期。
    off:不修改“Expires”和"Cache-Control"的值
    
    expires使用了特定的时间,并且要求服务器和客户端的是中严格同步。
    而Cache-Control是用max-age指令指定组件被缓存多久。
    对于不支持http1.1的浏览器,还是需要expires来控制。所以最好能指定两个响应头。
    但HTTP规范规定max-age指令将重写expires头。
    
  7. **在设置Expires、Etag/If-None-Match、Last-Modified/If-Modified-Since缓存机制下,浏览器第一次请求和第二次请求缓存机制
    **a.第一次请求

    b.第二次请求

  8. 用户操作与缓存

  9. **基于vue-cli3.x脚手架下打包的spa项目缓存机制方案
    **a.思路
    spa项目的网站域名映射的ip地址下入口文件是index.html,除此之外,js、css、img、font等等静态文件都是从index.html下加载出来的。
    在vue-cli3.x脚手架打包下的js、css、img、font等静态文件名都是包含hash,所以每次打包index.html加载出来的文件都不会出现相同名称文件,因此也不会出现缓存问题。
    因此,我们只需要通过配置nginx,设置协商缓存机制,每次发版本后,让浏览器每次使用的index.html,这样js、css、img、font等静态文件都是最新。
    本质上,我只需要设置index.html受nginx配置的缓存机制的影响就好了,js、css、img、font等静态文件可以留给浏览器自身的缓存机制来控制,当不是第一次请求js、css、img、font等静态文件时,这些资源文件可以from disk cache/from memory cache ,直接从缓存中取对应的文件,这样大大减少服务器的资源消耗,同时,通过网友文件的加载速度和页面渲染速度。

    b.遇到的问题
    在nginx配置过程,我们尝试取捕捉index.html文件,捕捉到之后设置如下:

    location ^~ /aa/ {  
            proxy_set_header  X-Real-IP  $remote_addr;
            #html 文件不缓存(你也可以设置为协商缓存,但是参考其他大厂方案,一般index.html入口文件都不作缓存,一方面是因为index.html很小基本在1KB左右,另外这个文件内容变化频繁)
            if ($request_filename ~* ^.*?.(html|htm)$){
                expires -1s;
                add_header Cache-Control no-cache;
               }
             proxy_pass  http://XXX/aa/;               
          } 
    

    但是这样是捕捉不到index.html的,无法控制index.html的缓存。
    原因分析,是因为我们在捕捉过程没有一个文件路径是含有.html后缀名的,因此无法过滤出index.html加以设置。

    c.解决方案
    我们无法通过捕捉含有.html后面的路径,但是我们知道“/aa/”所对应的文件就是index.html,所以我们来捕捉这个“/aa/”路径,且不捕捉该路径下子级文件(这样把子文件夹中的js、css、img、font等静态文件也设置来),配置如下:

    location ^~ /aa/ {  
       proxy_set_header  X-Real-IP  $remote_addr;        #html 文件不缓存(你也可以设置为协商缓存,但是参考其他大厂方案,一般index.html入口文件都不作缓存,一方面是因为index.html很小基本在1KB左右,另外这个文件内容变化频繁)
            if ( $request_uri = "/aa/") {
                expires -1s;
                add_header Cache-Control no-cache;
           #add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";           }
             proxy_pass  http://XXX/aa/;           
          } 
    

    如此,我们就能简单的过滤出index.html,并加以设置缓存。
    设置完成后浏览器第一次加载:

    设置完成后浏览器第二次加载:
    **
    **
    通过以上设置,我们可以很轻松的解决了浏览器和服务器之间的缓存问题。

    有建议或问题可以加群qq交流535798405