http协议(三)缓存头Cache-Control和缓存验证ETag、Last-Modified

Cache-Control 通用消息头字段,被用于在http请求和响应中,通过指定指令来实现缓存机制。缓存指令是单向的,这意味着在请求中设置的指令,不一定被包含在响应中。

Cache-Control指令包含了多个指令,分别具有不同的用途,比如设置缓存权限和缓存时间。

指令格式具有以下有效规则:

  1. 不区分大小写,但建议使用小写。
  2. 多个指令以逗号分隔。
  3. 具有可选参数,可以用令牌或者带引号的字符串语法。

1. 指令

1.1 可缓存性

public
表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容。(例如:1.该响应没有max-age指令或Expires消息头;2. 该响应对应的请求方法是 POST 。)
private
表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容,比如:对应用户的本地浏览器。
no-cache
在使用缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证(协商缓存验证)。
no-store
缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。

1.2 到期

max-age=
设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间。
s-maxage=
覆盖max-age或者Expires头,但是仅适用于共享缓存(比如各个代理),私有缓存会忽略它。
max-stale[=]
表明客户端愿意接收一个已经过期的资源。可以设置一个可选的秒数,表示响应不能已经过时超过该给定的时间。
min-fresh=
表示客户端希望获取一个能在指定的秒数内保持其最新状态的响应。

1.3 重新验证和重新加载

must-revalidate
一旦资源过期(比如已经超过max-age),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求。
proxy-revalidate
与must-revalidate作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。

1.4 其他

no-transform
不得对资源进行转换或转变。Content-Encoding、Content-Range、Content-Type等HTTP头不能由代理修改。某些代理服务器可能会对一些资源进行转换,以便节省缓存空间或者减少缓慢链路上的流量。no-transform指令不允许这样做。
only-if-cached
表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝。

2. 使用Cache-Control合理缓存资源

启动以下服务器,当我们访问8888端口时,根据请求路径分别返回一个htnl文件和一个js文件:
Server.js:

const http = require('http')
const fs = require('fs')

http.createServer(function (request, response){
  console.log('request url: ', request.url)

  if (request.url === '/') {
    const html = fs.readFileSync('index.html', 'utf-8')
    response.writeHead(200, {
      'Content-Type': 'text/html'
    })
    response.end(html)
  } else if (request.url === '/script.js') {
    response.writeHead(200, {
      'Content-Type': 'text/javascript'
    })
    response.end('console.log("This is a script")')
  }
}).listen(8888)

console.log('server running on 8888')

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body></body>
<script src='/script.js'></script>
</html>

现在我们访问8888端口,并且刷新几次,我们发现,始终会发起两个网络请求:
http协议(三)缓存头Cache-Control和缓存验证ETag、Last-Modified_第1张图片
现在修改server.js,使script.js能被缓存在客户端,下次访问时从本地加载:

...
else if (request.url === '/script.js') {
    response.writeHead(200, {
      'Content-Type': 'text/javascript',
      'Cache-Control': 'public, max-age=31536000' //增加Cache-Contorl指令
    })
    response.end('console.log("This is a script")')
  }
...

现在我们再次访问8888端口并刷新几次,这是我们发现 /script.js请求直接从本地加载资源:
在这里插入图片描述
但是,这里存在一个问题需要注意,如果我们在max-age时间内资源发生了改变,但是请求的url仍然不变时,浏览器已久会加载本地的资源。比如:
我们修改server.js,当我们访问 /script.js的时候,我们的打印内容更改为Script Changed

...
else if (request.url === '/script.js') {
    response.writeHead(200, {
      'Content-Type': 'text/javascript',
      'Cache-Control': 'public, max-age=31536000'
    })
    response.end('console.log("Script Changed")')
}
...

但是,网络请求依旧从本地加载了 /script.js,控制台打印了之前未修改的内容:
http协议(三)缓存头Cache-Control和缓存验证ETag、Last-Modified_第2张图片
http协议(三)缓存头Cache-Control和缓存验证ETag、Last-Modified_第3张图片
这就是为什么我们在使用webpack打包的时候,需要向文件名中增加一段由文件内容生成的hash值,当文件发生改变时,其文件名也会发生改变,所以请求链接也会发生改变,当请求链接发生改变之后,浏览器中缓存的旧的资源就不会被命中,从而加载到最新的文件。

除了改变文件名是缓存不命中,还能使用http的缓存验证来保证缓存是最新的。

3. 缓存验证

3.1 ETag

ETag HTTP响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web服务器不需要发送完整的响应。而如果内容发生了变化,使用ETag有助于防止资源的同时更新相互覆盖(“空中碰撞”)。
如果给定URL中的资源更改,则一定要生成新的Etag值。 因此Etags类似于指纹,也可能被某些服务器用于跟踪。 比较etags能快速确定此资源是否变化,但也可能被跟踪服务器永久存留。
避免“空中碰撞”
在ETag和 If-Match 头部的帮助下,您可以检测到"空中碰撞"的编辑冲突。
例如,当编辑一个共享文档时,该文档内容被哈希计算后,并在响应中放入Etag:

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

