【HTTP完全注解】看了还搞不懂缓存你直接来打我

HTTP缓存

HTTP缓存是一种HTTP的性能优化机制,它是为了提高Web页面加载速度和减轻服务器负载而设计的,通过这种机制,Web浏览器或其他客户端可以存储先前获取的Web资源的副本,并在后续请求相同资源时使用这些副本,而不是再次从服务器请求。 通过使用HTTP缓存,可以减少对服务器的请求次数,这有助于减少请求的网络延迟、提高网页加载速度、降低服务器负载,以及减少网络流量。

缓存的分类

在 HTTP Caching 标准中,有两种不同类型的缓存:私有缓存共享缓存

  • 私有缓存:绑定到特定客户端的缓存——通常是浏览器缓存,由于存储的响应不与其他客户端共享,因此私有缓存可以存储该用户的个性化响应。

  • 公有缓存:位于客户端和服务器之间的缓存——通常是代理服务器缓存、CDN以及反向代理等,存储的缓存能被所有用户共享。

缓存的工作流程

HTTP缓存运行主要依赖服务端设置Cache-ControlEtagAgeExpires以及Vary等响应标头来指定相应的缓存策略,客户端通过自动携带If-Modified-SinceIf-None-Match 等请求标头来验证缓存的有效性。由于客户端的相关请求标头都是自动携带的,因此缓存的配置通常只发生在服务端,具体工作流程如下:

  • 客户端首次请求:当浏览器需要获取一个Web资源(例如HTML文档、图像、CSS文件等)时,它向服务器发送一个HTTP请求。

  • 服务端携带缓存策略返回响应:服务器收到请求后,会返回所请求资源的响应,该响应中包含了与缓存相关的信息,如:使用Cache-Control指定的缓存策略,使用ageExpires,max-age控制缓存时间,ETag控制缓存版本,vary控制缓存的位置等

  • 客户端再次请求相同资源:客户端再次请求相关资源,浏览器会首先检查本地缓存,看是否已经有了所请求资源的副本。如果有缓存且

    • 缓存有效(根据服务端设置的缓存策略来判断):浏览器可以直接从缓存中获取资源,无需再次向服务器发起请求。

    • 缓存无效:但缓存策略中具有ETagLast-Modified等验证信息,浏览器可以通过条件请求(携带If-Modified-SinceIf-None-Match 等请求标头)向服务器发起验证请求。服务器根据验证信息判断资源是否已经发生变化,如果没有变化,服务器将会返回一个304 Not Modified响应,告诉浏览器资源没有发送变化,可以使用缓存中的副本,此时客户端则会直接从缓存中获取资源。如果缓存无效或没有缓存,或者服务器返回了新的资源,浏览器会获取服务器的最新副本,并将其存储在缓存中。

私有缓存与共享缓存

上文介绍了缓存分为了私有缓存共享缓存,私有缓存是绑定到特定客户端的缓存——通常是浏览器缓存,由于存储的响应不与其他客户端共享,因此私有缓存可以存储该用户的个性化响应;而共享缓存则是位于客户端和服务器之间的缓存——通常是代理服务器缓存、CDN以及反向代理等,存储的缓存能被所有用户共享。

如何设置缓存为私有缓存还是共享缓存呢?答案是设置Cache-Control当把Cache-Control设置为private则为私有缓存,此时响应仅会存储在特定的客户端缓存中;当把Cache-Control设置为public并携带了s-maxage参数(例如:s-maxage=3600)则为共享缓存,此时响应仅会存储在客户端和服务器之间的缓存中

Cache-Control: private   //设置缓存为私有缓存
Cache-Control: public, s-maxage=3600    //设置缓存为共享缓存

值得注意的是,如果仅是把Cache-Control设置为public但不携带s-maxage参数,则表示响应可以被任何对象缓存,即使是通常不可缓存的内容,比如携带了Authorization 标头的响应通常是不能被存储的,但指定了public则可被存储。这也意味着无论是私有缓存或是共享缓存都会存储任何响应。

Cache-Control:public //意味着缓存是公开的,且可以由任何缓存存储和共享,无论是私有缓存还是公共缓存。

