前言
HTTP Cache 是一种提高 Web 页面资源加载效率的技术手段。具体实现为:Web 资源经过的各级系统通过存储 Web 文档,视频,网页,图片等资源的副本。在指定的条件下,前端(如浏览器)直接加载存储的副本,而无需到服务器上请求,以此减少资源请求的滞后性。
相关规范:RFC 7234
HTTP Cache
HTTP Cache 的实现主要和 HTTP Header 中的几个字段有关,我们一起了解一下。
Cache-Control
用于指定缓存在整个请求/响应链中必须服从的指令。这些指令指定用于指导缓存对于请求或响应内容的具体行为。缓存指令是单向的,在请求中存在一个指令并不意味着响应中也会存在同一个指令,请求中的指令一般是客户端设置的,响应中的指令一般是服务端设置的。
一般来说常用的 Cache-Control 指令包括:
指令 | 效果 |
---|---|
no-store | 不对资源做任何的缓存 |
no-cache | 必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。no-cache 会发起通信通过 E-tag 字段向服务器验证缓存的响应,如果资源未被更改,那么加载副本中的资源。 |
max-age=30 | 缓存的内容将在指定的秒数(例子是 30 秒)后失效, 这个选项只在 HTTP 1.1 及以上可用。当和 Last-Modified 一起使用时, max-age 的优先级更高。 |
public | 所有内容都将被缓存(客户端和代理服务器都可缓存),并且该缓存是共享的。也就是如果新打开一个一模一样的网页窗口,会直接使用前一个窗口的缓存。 |
private | 内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)。如果新打开一个一模一样的网页窗口,不允许使用前一个窗口的缓存,而是向服务器请求资源。 |
must-revalidation/proxy-revalidation | 如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证 |
下面是在不同的情况下,浏览器是将请求发往服务器还是直接使用缓存中的内容:
指令 | 打开一个新的浏览器窗口 | 在原窗口敲击 Enter | 刷新 | 单击 Back 按钮 |
---|---|---|---|---|
no-store | 发送请求到 Server,不会比较 Etag | 和前面一致 | 和前面一致 | 和前面一致 |
no-cache | 发送请求到 Server,会比较 Etag | 和前面一致 | 和前面一致 | 和前面一致 |
max-age=30 | 在 30 秒后,浏览器重新发送请求到服务器 | 在 30 秒后,浏览器重新发送请求到服务器 | 浏览器重新发送请求到服务器 | 在 30 秒后,浏览器重新发送请求到服务器 |
public | 浏览器呈现来自缓存的页面 | 浏览器呈现来自缓存的页面 | 浏览器重新发送请求到服务器 | 浏览器呈现来自缓存的页面 |
public | 浏览器呈现来自缓存的页面 | 浏览器呈现来自缓存的页面 | 浏览器重新发送请求到服务器 | 浏览器呈现来自缓存的页面 |
must-revalidation/proxy-revalidation | 浏览器重新发送请求到服务器 | 第一次,浏览器重新发送请求到服务器;此后,浏览器呈现来自缓存的页面 | 浏览器重新发送请求到服务器 | 浏览器呈现来自缓存的页面 |
Expires
Expires 字段存储了一个 GMT 格式的时间戳,在改时间戳之后的响应会被认为失效。失效的缓存条目通常不会被缓存(无论是代理缓存还是用户代理缓存)返回,除非通过了原始服务器(或者拥有该实体的最新副本的中介缓存)验证。(注意:Cache-Control 中的 max-age 和 s-maxage 指令将覆盖 Expires。)
Expires 字段中时间戳格式为 Expires: Sun, 08 Nov 2009 03:37:26 GMT
,如果请求资源时的日期在给定的日期之前,则认为该资源没有失效直接缓存中提取出来使用,反之则认为该资源失效。
E-Tag
ETag 响应头部字段值是一个实体标记,它提供一个 “不透明” 的缓存验证器。这可能在以下几种情况下提供更可靠的验证:不方便存储修改日期;HTTP 日期值的 one-second (主要是可能会有一秒内多次请求的情况)解决方案不够用;或者原始服务器希望避免由于使用修改日期而导致的某些冲突。
配置 HTTP Cache
在 Spring Boot 的 ResourceProperties 类中提供了关于 HTTP Cache 的配置。使用外部配置:
1
2
3
4
5
# 开启 chain cache
spring.resources.chain.cache=true
# http 缓存过期时间
spring.resources.cachePeriod=60
此外还可以通过实现 WebMvcConfigurer
接口的 addResourceHandlers
方法通过代码进行 HTTP 缓存的配置:
1
2
3
4
5
6
7
8
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl
.maxAge(10, TimeUnit.MINUTES)
.cachePrivate());
}
我们也可以定义自己的 HTTP Cache 配置类,实现个性化的配置:
配置类:ResourcesConfiguration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package cn.keyou.web.common.resources;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 前端页面资源配置
*/
@Configuration
@ConfigurationProperties("service.resources")
public class ResourcesConfiguration implements WebMvcConfigurer {
private String[] handler = new String[]{"/**"};
private String[] locations = new String[]{"classpath:/resources/", "classpath:/META-INF/resources/", "classpath:/static/", "classpath:/public/"};
private ResourceProperties.Cache.Cachecontrol cache;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
ResourceHandlerRegistration registration = registry.addResourceHandler(handler).addResourceLocations(locations);
if (cache == null) {
cache = new ResourceProperties.Cache.Cachecontrol();
cache.setNoCache(true);
cache.setCachePublic(true);
}
registration.setCacheControl(cache.toHttpCacheControl());
}
public String[] getHandler() {
return handler;
}
public void setHandler(String[] handler) {
this.handler = handler;
}
public String[] getLocations() {
return locations;
}
public void setLocations(String[] locations) {
this.locations = locations;
}
public ResourceProperties.Cache.Cachecontrol getCache() {
return cache;
}
public void setCache(ResourceProperties.Cache.Cachecontrol cache) {
this.cache = cache;
}
}
外部配置:
1
2
3
4
5
6
7
service:
resources:
handler: /static/**
locations: classpath:/demo/
cache:
max-age: 15
cache-public: true