将更改保存,发布数据时,POST请求将包含有ETag值的If-Match头来检查是否为最新版本。

If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

如果哈希值不匹配,则意味着文档已经被编辑,抛出412前提条件失败错误。

缓存未更改的资源
ETag头的另一个典型用例是缓存未更改的资源。 如果用户再次访问给定的URL(设有ETag字段),显示资源过期了且不可用,客户端就发送值为ETag的If-None-Match header字段:

If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

服务器将客户端的ETag(作为If-None-Match字段的值一起发送)与其当前版本的资源的ETag进行比较,如果两个值匹配(即资源未更改),服务器将返回不带任何内容的304未修改状态,告诉客户端缓存版本是最新的。

3.2 Last-Modified

The Last-Modified 是一个响应首部,其中包含源头服务器认定的资源做出修改的日期及时间。 它通常被用作一个验证器来判断接收到的或者存储的资源是否彼此一致。由于精确度比 ETag 要低,所以这是一个备用机制。包含有 If-Modified-SinceIf-Unmodified-Since 首部的条件请求会使用这个字段。这里的If-Modified-SinceIf-None-Match 类似,常用于请求资源,当If-Modified-SinceIf-None-Match 一起出现在请求头中时,If-Modified-Since会被服务器忽略掉,除非服务器不支持 If-None-Match

注意:3.1和3.2中,在不同情况下使用不同的请求头,这里只是简单说明一下,更详细的说明可以参见MDN文档中对这几个请求头指令的说明。

3.3 使用缓存验证

注意,正常情况下,我们的ETag一般是通过对资源进行哈希计算得到的,这里为了测试,笔者使用1234567作为ETag。
修改server.js

else if (request.url === '/script.js') {
    const etag = request.headers['if-none-match']
    if (etag === '1234567') {
      response.writeHead(304, {
        'Content-Type': 'text/javascript',
        'Cache-Control': 'max-age=2000000, no-cache',
        'Etag': '1234567'
      })
      response.end('console.log("Script Changed")')
    } else {
      response.writeHead(200, {
        'Content-Type': 'text/javascript',
        'Cache-Control': 'max-age=2000000, no-cache',
        'Etag': '1234567'
      })
      response.end('console.log("Script Changed")')
    }
  }

注意,如果我们需要进行服务器缓存验证时,我们需要设置Cache-Control: no-cache,表示我们在使用本地缓存之前需要向服务器发起验证,如果没有设置no-cache,客户端将直接使用本地缓存。

现在我们清理浏览器缓存后重新访问8888端口,
第一次访问:
http协议(三)缓存头Cache-Control和缓存验证ETag、Last-Modified_第4张图片
http协议(三)缓存头Cache-Control和缓存验证ETag、Last-Modified_第5张图片
http协议(三)缓存头Cache-Control和缓存验证ETag、Last-Modified_第6张图片

第二次访问:
http协议(三)缓存头Cache-Control和缓存验证ETag、Last-Modified_第7张图片
http协议(三)缓存头Cache-Control和缓存验证ETag、Last-Modified_第8张图片
http协议(三)缓存头Cache-Control和缓存验证ETag、Last-Modified_第9张图片
通过对比这两次网络请求,我们可以发现,第二次访问8888端口时,带上了If-None-Match,服务端匹配成功后,直接返回了304状态码表示缓存可用,故客户端加载本地缓存。

现在我们更改server.js,使控制台打印内容更改为Updated Script,当我们改变了静态资源的内容时,同时需要改变ETag

...
else if (request.url === '/script.js') {
    const etag = request.headers['if-none-match']
    if (etag === '123456') {
      response.writeHead(304, {
        'Content-Type': 'text/javascript',
        'Cache-Control': 'max-age=2000000, no-cache',
        'Etag': '123456'
      })
      response.end()
    } else {
      response.writeHead(200, {
        'Content-Type': 'text/javascript',
        'Cache-Control': 'max-age=2000000, no-cache',
        'Etag': '123456'
      })
      response.end('console.log("Updated Script")')
    }
  }
...

现在再次访问8888:
在这里插入图片描述
http协议(三)缓存头Cache-Control和缓存验证ETag、Last-Modified_第10张图片
http协议(三)缓存头Cache-Control和缓存验证ETag、Last-Modified_第11张图片
可以看到,我们的请求头中还是之前未更新的ETag,和服务端的ETag不再匹配,故此时服务端重新返回最新的资源文件,状态码为200,同时更新本地的ETag(If-None-Match值。

你可能感兴趣的:(网络协议,http)