http缓存实战(让你不再会学过就忘)

“学过就忘”,一个知识点长时间不去读取,那么在你脑海中的基因片断会逐渐模糊,可是基因索引仍是存在的。本篇文章经过实战理论想结合的方式来帮助你们更深入的记忆经常使用知识点。 若有不足之处,恳请斧正。javascript

什么是HTTP缓存 ?

http缓存通俗来讲: 当客户端向服务器请求资源时,不会直接向服务器获取资源(喂:服务器,给我一张最新的自拍图片)。而是先看下本身的存储里有没有上次保存的还在有效期内的资源。若是有的话,就省下了一笔运费(流量)。这里举一个简单的例子:html

  • 客户端须要a.js,因而发送一个请求头(1kb)
  • 服务端响应后,返回a.js(10kb) + 响应头(1kb)
  • 如此反复每次就是11kb的流量传送

可是咱们咱们须要的文件a.js的内容每每并无发生变化,却仍然须要浪费流量,为了解决这个问题,因而人们提出了http缓存这个概念。java

HTTP缓存能够分为强缓存和协商缓存。node

强缓存

  • Expires
  • Cache-Control
  • Pragma

协商缓存

  • Last-Modified
  • If-Modified-Since
  • ETag
  • If-Not-Match

强缓存与协商缓存的区别:

用户大人:我如今须要a.js,大家帮我拿回来
强缓存: 稍等,我找下我这里有没有关于a.js的备份,找到了。(消耗0kb流量)
协商缓存: 我这里也有备份,不过我得问下服务端这个备份是否是最新款,发送请求头(1kb流量)。服务端回复(1kb流量)响应头则使用本地备份,如果返回a.js(10kb)和响用头(1kb)则使用服务器返回的最新数据。webpack

强缓存

Expires

这是 HTTP 1.0 的字段,表示缓存到期时间,是一个绝对的时间 (当前时间 + 缓存时间)。在响应消息头中,请求资源前浏览器会用当前时间与其值对比,如果未过时则不须要再次请求。web

新建cache.html面试

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="icon" href="data:;base64,=">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>cache</title>
  <script src="cache.js"></script>
</head>
<body>
  <h1>cache</h1>
</body>
</html>
复制代码

新建cache.jsnpm

let http = require('http'),
fs = require('fs'),
path = require('path'),
url = require("url")
http.createServer((req,res)=>{
  let pathname = __dirname + url.parse(req.url).pathname; // 获取文件路径
  let statusCode = 200 // 响应头状态码
  let extname = path.extname(pathname) // 获取文件扩展名
  let headType = 'text/html' // 内容类型
  if( extname){
    switch(extname){
      case '.html':
       headType = 'text/html'
      break;
      case '.js':
        headType = 'text/javascript'
      break;
    }
  }
  fs.readFile(pathname, function (err, data) {
    res.writeHead(statusCode, {
      'Content-Type': headType,
      'Expires':new Date(Date.now() + 10000).toUTCString() // 设置文件过时时间为10秒后
    });
    res.end(data);
  });
}).listen(3000)

console.log("Server running at http://127.0.0.1:3000/");
复制代码

启动json

node cache.js
复制代码

打开浏览器访问http://127.0.0.1:3000/cache.html 查看network
浏览器

  1. 首次访问所有走网络请求资源
    cache1.png
  2. 10s内再次刷新从内存中加载(from memory cache)
    cache2.png
  3. 10s内关闭tab,从新打开请求的cache.html,从磁盘加载(from disk cache)
    cache3.png
  4. 10s之后请求,缓存已经失效,重复第1步

缺点:

  1. Expires过时控制不稳定。由于若是咱们修改电脑的本地时间,会致使浏览器判断缓存失效。

Cache-control

这是HTTP1.1的字段,这是一个相对时间'Cache-Control':'max-age=20'意思是20秒内使用缓存资源,不用请求服务器