Cache-Control的默认行为也是比较特殊的,对于没有明确设置 Cache-Control 的响应,其行为与将Cache-Control 设置为public相似,它可以被私有缓存或是共享缓存存储,但它又与public不同,一些存在特殊标头的响应不会被存储,例如携带了Authorization 标头的响应不会被存储。

控制缓存时间

HTTP缓存提供了两种方式来控制缓存的有效时间:Expiresmax-age

Expires

Expires 响应标头使用绝对时间来指定缓存的生命周期,如下所示:

Expires: Tue, 28 Feb 2022 22:22:22 GMT  
//指定缓存2022年2月28日星期二22:22:22 过期

使用Expires 响应标头控制缓存时间存在很多问题——时间格式难以解析,并且判断缓存是否过期是根据客户端时间来计算的,这也就意味着用户可以通过更改客户端时间来使得缓存延期! 如下所示:

Expires: Tue, 28 Feb 2022 22:22:22 GMT  
//指定缓存2022年2月28日星期二22:22:22 过期
// 用户将客户端时间从2022年2月29日更改为了2022年2月27日
// 原本过期的缓存变为了有效
max-age

针对该问题,在 HTTP/1.1 中,Cache-Control 采用了 max-age来控制缓存的有效时间。max-age通过指定缓存经过多少秒后(相对于请求的时间)过期来规定其生命周期,如下所示:

Cache-Control: max-age=604800  //指定缓存经过604800秒(一周)后过期

当响应存储在共享缓存中时,还有必要返回Age响应标头,说明该缓存已在共享缓存中缓存了多长时间,此时客户端的缓存有效时间则为max-age减去Age

// 共享缓存返回如下响应
Cache-Control: max-age=604800  //指定缓存经过604800秒(一周)后过期
Age: 86400    //该缓存已在共享缓存中存储了86400秒(一天)
// 客户端缓存的有效时间则为:604800-86400=518400(六天)

s-maxagemax-age的功能完全一致,只不过指定了s-maxage的响应仅会存放在共享缓存中,并不会存放在私有缓存中,且会覆盖max-age或者Expires头。

控制缓存存放位置

缓存的存放位置本质上是基于URL的,对于不同URL的响应内容,缓存将会单独存放:

但即使是相同的URL,有时响应的内容并不总是相同,特别是使用 AcceptAccept-LanguageAccept-Encoding 等请求标头进行内容协商时。

例如,对于带有 Accept-Language: en 标头并已缓存的英语内容,不希望再对具有 Accept-Language: ja 请求标头的请求重用该缓存响应。在这种情况下,你可以通过在 Vary 标头的值中添加“Accept-Language”,根据语言单独缓存响应。

Vary: Accept-Language

这会导致缓存基于响应 URL 和 Accept-Language请求标头的组合进行键控——而不是仅仅基于响应 URL。

验证过期缓存

HTTP 缓存有一种机制,对于指定了资源更改时间(Last-Modified)或版本号(ETag )的过期的缓存并不会立即被丢弃,在客户端再次请求该内容时会先发起请求询问服务端缓存内容是否已更新,如果内容已更新则返回新的内容,如果未更新则直接拿取缓存中过期的内容使用即可。这种通过询问源服务器将陈旧的响应转换为新的响应被称为验证,有时也被称为重新验证,这种机制的存在避免了重新传输相同的资源,提高性能并减少带宽消耗。

验证有两种方式,一种是基于时间(Last-Modified/If-Modified-Since)的验证,另一种则是基于版本(ETag/If-None-Match)的验证

基于时间的验证——Last-Modified/If-Modified-Since

当客户端首次请求资源时,服务器的响应会携带Last-Modified响应标头来表明请求资源最后被修改的时间,通常情况下,服务器会根据文件系统中资源的最后修改时间自动设置这个标头。 如下所示:

HTTP/1.1 200 OK
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
Cache-Control: max-age=3600
........

客户端收到响应后会将这个时间存储起来,并把响应存入缓存中。当存储的响应过期失效,此时过期的缓存并不会立即被丢弃,当客户端再次请求资源时,它会自动在请求头中包含一个If-Modified-Since请求标头,该标头的值则为资源最后被修改的时间(上述响应中Last-Modified的值),如下所示:

