HttpClient缓存

HttpClient缓存,第1张

HttpClient缓存

目录

一、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> testHttpClientCache(String url) {
        Response> response = new Response<>();
        try {
            // 返回结果
            Map result = Maps.newHashMap();

            // 创建GET请求
            HttpGet httpGet = new HttpGet(url);
            // 创建缓存上下文
            HttpCacheContext httpCacheContext = HttpCacheContext.create();

            // 请求响应
            CloseableHttpResponse closeableHttpResponse = HttpClientCache.getHttpClient().execute(httpGet, httpCacheContext);
            // 获取响应缓存情况
            CacheResponseStatus cacheResponseStatus = httpCacheContext.getCacheResponseStatus();
            result.put("cacheResponseStatus", cacheResponseStatus.toString());

            // 获取响应体
            HttpEntity entity = closeableHttpResponse.getEntity();
            if (entity != null) {
                String data = EntityUtils.toString(entity, "utf-8");

                result.put("data", data);
            }

            response.setData(result);
            response.setSuccess(true);
        } catch (IOException e) {
            e.printStackTrace();
            return Response.error();
        }
        return 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;
}
五、参考资料

HttpClient详细使用示例_JustryDeng-CSDN博客_httpclient

HttpClient 4.3详细教程之HTTP缓存_liujiding的博客-CSDN博客

浏览器缓存缓存策略(看完就懂) - 掘金

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5563779.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-14
下一篇 2022-12-14

发表评论

登录后才能评论

评论列表(0条)

保存