// 省略其余代码
  fs.readFile(pathname, function (err, data) {
    res.writeHead(statusCode, {
      'Content-Type': headType,
      'Cache-Control':'max-age=20'
    });
复制代码

cache5.png

Cache-Control 除了max-age 相对过时时间之外,还有不少属性

  1. no-store:全部内容都不缓存
  2. no-cache:缓存,可是浏览器使用缓存前,都会请求服务器判断缓存资源是不是最新,它是个比较高贵的存在,由于它只用不过时的缓存。
  3. public 客户端和代理服务器(CDN)均可缓存
  4. private 只有客户端能够缓存

更多属性可参考 Cache-Control

Tips:也许你们在这里有一些小疑问,为何对cache.html设置ExpiresCache-Control在谷歌浏览器里不生效。这时候查看request header 发现 Cache-Control: max-age=0,浏览器强制不用缓存。

cache6.png
这是由于浏览器会针对的用户不一样行为采用不一样的缓存策略,这样会致使在不一样的浏览器会产生不一样的现象:

Chrome does something quite different: 'Cache-Control' is always set to 'max-age=0′, no matter if you press enter, f5 or ctrl+f5. Except if you start Chrome and enter the url and press enter.

pragma

pragma是http/1.1以前版本的历史遗留字段,仅做为与http的向后兼容而定义。这里不作讨论。感兴趣的朋友能够点击了解 Pragma

node 热更新

这里若是你们以为每次修改cache.js文件都须要从新执行node run cache.js,这里咱们能够配置node 热更新

npm init
npm i -D nodemon
复制代码

npm i -Dnpm install --save-dev的缩写

修改package.json

"scripts": {
    "dev": "nodemon ./bin/www"
  },
复制代码

下面咱们只须要执行一次npm run dev便可

对比缓存(协商缓存)

上面的强缓存依旧存在着很大的缺陷。当设置的时间过时后,无论文件内容有没有变化,咱们不得再也不次向服务器请求资源。这时候咱们就须要用到协商缓存了。

Last-Modified

响应值,由服务器返回给客户端关于请求资源的最近修改时间 (GMT标准格式)

If-Modified-Since

请求值 , 由客户端发送给服务端上次返回的资源修改时间

首次请求时服务端会携带Last-Modified返回给客户端,客户端将其数值保存起来,并从新命名为If-Modified-Since 再次请求时,客户端会先发送一个携带If-Modified-Since的请求头发送到服务端,服务端会比较请求头的If-Modified-Since和服务器请求资源上次的修改时间(Last-Modified).

  1. 若是资源已经被修改:那么返回响应资源a.js(10kb) + 响应头(1kb),状态码:200 OK
  2. 若是没有被修改:那么只返回响应头(1kb),状态码:304 Not Modified
// 省略其余代码
let stat = fs.statSync(pathname);
  fs.readFile(pathname, function (err, data) {
    // 判断请求头的文件修改时间是否等于服务端的文件修改时间
    if(req.headers['if-modified-since'] === stat.mtime.toUTCString()) { // mtime为文件内容改变的时间戳
      statusCode = 304;
    }
    res.writeHead(statusCode, {
      'Content-Type': headType,
      'Last-Modified':stat.mtime.toUTCString()
    });
    res.end(data);    
  });
复制代码

cache7.png
但使用Last-Modified一样存在缺陷

  1. last-modified是以秒为单位的,若是资源在1s内修改屡次,因为1s内last-modified并未改变,客户端仍然会使用缓存。
  2. 若是在服务器上请求的资源(a.js)被修改了,但其实际内容根本没发生改变,会由于 Last-Modified 时间匹配不上而从新返回 a.js 给浏览器(举例:服务器动态生成文件)

为了解决上述问题,使用新的字段 ETag 和 If-None-Match

ETag

响应值,由服务器返回给客户端根据文件内容,算出的一个惟一的值 (GMT标准格式)

If-Not-Match

请求值 , 由客户端发送给服务端上次返回的资源惟一值

请求流程与Last-Modified一致

上面咱们所述last_modified 通常由 mtime 组成,而 ETag 通常由 last_modified content_length 组成

// 省略其余代码
  fs.readFile(pathname, function (err, data) {
    let Etag = `${data.length.toString(16)}${stat.mtime.toString(16)}`
    if((req.headers['if-modified-since'] === stat.mtime.toUTCString()) || (req.headers['if-none-match'] === Etag)) {
      statusCode = 304;
    }
    res.writeHead(statusCode, {
      'Content-Type': headType,
      Etag
    });
    res.end(data);    
  });
复制代码

cache8.png

缓存优先级

当多种缓存同时存在时,浏览器改使用哪一种缓存方式呢?这里就有了一个优先级关系

  • 强缓存与协商缓存同时存在时,若是强缓存还在生效期则强制缓存,不然使用协商缓存。(强缓存优先级 > 对比缓存优先级)
  • 强缓存expirescache-control同时存在时,cache-control会覆盖expires (cache-control优先级 > expires优先级。)
  • 对比缓存EtagLast-Modified同时存在时,Etag会覆盖Last-Modified效。(ETag优先级 > Last-Modified)优先级。

最佳实践

上面只是让你们更好的了解了http缓存的大概知识点,那么在实际开发中咱们是如何如何利用缓存实现更好的用户体验的呢?
相信webpack你们已经并不陌生,若是有过实际配置经验的同窗必定记得咱们在配置出口文件output或者打包图片的filename时每每会加上hash || chunkhash || contenthash这些字段

module.exports = {
     output:{
        path:path.resolve(__dirname,'../dist'),
        filename:'js/[name].[contenthash:8].js',
        chunkFilename:'js/[name].[chunkhash:8].js'
    },
    loader:{
        rules:[
             {
                test:/\.(jep?g|png|gif)$/,
                use:{
                  loader:'url-loader',
                  options:{
                    limit:10240,
                    fallback:{
                      loader:'file-loader',
                      options:{
                        name:'img/[name].[hash:8].[ext]'
                      }
                    }
                  }
                }
            }
        ]
    }
}
复制代码

这样打包出来的文件名称每每以下

这样若是每次文件有变化,那么文件名称随即变化。浏览器则会从新请求这些文件资源。若是文件名称与上次一致,那么则会使用到http缓存策略。

最后附上用户行为对浏览器缓存的影响

拓展阅读

面试精选之http缓存

相关文章
相关标签/搜索