GET /index.html HTTP/1.1
If-Modified-Since: Tue, 22 Feb 2022 22:00:00 GMT
......

服务器收到该请求后会比较这个时间戳与当前资源的最后修改时间。如果内容自指定时间以来没有更改,服务器将响应 304 Not Modified,告诉客户端可以直接使用过期的缓存,客户端收到此响应后会直接拿取过期缓存内容使用,并根据响应的缓存配置重新刷新过期缓存的状态。如果请求资源发生了更改,服务器将返回新的资源。

基于时间的验证虽然避免了重新传输相同的资源的问题,但它也存在诸多问题:

  • 时钟同步问题: 基于时间的验证的前提条件是服务器和客户端的时钟是同步的。如果两者的时钟存在差异,可能会导致验证失败。这种情况下,服务器认为资源已经过期,但实际上客户端的时钟比服务器快或慢,导致了不必要的资源传输。
  • 时间精度问题: 时间戳通常只有秒级别的精度,这可能导致在某些情况下无法检测到资源的真正修改。如果两次修改之间的时间间隔很短,可能无法捕捉到变化。
  • 服务器时钟回退: 如果服务器的时钟回退(例如,由于时钟同步服务的干预),可能会导致客户端认为资源已经过期,尽管实际上它仍然是最新的。
  • 不适合不稳定的资源: 对于频繁更新的资源,基于时间的验证可能会导致较高的带宽消耗,因为客户端可能会在资源实际发生变化之前多次发起验证请求。
  • 资源未被修改但最后修改时间已变: 有时资源的内容并没有实际修改,但由于某些原因,最后修改时间被更新了。这可能导致不必要的资源传输。

为了解决这些问题,HTTP缓存推出了基于版本的验证作为替代方案。

基于版本的验证——ETag/If-None-Match

当客户端首次请求资源时,服务器的响应会携带ETag响应标头来表明请求资源的版本,该标头的值是服务器生成的任意值,因此服务器可以根据他们选择的任何方式自由设置值——例如主体内容的哈希或版本号,如下所示:

HTTP/1.1 200 OK
ETag: "deadbeef"
Cache-Control: max-age=3600
........

客户端收到响应后会ETag的值存储起来,并把响应存入缓存中。当存储的响应过期失效,此时过期的缓存并不会立即被丢弃,当客户端再次请求资源时,它会自动在请求头中包含一个If-None-Match请求标头,该标头的值则为资源的版本号(上述响应中ETag的值),如下所示:

GET /index.html HTTP/1.1
If-None-Match: "deadbeef"
......

服务器收到该请求后会比较请求中的 If-None-Match 值与当前资源版本号是否相同,如果当前资源版本号与请求中的 If-None-Match 值相同,则服务器将返回 304 Not Modified,告诉客户端可以直接使用过期的缓存,客户端收到此响应后会直接拿取过期缓存内容使用,并根据响应的缓存配置重新刷新过期缓存的状态。如果当前资源版本号与请求中的 If-None-Match 值不同,则服务器将会使用 200 OK 和资源的最新版本进行响应。

强制重新验证

如果你希望即使是未过期的缓存也要重新验证,想要始终从服务器获取最新的内容,那么你可以在响应标头中添加Cache-Control: no-cacheCache-Control: max-age=0, must-revalidatemax-age=0 意味着响应立即过时,而 must-revalidate 意味着缓存一旦过时就不能在没有重新验证的情况下重用它,因此两者结合起来,语义与 no-cache 相同。

强制重新验证的流程与验证过期缓存一致,只不过一个针对未过期的缓存,一个是针对已过期的缓存。当然与验证过期缓存一样响应必须携带Last-Modified(资源更改时间)或ETag( 版本号)才能走验证流程。

强制重新验证示例

Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
ETag: deadbeef
Cache-Control: no-cache
// 或
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
ETag: deadbeef
Cache-Control: max-age=0, must-revalidate

不使用缓存

如果你不想使用缓存,不希望将响应存储在任何缓存中,可以通过在响应标头中添加Cache-Control: no-cache来实现。需要注意的是指定该指令只会阻止存储响应,但不会删除相同 URL 的任何已存储响应,也就是说如果已经为特定 URL 存储了旧响应,则返回 no-store 不会阻止旧响应被重用。

