第六章. HTTP缓存
6.1. 通用概念
HttpClient Cache 提供了用HttpClient(等效浏览器缓存的Java实现)来兼容HTTP / 1.1的缓存层。实现遵循责任链模式,HttpClient缓存的实现类可以替代默认无缓存的HttpClient;完全可以通过缓存实现的请求将不会触发实际的原始请求。在可以的情况下,使用GETs条件If-Modified-Since和/or If-None-Match请求头,会自动验证旧的缓存项。HTTP / 1.1缓存一般被设计成语义透明的,也就是说,缓存不会改变客户端和服务器端的请求响应之间交换的意义。因此,向一个现有的客户端-服务器的关系中添加HttpClient是安全的。尽管从一个HTTP协议的角度来看,缓存模块是客户端的一部分,实现的目标是满足基于透明缓存代理的要求。最后,缓存HttpClient包括支持RFC 5861(stale-if-error和stale-while-revalidate)指定的cache – control扩展。 当缓存HttpClient执行一个请求时,它会通过以下流程:
- 检查要求是否符合基本的HTTP 1.1协议并尝试正确的请求。
- 刷新当前请求导致无效的一些缓存项。
- 确定当前请求可以从缓存中提供。如果不能, 则直接将这个请求发送给原始服务器并换回响应,如果合适的话保存缓存.
- 如果是cache-servable请求时,将尝试从缓存中读取。当缓存中没有的情况下,调用原始的服务器,如果可以的话将响应缓存。
- 如果缓存中的响应能够被当做一个响应提供服务,构造一个包含ByteArrayEntity的BasicHttpResponse并返回。否则,尝试重新验证对原始服务器的缓存项.
- 当一个缓存的响应不能重新生效的情况下,调用原始服务器,如果可以的话缓存响应.
当缓存HttpClient收到响应时,它通过以下流程:
- 根据协议内容,检查响应。
- 确定响应是否可以被缓存。
- 如果可以被缓存,读取配置中允许的最多内容,并将其保存在缓存中。
- 如果对缓存来说响应过大,则重新构建被消费的部分响应,并跳过缓存流程直接返回。
需要着重注意的是,HttpClient Cache本身并不是一个HttpClient的不同实现,而是通过插件的方式作为一个额外管道执行请求。
6.2. RFC-2616 规范
我们相信HttpClient缓存是无条件地符合RFC-2616。也就是说无论规范表明对于HTTP缓存 必须、不必须、可以、不可以,缓存层都会试图以满足这些条件的方式实现。 这就意味着,当你使用缓存模块时,不会发生错误的行为。
6.3. Example Usage
下面是一个如何建立一个基本的缓存HttpClient例子。根据配置,他会存储一个最大数量为1000的缓存实例,每个最多能够保存8192 bytes。这里的数字仅仅是个例子,并不是规定,也不能被看做成建议。
CacheConfig cacheConfig = CacheConfig.custom()
.setMaxCacheEntries(1000)
.setMaxObjectSize(8192)
.build();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(30000)
.setSocketTimeout(30000)
.build();
CloseableHttpClient cachingClient = CachingHttpClients.custom()
.setCacheConfig(cacheConfig)
.setDefaultRequestConfig(requestConfig)
.build();
HttpCacheContext context = HttpCacheContext.create();
HttpGet httpget = new HttpGet("http://www.mydomain.com/content/");
CloseableHttpResponse response = cachingClient.execute(httpget, context);
try {
CacheResponseStatus responseStatus = context.getCacheResponseStatus();
switch (responseStatus) {
case CACHE_HIT:
System.out.println("A response was generated from the cache with " +
"no requests sent upstream");
break;
case CACHE_MODULE_RESPONSE:
System.out.println("The response was generated directly by the " +
"caching module");
break;
case CACHE_MISS:
System.out.println("The response came from an upstream server");
break;
case VALIDATED:
System.out.println("The response was generated from the cache " +
"after validating the entry with the origin server");
break;
}
} finally {
response.close();
}
6.4. 配置
HttpClient Cache 继承了所有非缓存实现的配置项和参数(包括像超时时间、连接池大小这样的设置选项)。对于缓存的具体配置,可以根据以下几个方提供一个缓存配置实例指定行为:
缓存大小,后端存储支持这些限制,可以像最大的可缓存响应体大小一样指定缓存项的数量.
公共/私有缓存. 在默认情况下,缓存模块本身是一个共享缓存,例如,不会缓存带有授权请求头的响应和标记为”Cache-Control: private”的响应。然而,如果缓存仅仅被应用于一个“用户”(类似于浏览器缓存的行为)逻辑,那么它将关闭共享缓存设置。
启发式缓存. 按照RFC2616, 缓存可以缓存特定的缓存条目,即使没有显示原始设置的缓存控制头。这种行为在默认情况下是关闭的,但是如果你处理的是一个没有完全设置原始请求头的请求,你可能想要打开它。你需要一个可以启发式的缓存,然后指定一个默认的新生命周期,并且/或者资源被最后修改后的一小段时间。参考HTTP/1.1 RFC的13.2.2和13.2.4部分,可以获得启发式缓存的更多细节。
后台验证. 缓存模块支持RFC5861的stale-while-revalidate指令,允许后台特定的缓存项重新生效。可以需要调整设置后台工作线程的最小和最大工作数量,以及他们回收前闲置的最大时间。没有足够的worker来跟上需求时,您还可以控制队列的大小用于重新生效线程。
6.5. 后端存储
默认的HttpClient Cache的实现会存储缓存项,并且在应用的JVM内存中缓存响应体。虽然这些能够提供高性能,但是有内存大小的限制,或者因为缓存项在应用重启后会失效,所以可能并不适用于您的应用。当前版本允许将缓存项保存到硬盘上,或者使用其他外部进程EhCache和Memcached存储缓存项。如果这些选项满足不了你的应用,可以通过实现HttpCachedStorage提供你自己的后台存储,并且在构建的时候提供给缓存HttpClient。在这种情况下,缓存项会通过你自己的框架存储,但是会重用所有HTTP/1.1协议的逻辑和缓存处理。一般来说,应该能够创建一个支持原子操作的key/value存储(类似于java的Map接口)的HttpCachedStorage实现。最后,通过一些额外的努力完全有可能建立一个多层缓存层次结构; 比如,包装一个内存中的缓存HttpClient在磁盘上的存储缓存条目,在memcached中远程存储,后一种模式类似于虚拟内存,L1 / L2处理器缓存等。