HTTP Cache兔子都能懂的浏览器缓存

Caching & Networking

Caching的来源

现在的网页架构已经复杂了许多,大部分资料的抓取过程都会产生许多request请求去取得response,而当这样的请求一多,就会对服务器的性能造成影响,所以Caching应运而生。

不论是前端还是后端,甚至是更底层的开发,都会有Caching的机制来提升效能...

Caching的核心概念其实就是提供一个额外的存储空间,将可能需要透过请求得到的资料放在里面,当之后需要再请求资料的时候,先问问Caching空间有没有你需要的资料,有的话很好,那就不需要再发出request!

HTTP Caching是避免浏览器向服务器发送不必要请求的一道防线,要启用HTTP Caching需要服务器端与浏览器端事先经过协商,浏览器与服务器透过在HTTP request与response header 带入一些资讯带协商缓存的机制,例如:

  • 服务器告诉浏览器需不需要缓存这个回传的资源?
  • 或是判断现在缓存的资料是不是已经过期?需要重新到server抓取...

image.png

Caching什么时候用?

  • 很常被用
  • 资料不常变动

public Caching & private Caching

凌驾在所有Caching 种类之上还可以再分为public Caching和private Caching。

  • public Caching是指服务器上存的response能给好几个不同的client 服务,

例如请求回传时经过proxy server上的caching可以提供给多个使用者使用。

  • 而私有服务器相对只会服务一个请求者,也就是只有这台电脑的使用者能使用Caching的资源。

Expires, Cache-control

Expires 绝对时间

Expires是一个旧版本(HTTP1.0)的方式,服务器在response header 加入Expires字段 。 后续有对相同资源的请求时会去检查使用者现在的时间是否有超过Expires 中指定的过期时间,如果没有超过,就会直接回传缓存空间中的资料;如果时间超过就会发起HTTP request跟服务器再拿一次。

不过这会发生一个问题,因为是根据使用者电脑的时间来决定,如果使用者的电脑时间被调到未来的时间点,那缓存就被会认定为失效。

Expires: Wed, 21 Oct 2022 07:28:00 GM
复制代码

Cache-control 相对时间

后来出现了新的header:Cache-control来解决这个问题,如果response header 中同时出现Expires 与Cache-control,将会采用Cache-control的设定,比较旧版的Expires 将会被浏览器忽略。

# 某个 http response header

Cache-Control: max-age=60
复制代码

这代表使用者在收到这个 response 的 60 秒内,如果再对相同资源发出请求,就会得到缓存的版本,如果超过 60 秒后才对这个资源发出请求,则会发出一个新的 network request。

而 Cache-Control 这个 header 并不是只有 max-age 可以使用,根据 MDN 文件,其实还有非常多属性可以搭配,今天就介绍一些较常见的属性就好 。

Cache-control:no-store 永远不用缓存

并不是所有内容我们都希望要被缓存,例如关于使用者资讯的一些比较私密的资料或是经常变动的内容,我们会希望不要保留在client side,而是每次都到server去抓取,这时候可以在response header 中加入

Cache-Control: no-store 

这个字段告诉浏览器「请不要帮我对这个资源做任何缓存」。
复制代码

Cache-Control:no-cache 永远检查缓存

当每一次重新造访同样一个页面的时候,无论 A 网站有没有更新,A 网站都会传来「整份新资料」,假设 index.html 有 100 kb 好了,造访了十次,累积的流量就是 1000kb。

B 网站的话,我们假设前九次网站都没有更新,一直到第十次才更新。所以前九次 Server 只会回传 Status code 304,这个封包大小我们姑且算作 1kb 好了。第十次因为有新的档案,会是 100kb。那十次加起来的流量就是 9 + 100 = 109 kb

这就是 no-store 跟 no-cache 的差异,永远不用缓存跟永远检查缓存。

Cache-control:s-maxage

s-maxage会覆写max-age或者Expires header,不过只对共用的缓存软件生效(如Nginx)。设置为私有缓存时浏览器会无视这个指令。

Cache-Control: private || public

private代表此 response 只可以被浏览器储存起来。

private代表此 response 可以被任何缓存软件储存起来,例如 reverse proxy 的缓存、浏览器的缓存。

缓存过期了之后怎么办?

这会出现两种情况:

  • 缓存过期了,资料没有任何改变
  • 缓存过期了,资料改变了

Last-Modified & If-Modified-Since

Last-Modified & If-Modified-Since是旧版本(HTPP1.0)浏览器采用的缓存方式。

  • Last-Modified: 出现在服务器返回给浏览器的response header里,告诉浏览器这个资料上次修改是什么时候
  • If-Modified-Since:出现在浏览器发出请求的request header里,用来跟服务器确认资料在某个时间点后是不是有经过修改。

而他们的使用流程是这样的。举例来说,当你对某个网站的logo第一次发出网络请求时,服务器的response header可能会带入一下资讯

# HTTP request header

GET /logo.png
If-Modified-Since: Sat, 11 Sep 2021 07:28:00 GMT
复制代码

浏览器收到 response 后会把 Logo 缓存起来,设定过期时限是一年,并记录档案最后修改日期是 Sat, 11 Sep 2021 07:28:00

在这一年内,每当有对这个logo的请求时,浏览器就会回传缓存的版本,而当超过一年,原本缓存的时效过期,浏览器就会带上之前记录的更改时间,去问服务器从这个时间点后,这个资料是否有被更改过。

# HTTP request header

GET /logo.png
If-Modified-Since: Sat, 11 Sep 2021 07:28:00 GMT
复制代码

假设服务器认定文件被更改过,那就会回传一个新的文件给浏览器,之后的缓存流程就会按照前面提过的重新跑一次。但是如果服务器查询了一下发现这个文件从浏览器提供的时间之后就都没有修改过,就会告诉使用者【你可以继续使用缓存的版本】,服务器会回传一个304的status code ,代表not modified,浏览器收到304后就会知道可以继续沿用之前的缓存。

不过Last-Modified & If-Modified-Since的方法会产生一个问题,就是它是根据文件的编辑时间来做判断的,如果今天文件只是被打开便重新保存,内容完全没有变动,因为编辑时间改动,服务器会认为这个文件被改变。更好的做法应该是依据文件的内容有没有更新来决定是否要重新抓取资料,接下来的Etag & If-None-Match(HTTP1.1)解决了这个问题。

Etag & If-None-Match

不知道大家是否听过JWTtoken这个身份验证机制,他的概念是利用hash函数去判断token有没有经过人为改变来判断是不是合法的token。

Etag也是利用类似的概念,相同内容的资料会产生独一无二的Etag值。

使用Etag的流程变成server在response header会带入Etag让浏览器存起来

Cache-Control: max-age=86400
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
复制代码

等到缓存失效后,使用者又请求了相同资源,浏览器就会在 request header加上这个资讯

If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
复制代码

服务器会去检验浏览器带来的Etag 与最新的资料是否相符。如果相同,代表资料内容没有改变,浏览器可以继续使用缓存的内容,如果不符,则会重新抓取。

image.png

参考资料

MDN

http-cache

小声说

点赞是不要钱的,但作者会开心好几天~

如果喜欢的话请帮忙按个赞喔,谢谢啦!

猜你喜欢

转载自juejin.im/post/7124571292283437093