不使用缓存示例

Cache-Control: no-store

无法删除以及重新验证的缓存

对于指定了很长过期时间(max-age)且没有指定强制重新验证的缓存,基本上没有什么办法删除以及重新验证该缓存!比如下述例子:

HTTP/1.1 200 OK
Cache-Control: max-age=31536000 //指定缓存有效期为1年

该例子将缓存有效期指定为了1年,在这1年的时间里,由于缓存的存在并且没有指定强制重新验证,因此对于该资源的请求将不会到达服务器。除非用户手动执行重新加载、强制重新加载或清除历史操作,不然则无法删除以及重新验证该缓存。

这个例子告诉我们,虽然缓存减少了对服务器的访问,但这也意味着服务器失去了对该 URL 的控制。如果服务器不想失去对 URL 的控制,你应该添加 no-cache,以便服务器始终接收请求并发送预期的响应。

请求折叠

共享缓存主要位于源服务器之前,旨在减少到源服务器的流量。但共享缓存存在一个问题——多个相同的请求同时到达共享缓存时,共享缓存只会把其中一个请求转发到源服务器,并且共享缓存收到源服务器响应后会将响应重用于所有请求,这称为请求折叠

当请求同时到达共享缓存会发生请求折叠,即使响应中给出了 max-age=0no-cache,它也会被重用。因此如果响应是针对特定用户个性化的,并且你不希望它在折叠中共享,应该使用私有缓存,在响应中添加 private 指令。

可被缓存的请求方法

HTTP协议定义了一些请求方法,其中一些方法通常可以被缓存。 可被缓存的请求方法是那些在满足特定条件下,可以被缓存代理服务器(如HTTP缓存)缓存的方法。以下是常见的能否被缓存的HTTP请求方法:

  • GETHEADOPTIONS方法: GETHEADOPTIONS方法通常被认为是可缓存的,因为它们是幂等且安全的,而且不会改变服务器状态。这意味着代理服务器可以缓存它们的响应,以提高性能并减轻服务器负载。
  • POSTPATCH方法:POSTPATCH方法的响应通常不会被缓存 ,因为它们通常用于向服务器提交数据,可能会改变服务器状态。然而,如果响应中指定了有效期(例如,通过Cache-Control头部)并设置了Content-Location头部,那么它们的响应可以被缓存。这种情况下,缓存代理服务器可以将响应缓存,并在之后的请求中使用。但这种情况下的缓存行为在实际应用中相对较少见,并且有些浏览器并不支持(例如Firefox 就不支持它Firefox bug 109553),因此通常不鼓励缓存POST请求的响应。
  • PUTDELETE方法: PUTDELETE 方法的响应通常不会被缓存,即使设置了有效期(通过Cache-Control头部)和Content-Location标头。这是因为这两个请求方法通常用于对资源进行修改或删除,可能会改变服务器的状态,因此响应不适合被缓存。
  • CONNECT 方法:CONNECT 方法的响应通常不会被缓存 ,即使设置了有效期(通过Cache-Control头部)和Content-Location标头。CONNECT 方法用于建立网络连接,通常用于代理服务器。由于 CONNECT 方法的目的是在客户端和目标服务器之间建立连接,而不是获取资源,所以 CONNECT 方法的响应通常不会被缓存。代理服务器通常不会缓存 CONNECT 方法的响应,因为它们不包含可被缓存的资源数据。
  • TRACE 方法:TRACE 方法的响应通常不会被缓存 ,即使设置了有效期(通过Cache-Control头部)和Content-Location标头。TRACE 方法用于在目标服务器上执行一个诊断测试,它返回由服务器收到的请求的副本。由于 TRACE 方法的主要目的是用于诊断和调试,而不是对服务器上的资源进行修改,因此 TRACE 方法的响应通常不会被缓存。代理服务器通常不会缓存 TRACE 方法的响应,因为这些响应只包含请求的副本,不包含可被缓存的资源数据。
能否缓存请求方法
可被缓存GET、HEAD、OPTIONS
可被缓存,但不鼓励且支持少POST、PATCH
不可被缓存PUT、DELETE、CONNECT、TRACE

