HTTP缓存

243 阅读7分钟

重用已获取的资源能够有效的提升网站与应用的性能。Web缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间。借助 HTTP 缓存,Web 站点变得更具有响应性。

缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。当web缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。这样带来的好处有:缓解服务器端压力,提升性能(获取资源的耗时更短了)。对于网站来说,缓存是达到高性能的重要组成部分。缓存需要合理配置,因为并不是所有资源都是永久不变的:重要的是对一个资源的缓存应截止到其下一次发生改变(即不能缓存过期的资源)。

缓存操作的目标

虽然 HTTP 缓存不是必须的,但重用缓存的资源通常是必要的。然而常见的 HTTP 缓存只能存储 GET 响应,对于其他类型的响应则无能为力。缓存的关键主要包括request method和目标URI(一般只有GET请求才会被缓存)。 普遍的缓存案例:

  • 一个检索请求的成功响应: 对于GET请求,响应状态码为:200,则表示为成功。一个包含例如HTML文档,图片,或者文件的响应。
  • 永久重定向: 响应状态码:301。
  • 错误响应: 响应状态码:404 的一个页面。
  • 不完全的响应: 响应状态码 206,只返回局部的信息。
  • 除了 GET 请求外,如果匹配到作为一个已被定义的cache键名的响应。

缓存控制

Cache-control 头

HTTP/1.1定义的 Cache-Control 头用来区分对缓存机制的支持情况,请求头和响应头都支持这个属性。通过它提供的不同的值来定义缓存策略。

禁止进行缓存

缓存中不得存储任何关于客户端请求和服务端响应的内容。每次由客户端发起的请求都会下载完整的响应内容。

Cache-Control: no-store

强制确认缓存

如下头部定义,此方式下,每次有请求发出时,缓存会将此请求发到服务器(译者注:该请求应该会带有与本地缓存相关的验证字段),服务器端会验证请求中所描述的缓存是否过期,若未过期(注:实际就是返回304),则缓存才使用本地缓存副本。

Cache-Control: no-cache

缓存过期机制

过期机制中,最重要的指令是 "max-age=seconds",表示资源能够被缓存(保持新鲜)的最大时间。相对Expires而,max-age是距离请求发起的时间的秒数。针对应用中那些不会改变的文件,通常可以手动设置一定的时长以保证缓存有效,例如图片、css、js等静态资源。

response.setHeader('Cache-Control','max-age=31536000')   
<!--或者使用Expires,Expires 响应头包含日期/时间, 即在此时间之后,响应过期-->
response.setHeader('Expires','Fri, 13 Dec 2019 11:07:50 GMT') //Expires响应头实例
//Expires所设置的时间是相对于服务器的时间,若客户端的本地时间与服务器时间不一致,就会出现问题,不靠谱。
<!--如果在Cache-Control响应头设置了 "max-age" 或者 "s-max-age" 指令,那么 Expires 头会被忽略-->

新鲜度

理论上来讲,当一个资源被缓存存储后,该资源应该可以被永久存储在缓存中。由于缓存只有有限的空间用于存储资源副本,所以缓存会定期地将一些副本删除,这个过程叫做缓存驱逐。另一方面,当服务器上面的资源进行了更新,那么缓存中的对应资源也应该被更新,由于HTTP是C/S模式的协议,服务器更新一个资源时,不可能直接通知客户端更新缓存,所以双方必须为该资源约定一个过期时间,在该过期时间之前,该资源(缓存副本)就是新鲜的,当过了过期时间后,该资源(缓存副本)则变为陈旧的。驱逐算法用于将陈旧的资源(缓存副本)替换为新鲜的,注意,一个陈旧的资源(缓存副本)是不会直接被清除或忽略的,当客户端发起一个请求时,缓存检索到已有一个对应的陈旧资源(缓存副本),则缓存会先将此请求附加一个If-None-Match头,然后发给目标服务器,以此来检查该资源副本是否是依然还是算新鲜的,若服务器返回了 304 (Not Modified)(该响应不会有带有实体信息),则表示此资源副本是新鲜的,这样一来,可以节省一些带宽。若服务器通过 If-None-Match 或 If-Modified-Since判断后发现已过期,那么会带有该资源的实体内容返回。

ETag

ETag HTTP响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web服务器不需要发送完整的响应。而如果内容发生了变化,使用ETag有助于防止资源的同时更新相互覆盖(“空中碰撞”)。例如可以使用MD5信息摘要算法,一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。可以使用MD5来设置ETag响应头的特定标识符。

ETag头的一个典型用例是缓存未更改的资源。 如果用户再次访问给定的URL时,客户端会带上If-None-Match header字段(该字段存了ETag的值),服务器将客户端的ETag值与其当前版本的资源的ETag进行比较,如果两个值匹配(即资源未更改),服务器将返回不带任何内容的304未修改状态,告诉客户端缓存版本可用(新鲜)。

let string = fs.readFileSync('./main.js', 'utf8')
let fileMd5 = md5(string)
response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
response.setHeader('ETag',fileMd5)
console.log(request.headers)
if(request.headers['if-none-match'] === fileMd5){
  response.statusCode = 304  //返回304,告诉浏览器直接使用缓存内容就行,该缓存内容仍然是新鲜的
}else{
  response.statusCode = 200
  response.write(string)
}
response.end()

ETag 与 Cache-Control的区别

  • ETag仍然会发起请求,服务器常返回304,还是会返回响应头,只不过响应体内容为空,这样可以避免资源的再次下载,告诉浏览器使用缓存内容即可。
  • Cache-Control在所设置的时间段内,再次发起请求时浏览器会拦截该请求,此后请求同一个url时将会使用浏览器或者硬盘中的缓存内容。

Last-Modified 响应头可以作为一种弱校验器。说它弱是因为它只能精确到一秒。如果响应头里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。

当向服务端发起缓存校验的请求时,服务端会返回 200 ok表示返回正常的结果或者 304 (Not Modified)(不返回body)表示浏览器可以使用本地缓存文件。304的响应头也可以同时更新缓存文档的过期时间。