美文网首页
聊聊浏览器缓存

聊聊浏览器缓存

作者: cd2001cjm | 来源:发表于2019-07-27 18:38 被阅读0次

    一.引言

    对于前端工程化,浏览器缓存是一个很重要的知识点。

    浏览器缓存是提升加载性能的重要手段,但如果使用不当,反而会造成副作用,

    比如:脚本发布了但客户端没加载最新的

    下面就对浏览器缓存一探究竟

    二. 浏览器缓存类型

    浏览器缓存类型分两种:

    协商缓存:每次读取缓存时,先到服务器端去验证一下是否有改变,如果有就获取新的,没有从缓存中读取,响应code为304

    强制缓存:只要缓存没过有效期,就强制读取缓存,响应code为200。该类缓存必定是要持久化到disk的。Firefox中并没有对强制缓存进行进一步的分类。但谷歌浏览器的处理略有不同,分为两种:一种是(from disk cache),另一种是(from memory cache)

    谷歌浏览器HTTP请求流程

    从流程图可以看到,http请求最重要的类就是HttpCache,请求发送前,会先验证是否命中缓存

    如果命中,就会发起httpCacheTransaction,去读取缓存信息。当然在读取前会判断是否要进行缓存有效性的验证。如果有变化就更新缓存

    未命中,就会发起httpNetworkTransaction去服务端获取,然后根据响应头去判断是否要缓存,以及何种形式的缓存。如果缓存就写入磁盘。

    谷歌浏览器的缓存模式有13种,具体可参考http_cache_transaction.cc。

    常见的有三种:无缓存,强制缓存,协商缓存

    三. HTTP缓存指令

    Header信息分为request header和response header。

    请求缓存指令:会影响请求发送时,是否要缓存。

    响应缓存指令:会影响接受到响应时,是否要缓存。

    Pragma,Expires是HTTP1.0的字段,在HTTP1.1中已经被Cache-Control取代,不做讨论。

    3.1,请求缓存指令

    从谷歌代码中可以看到(注意注释),Cache-Control相关的有两个值:

    no-cache:如果请求包含这些请求头中的一个,那么避免重用我们的缓存副本(如果有的话)

    max-age=0:如果请求包含这些请求头中的一个,则强制缓存副本(如果有)将在重用前重新验证

    3.2,响应缓存指令

    Cache-control:nostore 

    响应内容不会写入disk_cache

    Cache-control:max-age=3600(1小时)

        写入disk_cache,1小时内再次访问该url,强制从缓存中读取。基础时间是第一次访问时,响应中返回的Date

    Cache-control:no-cache 

    再次访问的时候,会去服务端验证

    3.3,对于协商缓存,去服务端的验证过程是什么,或者说我们要验证什么。

    简单来说就是验证服务器脚本是否改变,改变了就加载新的,分为下面三个步骤:

    a,响应头信息是否改变

    响应头的改变会影响浏览器对缓存的处理。

    比如:响应头由【缓存】->【不缓存】,那么对已有缓存也要根据情况进行相应的删除替换处理

    b,资源的最后修改时间是否改变

      首次访问的时候,响应会返回Last-Modified,再次访问时,该字段会放在请求中的If-Modified-Since,然后在服务端比较文件的最后修改时间,以确认资源是否发生变化。

    这只是时间维度的比较,但如果内容没变,只是时间改变了呢?

    为了解决内容对比问题,就有了下面的Etag

    c,内容是否改变

      首次访问的时候,响应同时会返回Etag,再次访问时,该字段会放在请求中的If-None-Match,然后在服务端比较是否一致。

    Etag真的能解决文件内容一致性的问题么?

    3.4 ,Etag

    Etag 是URL的Entity Tag,用于标示URL对象是否改变。标准概念网上资料很多,这里不做累述。

    根据Etag的定义,我们期待的是:当文件内容改变的时候,浏览器能根据Etag去加载最新的资源文件。

    是不是真的能达到期望的效果,完全取决于中间件对Etag的算法实现。

    我们看看Nginx的Etag生成代码:

    ngx_int_t

    ngx_http_set_etag(ngx_http_request_t *r)

    {

        ngx_table_elt_t          *etag;

        ngx_http_core_loc_conf_t  *clcf;

        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

        if (!clcf->etag) {

            return NGX_OK;

        }

        etag = ngx_list_push(&r->headers_out.headers);

        if (etag == NULL) {

            return NGX_ERROR;

        }

        etag->hash = 1;

        ngx_str_set(&etag->key, "ETag");

        etag->value.data = ngx_pnalloc(r->pool, NGX_OFF_T_LEN + NGX_TIME_T_LEN + 3);

        if (etag->value.data == NULL) {

            etag->hash = 0;

            return NGX_ERROR;

        }

        etag->value.len = ngx_sprintf(etag->value.data, "\"%xT-%xO\"",

                                      r->headers_out.last_modified_time,

                                      r->headers_out.content_length_n)

                          - etag->value.data;

        r->headers_out.etag = etag;

        return NGX_OK;

    }

    void

    ngx_http_weak_etag(ngx_http_request_t *r)

    {

        size_t            len;

        u_char          *p;

        ngx_table_elt_t  *etag;

        etag = r->headers_out.etag;

        if (etag == NULL) {

            return;

        }

        if (etag->value.len > 2

            && etag->value.data[0] == 'W'

            && etag->value.data[1] == '/')

        {

            return;

        }

        if (etag->value.len < 1 || etag->value.data[0] != '"') {

            r->headers_out.etag->hash = 0;

            r->headers_out.etag = NULL;

            return;

        }

        p = ngx_pnalloc(r->pool, etag->value.len + 2);

        if (p == NULL) {

            r->headers_out.etag->hash = 0;

            r->headers_out.etag = NULL;

            return;

        }

        len = ngx_sprintf(p, "W/%V", &etag->value) - p;

        etag->value.data = p;

        etag->value.len = len;

    }


    通过上面的代码我们可以看到nginx的算法:

    强Etag=最后修改时间+”-”+内容长度 (16进制)

    弱Etag=“\W”+ 强Etag

    当我们发布新文件的时候,最后修改时间都会发生变化。基本能满足项目需要。

    但通过该算法,我们也能看到,当内容没变最后修改时间变了,就不会命中缓存的问题依然没有得到解决。仅仅判断内容长度还是不够的。

    所以对于有能力扩展中间件的公司,都会自行实现Etag算法,目前看来基于文件MD5码是个不错的选择

    四,写在最后

    通常在项目中,多数都是对响应指令进行控制。

    缓存机制完全是依赖浏览器和中间件的实现,本文的分析是基于谷歌和nginx,不代表所有浏览器和中间件完全一致。切记!

    相关文章

      网友评论

          本文标题:聊聊浏览器缓存

          本文链接:https://www.haomeiwen.com/subject/bqoerctx.html