相关标头

Cache-Control 通用标头

通用标头Cache-Control 通常是由服务器在响应头中设置,配置缓存策略以指导客户端和中间缓存服务器如何处理响应的缓存。然而,在某些情况下,客户端也可以在请求头中使用 Cache-Control 标头,以向服务器传达有关请求的缓存期望。

参数

  • <cache-model> 可选

    指定缓存的模式,可选的属性值有:

    • public:表明响应可以被任何对象(无论是私有缓存还是共享缓存)存储,即使是通常不可缓存的内容,比如携带了Authorization 标头的响应通常是不能被存储的,但指定了public则可被存储。注意该指令并不是指定缓存为共享缓存
    • private:指定缓存为私有缓存,表明响应只能被客户端缓存存储
    • no-cache:指定缓存强制重新验证,也就是说即使是未过期的缓存也要重新验证,始终从服务器获取最新的内容。注意该指令不会阻止响应的存储,而是阻止在没有重新验证的情况下重用响应
    • no-store:指定不使用缓存,也就是说不存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。注意该指令只会阻止存储响应,但不会删除相同 URL 的任何已存储响应,也就是说如果已经为特定 URL 存储了旧响应,则返回 no-store 不会阻止旧响应被重用
  • max-age=<seconds> 可选

    指定缓存经过多少秒后(相对于请求的时间)过期

  • s-maxage=<seconds> 可选

    指定缓存经过多少秒后(相对于请求的时间)过期,s-maxagemax-age的功能完全一致,只不过s-maxage仅适用于共享缓存 ,私有缓存会忽略它。

  • max-stale=<seconds> 可选

    表明客户端愿意接收一个已经过期的资源。可以设置一个可选的秒数,表示响应不能已经过时超过该给定的时间。

  • min-fresh=<seconds> 可选

    表示客户端希望获取一个能在指定的秒数内保持其最新状态的响应。

  • stale-while-revalidate=<seconds> 可选 实验性

    表明客户端愿意接受陈旧的响应,同时在后台异步检查新的响应。秒值指示客户愿意接受陈旧响应的时间长度。

  • stale-if-error=<seconds> 可选 实验性

    表示如果新的检查失败,则客户愿意接受陈旧的响应。秒数值表示客户在初始到期后愿意接受陈旧响应的时间。

  • must-revalidate 可选

    一旦缓存过期(比如已经超过max-age),在成功向原始服务器验证之前,不是使用该缓存。

  • proxy-revalidate 可选

    与 must-revalidate 作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。

  • immutable 可选

    表示响应的内容永远不会发生更改,请谨慎使用该头,因为这可能会导致无法删除以及重新验证缓存。

  • no-transform 可选

    不得对资源进行转换或转变。Content-EncodingContent-RangeContent-Type等 HTTP 头不能由代理修改。例如,非透明代理或者如Google’s Light Mode可能对图像格式进行转换,以便节省缓存空间或者减少缓慢链路上的流量。no-transform指令不允许这样做。

  • only-if-cached 可选

    表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝。

示例

Cache-Control: no-store  //不使用缓存
Cache-Control:public, s-maxage=31536000 // 仅允许共享缓存存储响应
Cache-Control: no-cache //指定强制重新验证

Expires 响应标头

响应标头Expires 通过使用绝对时间来指定缓存的的过期时间

参数

该响应标头并无其他参数

取值

  • <date>

    指定一个绝对时间表明缓存的过期时间

示例

Expires: Wed, 21 Oct 2015 07:28:00 GMT

Vary 响应标头

响应标头Vary通过指定一系列的请求标头,使得缓存的存放位置不再仅基于响应URL,而是与指定的请求标头进行组合键控。

参数

该响应标头并无其他参数

取值

  • *

    指定与任何请求头字段进行组合键控。

  • <heder-name,heder-name, heder-name,…>

    请求标头列表,用逗号(', ')隔开,指定与相关请求标头进行组合键控。

示例

Vary: Accept-Language
Vary: Accept-Language,User-Agent

Last-Modified 响应标头

响应标头Last-Modified指定了响应的资源最后被修改的时间,通常情况下,服务器会根据文件系统中资源的最后修改时间自动设置这个标头。

参数

