聊聊浏览器缓存

一.引言

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

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

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

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

二. 浏览器缓存类型

浏览器缓存类型分两种:

协商缓存:每次读取缓存时,先到服务器端去验证一下是否有改变,如果有就获取新的,没有从缓存中读取,响应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,不代表所有浏览器和中间件完全一致。切记!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356