目录
一、HttpClient流程
1. 请求流程
2. 响应流程
二、代码实例
1. 依赖jar包
2. HTTP缓存核心类
3. 调用代码
4. 验证
三、缓存状态
四、源码解析
1. CachingExec#execute
2. CachingExec#handleCacheHit
五、参考资料
一、HttpClient流程
1. 请求流程
2. 响应流程
二、代码实例
1. 依赖jar包
org.apache.httpcomponents
httpclient
4.5.13
org.apache.httpcomponents
httpclient-cache
4.5.6
2. HTTP缓存核心类
package com.common.instance.test.config.cache;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.cache.*;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Component
public class HttpClientCache {
// http请求客户端
private static CloseableHttpClient httpClient;
// 连接管理器
private static PoolingHttpClientConnectionManager connectionManager;
static {
connectionManager = new PoolingHttpClientConnectionManager();
// 设置最大连接数
connectionManager.setMaxTotal(500);
// 默认每路由最高50并发
connectionManager.setDefaultMaxPerRoute(300);
getHttpClient();
}
// 获取http请求客户端
public static CloseableHttpClient getHttpClient(){
if (Objects.isNull(httpClient)){
// 缓存配置
CacheConfig cacheConfig = CacheConfig.custom()
// 最大缓存条目
.setMaxCacheEntries(100)
// 缓存对象最大2MB
.setMaxObjectSize(2 * 1024 * 1024)
// 异步更新缓存线程池最小空闲线程数
.setAsynchronousWorkersCore(5)
// 异步更新缓存线程池最大线程数
.setAsynchronousWorkersMax(10)
// 异步更新缓存线程池队列大小
.setRevalidationQueueSize(1000)
.build();
// 缓存存储
HttpCacheStorage cacheStorage = new BasicHttpCacheStorage(cacheConfig);
// 请求配置
RequestConfig requestConfig = RequestConfig.custom()
// 获取数据超时时间
.setSocketTimeout(10000)
// 远程建立连接的超时时间
.setConnectTimeout(10000)
// 连接池获取连接的超时时间
.setConnectionRequestTimeout(10000)
.build();
// 创建httpclient
httpClient = CachingHttpClients.custom()
.setCacheConfig(cacheConfig)
.setHttpCacheStorage(cacheStorage)
// 验证缓存时,缓存调度策略
.setSchedulingStrategy(new ImmediateSchedulingStrategy(cacheConfig))
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.build();
}
return httpClient;
}
}
3. 调用代码
package com.common.instance.test.controller;
import com.common.instance.test.config.cache.HttpClientCache;
import com.common.instance.test.core.Response;
import com.common.instance.test.core.serviceLevel.OneLevelAsyncContext;
import com.common.instance.test.entity.WcPendantTab;
import com.common.instance.test.service.WcPendantTabService;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.cache.CacheResponseStatus;
import org.apache.http.client.cache.HttpCacheContext;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@Slf4j
@RestController
@RequestMapping("/tab")
@Api(tags = "活动tab测试")
public class WcPendantTabController {
@Resource
private WcPendantTabService wcPendantTabService;
@GetMapping("/testHttpClientCache")
@ApiOperation("测试HttpClient缓存")
public Response
4. 验证
1. 依赖jar包
org.apache.httpcomponents
httpclient
4.5.13
org.apache.httpcomponents
httpclient-cache
4.5.6
2. HTTP缓存核心类
package com.common.instance.test.config.cache;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.cache.*;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Component
public class HttpClientCache {
// http请求客户端
private static CloseableHttpClient httpClient;
// 连接管理器
private static PoolingHttpClientConnectionManager connectionManager;
static {
connectionManager = new PoolingHttpClientConnectionManager();
// 设置最大连接数
connectionManager.setMaxTotal(500);
// 默认每路由最高50并发
connectionManager.setDefaultMaxPerRoute(300);
getHttpClient();
}
// 获取http请求客户端
public static CloseableHttpClient getHttpClient(){
if (Objects.isNull(httpClient)){
// 缓存配置
CacheConfig cacheConfig = CacheConfig.custom()
// 最大缓存条目
.setMaxCacheEntries(100)
// 缓存对象最大2MB
.setMaxObjectSize(2 * 1024 * 1024)
// 异步更新缓存线程池最小空闲线程数
.setAsynchronousWorkersCore(5)
// 异步更新缓存线程池最大线程数
.setAsynchronousWorkersMax(10)
// 异步更新缓存线程池队列大小
.setRevalidationQueueSize(1000)
.build();
// 缓存存储
HttpCacheStorage cacheStorage = new BasicHttpCacheStorage(cacheConfig);
// 请求配置
RequestConfig requestConfig = RequestConfig.custom()
// 获取数据超时时间
.setSocketTimeout(10000)
// 远程建立连接的超时时间
.setConnectTimeout(10000)
// 连接池获取连接的超时时间
.setConnectionRequestTimeout(10000)
.build();
// 创建httpclient
httpClient = CachingHttpClients.custom()
.setCacheConfig(cacheConfig)
.setHttpCacheStorage(cacheStorage)
// 验证缓存时,缓存调度策略
.setSchedulingStrategy(new ImmediateSchedulingStrategy(cacheConfig))
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.build();
}
return httpClient;
}
}
3. 调用代码
package com.common.instance.test.controller;
import com.common.instance.test.config.cache.HttpClientCache;
import com.common.instance.test.core.Response;
import com.common.instance.test.core.serviceLevel.OneLevelAsyncContext;
import com.common.instance.test.entity.WcPendantTab;
import com.common.instance.test.service.WcPendantTabService;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.cache.CacheResponseStatus;
import org.apache.http.client.cache.HttpCacheContext;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@Slf4j
@RestController
@RequestMapping("/tab")
@Api(tags = "活动tab测试")
public class WcPendantTabController {
@Resource
private WcPendantTabService wcPendantTabService;
@GetMapping("/testHttpClientCache")
@ApiOperation("测试HttpClient缓存")
public Response
4. 验证
package com.common.instance.test.config.cache;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.cache.*;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Component
public class HttpClientCache {
// http请求客户端
private static CloseableHttpClient httpClient;
// 连接管理器
private static PoolingHttpClientConnectionManager connectionManager;
static {
connectionManager = new PoolingHttpClientConnectionManager();
// 设置最大连接数
connectionManager.setMaxTotal(500);
// 默认每路由最高50并发
connectionManager.setDefaultMaxPerRoute(300);
getHttpClient();
}
// 获取http请求客户端
public static CloseableHttpClient getHttpClient(){
if (Objects.isNull(httpClient)){
// 缓存配置
CacheConfig cacheConfig = CacheConfig.custom()
// 最大缓存条目
.setMaxCacheEntries(100)
// 缓存对象最大2MB
.setMaxObjectSize(2 * 1024 * 1024)
// 异步更新缓存线程池最小空闲线程数
.setAsynchronousWorkersCore(5)
// 异步更新缓存线程池最大线程数
.setAsynchronousWorkersMax(10)
// 异步更新缓存线程池队列大小
.setRevalidationQueueSize(1000)
.build();
// 缓存存储
HttpCacheStorage cacheStorage = new BasicHttpCacheStorage(cacheConfig);
// 请求配置
RequestConfig requestConfig = RequestConfig.custom()
// 获取数据超时时间
.setSocketTimeout(10000)
// 远程建立连接的超时时间
.setConnectTimeout(10000)
// 连接池获取连接的超时时间
.setConnectionRequestTimeout(10000)
.build();
// 创建httpclient
httpClient = CachingHttpClients.custom()
.setCacheConfig(cacheConfig)
.setHttpCacheStorage(cacheStorage)
// 验证缓存时,缓存调度策略
.setSchedulingStrategy(new ImmediateSchedulingStrategy(cacheConfig))
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.build();
}
return httpClient;
}
}
3. 调用代码
package com.common.instance.test.controller;
import com.common.instance.test.config.cache.HttpClientCache;
import com.common.instance.test.core.Response;
import com.common.instance.test.core.serviceLevel.OneLevelAsyncContext;
import com.common.instance.test.entity.WcPendantTab;
import com.common.instance.test.service.WcPendantTabService;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.cache.CacheResponseStatus;
import org.apache.http.client.cache.HttpCacheContext;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@Slf4j
@RestController
@RequestMapping("/tab")
@Api(tags = "活动tab测试")
public class WcPendantTabController {
@Resource
private WcPendantTabService wcPendantTabService;
@GetMapping("/testHttpClientCache")
@ApiOperation("测试HttpClient缓存")
public Response
4. 验证
如下结果所示,cacheResponseStatus是缓存状态,第一次缓存状态是CACHE_MISS(缓存未命中),第二次缓存状态CACHE_HIT(缓存命中),请求的资源必须是客户端可缓存的,否则缓存状态一直是CACHE_MISS。缓存状态见下章节。
// 第一次请求结果
{
"success": true,
"code": null,
"message": null,
"tip": null,
"data": {
"cacheResponseStatus": "CACHE_MISS",
"data": "rndefine("TB_ROOT/js/localStorageObj",function(require,a){var c=!!window.localStorage;var d={init:function(a){return{expire:a||7,ts:"_timestamp"}},get:function(a){if(!c)return!1;var b=this.init();return localStorage.getItem(a+b.ts)},set:function(a,b){if(!c)return!1;var d=this.init(),e=(new Date).getTime();b=b||e,localStorage.setItem(a+d.ts,b)},del:function(a){var b=this.init();c&&localStorage.removeItem(a+b.ts)},check:function(a,b){var e,d=this.init(b);return c?(e=this.get(a),e?((new Date).getTime()-e)/1e3/60/60/24>d.expire?(this.del(a),!0):!1:!0):void 0}};a.localStorage=d});rn"
}
}
// 第二次请求结果
{
"success": true,
"code": null,
"message": null,
"tip": null,
"data": {
"cacheResponseStatus": "CACHE_HIT",
"data": "rndefine("TB_ROOT/js/localStorageObj",function(require,a){var c=!!window.localStorage;var d={init:function(a){return{expire:a||7,ts:"_timestamp"}},get:function(a){if(!c)return!1;var b=this.init();return localStorage.getItem(a+b.ts)},set:function(a,b){if(!c)return!1;var d=this.init(),e=(new Date).getTime();b=b||e,localStorage.setItem(a+d.ts,b)},del:function(a){var b=this.init();c&&localStorage.removeItem(a+b.ts)},check:function(a,b){var e,d=this.init(b);return c?(e=this.get(a),e?((new Date).getTime()-e)/1e3/60/60/24>d.expire?(this.del(a),!0):!1:!0):void 0}};a.localStorage=d});rn"
}
}
三、缓存状态
org.apache.http.client.cache.CacheResponseStatus是个枚举类,如下表所示,各值的含义。
| 值 | 说明 |
| CACHE_MODULE_RESPONSE | 缓存直接生成响应,若缓存没有,则响应504状态(如:请求头Cache-Control:if-only-cached,只使用缓存) |
| CACHE_HIT | 缓存命中,不会发送请求到上游服务 |
| CACHE_MISS | 缓存未命中,响应来自上游服务 |
| VALIDATED | 从源服务器验证缓存是否被修改,验证通过后,从缓存响应 |
四、源码解析
HttpClient使用职责链模式实现,使用org.apache.http.impl.client.cache.CachingExec#execute组件进行缓存操作。
1. CachingExec#execute
@Override
public CloseableHttpResponse execute(
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext context,
final HttpExecutionAware execAware) throws IOException, HttpException {
// 获取远程服务器地址
final HttpHost target = context.getTargetHost();
// 生成Via请求头
final String via = generateViaHeader(request.getOriginal());
// 默认响应缓存状态CACHE_MISS(缓存未命中)
setResponseStatus(context, CacheResponseStatus.CACHE_MISS);
// 判断请求方法是OPTIONS
if (clientRequestsOurOptions(request)) {
setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
return Proxies.enhanceResponse(new OptionsHttp11Response());
}
// 默认未错误响应
final HttpResponse fatalErrorResponse = getFatallyNoncompliantResponse(request, context);
if (fatalErrorResponse != null) {
return Proxies.enhanceResponse(fatalErrorResponse);
}
// 使请求符合HTTP/1.1规范
requestCompliance.makeRequestCompliant(request);
request.addHeader("Via",via);
// 清除无效的缓存内容
if (!cacheableRequestPolicy.isServableFromCache(request)) {
log.debug("Request is not servable from cache");
flushEntriesInvalidatedByRequest(context.getTargetHost(), request);
return callBackend(route, request, context, execAware);
}
// 从缓存获取内容
final HttpCacheEntry entry = satisfyFromCache(target, request);
// 缓存未命中,则请求上游服务
if (entry == null) {
log.debug("Cache miss");
return handleCacheMiss(route, request, context, execAware);
// 缓存命中
} else {
return handleCacheHit(route, request, context, execAware, entry);
}
}
2. CachingExec#handleCacheHit
private CloseableHttpResponse handleCacheHit(
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext context,
final HttpExecutionAware execAware,
final HttpCacheEntry entry) throws IOException, HttpException {
// 获取源服务器地址
final HttpHost target = context.getTargetHost();
// 记录缓存命中
recordCacheHit(target, request);
CloseableHttpResponse out = null;
// 当前时间
final Date now = getCurrentDate();
// 缓存内容是否可以直接响应
if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) {
log.debug("Cache hit");
// 命中后,生成响应
out = generateCachedResponse(request, context, entry, now);
// 缓存不新鲜,若请求头有only-if-cached则响应504状态
} else if (!mayCallBackend(request)) {
log.debug("Cache entry not suitable but only-if-cached requested");
out = generateGatewayTimeout(context);
// 缓存不新鲜,若响应不是304状态码或则If-Modified-Since + If-None-Match,则需要请求上游服务
} else if (!(entry.getStatusCode() == HttpStatus.SC_NOT_MODIFIED
&& !suitabilityChecker.isConditional(request))) {
log.debug("Revalidating cache entry");
return revalidateCacheEntry(route, request, context, execAware, entry, now);
// 其他情况,直接请求上游服务
} else {
log.debug("Cache entry not usable; calling backend");
return callBackend(route, request, context, execAware);
}
context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target);
context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
context.setAttribute(HttpCoreContext.HTTP_RESPONSE, out);
context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.TRUE);
return out;
}
五、参考资料
private CloseableHttpResponse handleCacheHit(
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext context,
final HttpExecutionAware execAware,
final HttpCacheEntry entry) throws IOException, HttpException {
// 获取源服务器地址
final HttpHost target = context.getTargetHost();
// 记录缓存命中
recordCacheHit(target, request);
CloseableHttpResponse out = null;
// 当前时间
final Date now = getCurrentDate();
// 缓存内容是否可以直接响应
if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) {
log.debug("Cache hit");
// 命中后,生成响应
out = generateCachedResponse(request, context, entry, now);
// 缓存不新鲜,若请求头有only-if-cached则响应504状态
} else if (!mayCallBackend(request)) {
log.debug("Cache entry not suitable but only-if-cached requested");
out = generateGatewayTimeout(context);
// 缓存不新鲜,若响应不是304状态码或则If-Modified-Since + If-None-Match,则需要请求上游服务
} else if (!(entry.getStatusCode() == HttpStatus.SC_NOT_MODIFIED
&& !suitabilityChecker.isConditional(request))) {
log.debug("Revalidating cache entry");
return revalidateCacheEntry(route, request, context, execAware, entry, now);
// 其他情况,直接请求上游服务
} else {
log.debug("Cache entry not usable; calling backend");
return callBackend(route, request, context, execAware);
}
context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target);
context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
context.setAttribute(HttpCoreContext.HTTP_RESPONSE, out);
context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.TRUE);
return out;
}
五、参考资料
HttpClient详细使用示例_JustryDeng-CSDN博客_httpclient
HttpClient 4.3详细教程之HTTP缓存_liujiding的博客-CSDN博客
浏览器缓存缓存策略(看完就懂) - 掘金