该响应标头并无其他参数。

取值

  • <date>

    一个绝对时间,指定响应的资源最后被修改的时间。

示例

Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

If-Modified-Since 请求标头

请求标头If-Modified-Since指定了一个绝对时间,表示在缓存中过期的响应内容的最后修改时间,携带该标头希望服务器检查该响应是否在指定的修改时间之后发生过更改,能否继续使用该过期的内容。 该头通常是由浏览器在发起 GET 请求时自动携带的,其值为缓存的过期响应中 Last-Modified 的值。

参数

该请求标头并无其他参数。

取值

  • <date>

    一个绝对时间,表示在缓存中过期的响应内容的最后修改时间。

示例

If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT

ETag 响应标头

响应标头ETag指定了一个版本号,表明响应的资源的版本,该标头的值是服务器生成的任意值,因此服务器可以根据他们选择的任何方式自由设置值——例如主体内容的哈希或版本号。

参数

  • W/ 可选

    'W/'(大小写敏感) 表示使用弱验证器。弱验证器很容易生成,但不利于比较。强验证器是比较的理想选择,但很难有效地生成。相同资源的两个弱Etag值可能语义等同,但不是每个字节都相同。

  • <etag_value>

    指定一个版本号,没有明确指定生成 ETag 值的方法。通常,使用内容的散列,最后修改时间戳的哈希值,或简单地使用版本号。例如,MDN 使用 wiki 内容的十六进制数字的哈希值。

示例

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
ETag: W/"0815"

If-None-Match 请求标头

请求标头If-None-Match指定了一个版本号,携带该标头希望服务器检查资源是否与给定版本号匹配,如果匹配,则返回状态码 304 Not Modified,表示资源没有变化,客户端可以继续使用缓存过期的版本。 该头通常是由浏览器在发起 GET 请求时自动携带的,其值为缓存的过期响应中 ETag 的值。

参数

该请求标头并无其他参数。

取值

  • W/ 可选

    'W/'(大小写敏感) 表示使用弱验证器。弱验证器很容易生成,但不利于比较。强验证器是比较的理想选择,但很难有效地生成。相同资源的两个弱Etag值可能语义等同,但不是每个字节都相同。

  • <etag_value>

    指定一个版本号,表示希望服务器检查资源是否与给定版本号匹配。

  • * 可选

    星号是一个特殊值,可以代表任意资源。它只用在进行资源上传时,通常是采用 PUT 方法,来检测拥有相同识别 ID 的资源是否已经上传过了。

示例

If-None-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"
If-None-Match: W/"67ab43", "54ed21", "7892dd"
If-None-Match: *

常见的缓存模式

  • 启发式缓存:当响应没有给出 Cache-Control,但响应中包含了Last-Modified以及Expires标头,响应也会被存储和重用,缓存的时间取决于实现,但规范建议存储后大约 10%的时间,这称为启发式缓存

  • 零缓存:顾名思义是一种禁止对资源进行缓存的策略,通过使用Cache-Control: no-store标头,指示客户端和所有中间缓存不存储有关客户端请求或服务器响应的任何内容,通常用于敏感数据或对即时性要求非常高的场景。

  • 协商缓存:协商缓存是一种通过与服务器协商确定是否需要重新获取资源的缓存策略,协商缓存分为两种:

    • 版本协商:使用ETagIf-None-Match等标头进行版本协商缓存。
    • 时间协商:使用Last-ModifiedIf-Modified-Since等标头进行时间协商缓存。

    当客户端发送请求时,服务器会根据这些标头的值判断是否需要返回新的内容。协商缓存通常用于资源可能发生变化但并不频繁的情况下,主要用于主资源的缓存。

  • 缓存破坏:缓存破坏是一种通过指定缓存永久有效,然后修改资源的URL来破坏缓存确保客户端获取最新版本的方法。在每次资源发生变化时,通过更改资源的URL来触发缓存破坏。例如,在资源的URL中添加时间戳、版本号或随机数,以确保每次更改都会导致新的URL,从而绕过缓存。破坏缓存适用于JS、CSS、图像、视频等静态不可变的资源。



点击链接或微信搜索“汪啊汪” ,关注我及时掌握最新动态

