浏览器缓存

之前对于浏览器的缓存一直是一知半解,可能这块前端涉入较少,主要是由后端在控制。这两天看Nodejs,于是重点研究了下,在此做个总结。

缓存必要性:

  浏览器打开一个网页,通常需要加载HTML、Javascript、CSS 等够成网页界面和逻辑的文件,加载这些东西是非常耗时的。而且这些内容在大部分情况下不会经常变更。因此浏览器提供了一种本地缓存机制,用户再次进入这个页面时,不需要再去向服务器端请求这些文件,而是直接从本地读取。这种机制减少了带宽浪费,同时也大大提高了用户体验。作为前端人员,研究下它的工作原理,还是很有必要的。

缓存实现方案:

  方案一: If-Modified-Since 和 Last-Modified

    浏览器第一次打开某个网址,必然是会请求服务器端的内容,并将返回的内容缓存在本地。文件返回头部都会带有一个 Last-Modified 字段,意思是最近一次的修改时间。如下所示:

      Last-Modified:Wed, 18 Dec 2013 00:32:38 GMT

    当浏览器再次访问该网址时,它会对本地文件进行检查,如果不能确定这份文件是否可以直接使用(如Expires过期), 将会发送一份请求给服务端,并且在请求头上带上If-Modified-Since字段,字段值为该文件的Last-Modified值:

      If-Modified-Since:Wed, 18 Dec 2013 00:32:38 GMT

    请求发送到服务器后,服务器端根据 If-Modified-Since 值是和文件的Last-Modified 值判断文件是否有更新。如果没有更新,返回状态码304,不需要返回文件内容,浏览器直接使用本地文件。否则,则返回200,并将新的内容返回给浏览器。

fs.stat(filename, function (err, stat) {
  var lastModified = stat.mtime.toUTCString();


  if(lastModified === req.headers[‘if-modified-since‘]) {
    res.writeHead(304, ‘Not Modified‘);
    res.end()
  } else {
    fs.readFile(filename, function (err, file) {
      var lastModified = stat.mtime.toUTCString();
      res.setHeader(‘Last-Modified‘, lastModified);
      res.writeHead(200, ‘OK‘);
      res.end();
    });
  }
});

      <nodejs 实现>

 

  方案二: If-None-Match 和 ETag

    方案一采用的是时间戳的方式实现,但是时间戳会有一些缺陷存在:

      文件的时间戳改动但内容不一定改动

      时间戳只能精确到秒级别,频繁的内容更新将无法生效

    因此在HTTP 1.1 中引入了ETag。ETag 是由服务器端根据文件内容生成的一串散列值,散列值随着文件内容改变而改变,且不会存在上述的问题。判断原理和方案一差不多,只不过将判断文件的修改时间改成判断文件散列值是否相同。

fs.readFile(filename, function (err, file) {
  var hash = getHash(file),
    nonMatch = req[‘if-none-match‘];


  if(hash === nonMatch) {
    res.writeHead(304, ‘Not Modified‘);
    res.end();
  } else {
    res.setHeader(‘ETag‘, hash);
    res.writeHead(200, ‘OK‘);
    res.end();
  }
});

    <nodejs 实现>

  方案三:设置 Expires

    方案一和方案二尽管在文件内容没有改动时可以节省带宽,但依然会消耗一个HTTP请求。想要浏览器在加载文件时不发送请求,而是直接去读本地文件,可以在请求返回头设置expires属性。

    expires 是一个GTM格式的时间字符串。服务器可以通过它设置文件在用户本地存储的过期时间。只要本地还存在这个缓存文件,在到期时间之前都不会再发送请求。如将Expires 设置为一个月以后:

      Expires:Sat, 15 Feb 2014 11:12:03 GMT

    在地址框中重新输入地址(注意不能按F5 或 Ctrl+R 刷新),查看chrome 控制台显示的请求信息如下:

    浏览器缓存

    状态信息显示的是200 OK (from cache)。在firebug 中这个请求直接会不可见。

  方案四:设置Cache-Control

    Cache-Control 功能更强大,它有一个 max-age 值,意思是文档被访问后的存活时间,单位为秒。这个时间是个相对值,相对的是文档第一次被请求时服务器记录的请求时间。max-age 能够避免Expires 带来的浏览器和服务前端时间不同步的问题。不过  HTTP 1.0不支持max-age,而且如果浏览器中max-age 和Expires 同时存在,且都被支持,max-age 会覆盖Expires。Cache-Control 由后台设置,在请求的返回头信息中出现。如下设置文档在客户端存活时间为2592000秒。

    Cache-Control:max-age=2592000

强制不读本地缓存:

  有些时候我们不希望浏览器去读去缓存数据,而是强制它去读去服务器端最新的数据。这种情况在ajax请求动态json数据的时候经常出现。解决方案如下:

  1)在URL search 部分加入随机数,如 http://example.com/data?123456

  2)设置请求头部的If-Modified-Since 属性为‘0‘。

清除缓存:

  当版本号更新的时候,我们希望浏览器读取我们最新版本的文件,而不是旧版本的文件。这种情况,比较普遍的解决办法是在URL中加入版本号,让浏览器发起新的URL请求,避免读去到本地缓存或是CDN缓存内容。如下面URL, 版本号为1.0.1:

    http://static.lanhuedu.com/kutianshi/1.0.1/assets/css/common.css

 

参考资料:《深入浅出Nodejs》

浏览器缓存

上一篇:HDU 1290 献给杭电五十周年校庆的礼物


下一篇:Java基础[03-面向对象]