完整手册可关注该仓库,如有帮助,麻烦给个✨

该站点也会同步更新,已满足PWA,您可安装到桌面随时访问

转载需要经过本人同意,并标明出处!

本文由mdnice多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/279552.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java学习笔记21——使用JDBC访问MySQL数据库

JDBC&#xff08;Java Database Connectivity&#xff0c;Java数据库连接&#xff09;是应用程序编程借口&#xff08;API&#xff09;&#xff0c;描述了一套访问关系数据库的标准Java类库。可以在程序中使用这些API&#xff0c;连接到关系数据库&#xff0c;执行SQL语句&…

【技术栈】Redis 中的事务及持久化方式

SueWakeup 个人主页&#xff1a; SueWakeup 系列专栏&#xff1a;学习技术栈 个性签名&#xff1a;保留赤子之心也许是种幸运吧 本文封面由 凯楠&#x1f4f8; 友情提供 目录 相关传送门 1. Redis 中的事务 2. Redis 持久化 2.1 RDB 方式 2.1.1 RDB手动 2.1.2 RDB自动 2.…

跨越时空的纽带:探索Facebook如何连接人与人

引言 Facebook作为全球最大的社交媒体平台之一&#xff0c;已经成为了人们日常生活中不可或缺的一部分。它不仅仅是一个社交网络&#xff0c;更是连接人与人、人与世界的纽带。在这篇文章中&#xff0c;我们将深入探讨Facebook如何跨越时空&#xff0c;连接人与人之间的关系&a…

Flutter 初始WidgetState 简单应用案例分析

本系列文章主要整理Flutter的知识汇总&#xff0c;由浅入深&#xff0c;从Widget的搭建到其中的原理。本文还是围绕Widget在开发中应用和理解。 关于Flutter环境配置和首次创建可以参考前面文章。链接如下&#xff1a; Flutter 安装部署与认识Dart语言 Flutter 使用AndroidS…

Spring Cloud 整合 GateWay

目录 第一章 微服务架构图 第二章 Spring Cloud整合Nacos集群 第三章 Spring Cloud GateWay 第四章 Spring Cloud Alibaba 整合Sentinel 第五章 Spring Cloud Alibaba 整合SkyWalking链路跟踪 第六章 Spring Cloud Alibaba 整合Seata分布式事务 第七章 Spring Cloud 集成Auth用…

多模匹配算法AC算法和单模匹配算法BM

多模匹配算法之AC算法详解 算法概述  Aho-Corasick算法 - 这是一种字典匹配算法,它用于在输入文本中查找字典中的字符串。时间复杂度是线性的。该算法应用有限自动机巧妙地将字符比较转化为了状态转移。  该算法的基本思想 − 在预处理阶段,AC自动机算法建立…

Springboot通过注解+切面实现接口权限校验

Springboot通过注解&#xff0b;切面实现接口权限校验 主要说一下在对接口请求时&#xff0c;如何用注解切面去拦截校验当前登录用户是否有访问权限 1.首先创建注解 HasPermission &#xff0c;跟普通注解创建方式基本一致 Retention(RetentionPolicy.RUNTIME) Target(Element…

小火星露谷管理器 报错:“你似乎没有安装Edge的webview2”

错误 解决办法 你可以到这个地方下载安装webview2 https://developer.microsoft.com/zh-cn/microsoft-edge/webview2/?formMT00IS

2024年亚洲图像处理趋势会议(ATIP 2024)即将召开!

2024年亚洲图像处理趋势会议&#xff08;简称&#xff1a;ATIP 2024&#xff09;将于2024年6月21日至23日在英国伦敦举行。在会议上我们将与相关领域的研究人员和知名专业人士共同讨论关于图像处理学科的最新研究方向及进展&#xff0c;评估当前最先进的技术和未来研究的关键领…

Tomcat(Win+Linux)安装教程

Windows环境安装 Tomcat安装及配置教程主要分为四步&#xff1a; 步骤一&#xff1a;确认自己是否已 安装JDK&#x1f50d; 步骤二&#xff1a;下载安装Tomcat 步骤三&#xff1a;Tomcat配置环境变量 步骤四&#xff1a;验证Tomcat配置是否成功 OK&#xff0c;我们开始&#x…

数据库基本介绍及编译安装mysql

目录 数据库介绍 数据库类型 数据库管理系统&#xff08;DBMS&#xff09; 数据库系统 DBMS的工作模式 关系型数据库的优缺点 编译安装mysql 数据库介绍 数据&#xff1a;描述事物的的符号纪录称为数据&#xff08;Data&#xff09; 表&#xff1a;以行和列的形式组成…

python大学生健身爱好者交流网站flask-django-nodejs-php

任何系统都要遵循系统设计的基本流程&#xff0c;本系统也不例外&#xff0c;同样需要经过市场调研&#xff0c;需求分析&#xff0c;概要设计&#xff0c;详细设计&#xff0c;编码&#xff0c;测试这些步骤&#xff0c;基于python技术、django/flask框架、B/S机构、Mysql数据…

【No.13】蓝桥杯二分查找|整数二分|实数二分|跳石头|M次方根|分巧克力(C++)

二分查找算法 知识点 二分查找原理讲解在单调递增序列 a 中查找 x 或 x 的后继在单调递增序列 a 中查找 x 或 x 的前驱 二分查找算法讲解 枚举查找即顺序查找&#xff0c; 实现原理是逐个比较数组 a[0:n-1] 中的元素&#xff0c;直到找到元素 x 或搜索整个数组后确定 x 不在…

linux网络服务学习(1):nfs

1.什么是nfs NFS&#xff1a;网络文件系统。 *让客户端通过网络访问服务器磁盘中的数据&#xff0c;是一种在linux系统间磁盘文件共享的方法。 *nfs客户端可以把远端nfs服务器的目录挂载到本地。 *nfs服务器一般用来共享视频、图片等静态数据。一般是作为被读取的对象&…

国内git最新版本下载链接2.44

git官网地址:Git - Downloading Package (git-scm.com) 蓝奏云: ​​​​​​gGit-2.44.0-64-bit.exe - 蓝奏云 git仓库地址:git/git: Git Source Code Mirror - This is a publish-only repository but pull requests can be turned into patches to the mailing list via …

算法笔记p251队列循环队列

目录 队列循环队列循环队列的定义初始化判空判满入队出队获取队列内元素的个数取队首元素取队尾元素 队列 队列是一种先进先出的数据结构&#xff0c;总是从队尾加入元素&#xff0c;从队首移除元素&#xff0c;满足先进先出的原则。队列的常用操作包括获取队列内元素的个数&a…

Typecho博客后台登陆界面美化

登录界面&#xff1a; 食用方法&#xff1a; 备份 admin 目录 压缩包内容上传到 admin 目录内。 结构:网站根目录 /admin/login.php 结构:网站根目录 /admin/style 修改 login.php 第35行&#xff0c;把“季春二九管理后台”替换成自己的信息 清理缓存&#xff0c;开始体验新的…

释放创造力,Nik Collection 6 by DxO 点亮你的视觉世界

在数字摄影时代&#xff0c;后期处理是提升摄影作品品质的重要环节。而Nik Collection 6 by DxO作为一套优秀的滤镜插件套装&#xff0c;不仅为摄影师提供了丰富的后期处理工具&#xff0c;更让他们能够释放无限的创造力&#xff0c;打造出惊艳的视觉作品。 Nik Collection 6 …

虚拟游戏理财 - 华为OD统一考试(C卷)

OD统一考试(C卷) 分值: 100分 题解: Java / Python / C++ 题目描述 在一款虚拟游戏中生活,你必须进行投资以增强在虚拟游戏中的资产以免被淘汰出局。 现有一家Bank,它提供有若干理财产品m,风险及投资回报不同,你有N (元)进行投资,能接受的总风,险值为X。 你要在可接…

WanAndroid(鸿蒙版)开发的第六篇

前言 DevEco Studio版本&#xff1a;4.0.0.600 WanAndroid的API链接&#xff1a;玩Android 开放API-玩Android - wanandroid.com 其他篇文章参考&#xff1a; 1、WanAndroid(鸿蒙版)开发的第一篇 2、WanAndroid(鸿蒙版)开发的第二篇 3、WanAndroid(鸿蒙版)开发的第三篇 …