首页 > Web开发 > 详细

apache httpclient cache 实现可缓存的http客户端

时间:2015-12-21 14:06:19      阅读:1005      评论:0      收藏:0      [点我收藏+]

这里的cache storage 采用ehcache,而不是默认的内存式的cache storage。采用ehcache可以将内容缓存到磁盘上。

maven

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5</version>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient-cache</artifactId>
            <version>4.5</version>
        </dependency>


        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.8.3</version>
        </dependency>

 

ehcache配置如下:

<ehcache>
    <!-- <diskStore path="java.io.tmpdir" /> -->
    
    <diskStore path="c:\\ehcache"/>
    
    <defaultCache 
        maxElementsInMemory="10000" 
        eternal="false"
        timeToIdleSeconds="120" 
        timeToLiveSeconds="120" 
        overflowToDisk="true"
        maxElementsOnDisk="10000000" 
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120" 
        memoryStoreEvictionPolicy="LRU" />


    <cache name="httpCache" 
        maxElementsInMemory="10000" 
        eternal="true"
        timeToIdleSeconds="0" 
        timeToLiveSeconds="0"  
        overflowToDisk="true"
        maxElementsOnDisk="10000000" 
        diskPersistent="true"
        diskExpiryThreadIntervalSeconds="120" 
        memoryStoreEvictionPolicy="LRU" />
</ehcache>

这里有两个关键点:一是将eternal设置为true,表示采用非内存式的缓存;二是将diskPersistent设置为true,表示将缓存持久化到硬盘。


测试的代码如下:

package my.httpClient;

import java.io.IOException;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.config.DiskStoreConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration.Strategy;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;

import org.apache.http.HttpHost;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.cache.CacheResponseStatus;
import org.apache.http.client.cache.HttpCacheContext;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.client.cache.CachingHttpClients;
import org.apache.http.impl.client.cache.ehcache.EhcacheHttpCacheStorage;

public class EhCacheTest1 {

    public static void main(String[] args) throws ClientProtocolException,
            IOException {

        System.out.println("begin");

        // EhCache缓存存储
        CacheManager cacheManager = CacheManager.create();
        Cache httpCache = cacheManager.getCache("httpCache");

        // 定义httpclient的缓存存储
        EhcacheHttpCacheStorage ehcacheHttpCacheStorage = new EhcacheHttpCacheStorage(
                httpCache);

        // 缓存配置
        CacheConfig cacheConfig = CacheConfig.custom()
                .setMaxCacheEntries(10000).setMaxObjectSize(819200).build();

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(30000).setSocketTimeout(30000).build();

        HttpHost proxy = new HttpHost("127.0.0.1", 8888);

        CloseableHttpClient cachingClient = CachingHttpClients.custom()
                .setCacheConfig(cacheConfig)
                .setHttpCacheStorage(ehcacheHttpCacheStorage)
                .setDefaultRequestConfig(requestConfig).setProxy(proxy).build();

        HttpCacheContext context = HttpCacheContext.create();
        HttpGet httpget = new HttpGet("http://test.cn:11677/api/values?id=8888");

        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;
            }
        } catch (Exception e) {
            // TODO: handle exception

        } finally {
             response.close();
             cacheManager.shutdown();        
             System.out.println("end");
        }
    }

}

以上代码有几个需要说明的地方:

(1)服务端需要遵循RFC2626中规定缓存方面的协议。

(2)代码setHttpCacheStorage(ehcacheHttpCacheStorage)用于设置缓存存储。

(3)代码setProxy(proxy)用于配置代理,当你使用fiddler进行调试的时候,这个很有用。若不采用代理,则可以将这句给去掉。

(4)代码cacheManager.shutdown()用于关闭cacheManager,记得一定要执行这一句,否则会报错。      

 

服务端的实现(asp.net WebApi)

定义一个Filter

  public class MyOutputCacheAttribute : ActionFilterAttribute
    {

        MemoryCacheDefault _cache = new MemoryCacheDefault();



        /// <summary>
        /// 客户端缓存(用户代理)缓存相对过期时间
        /// </summary>
        public int ClientCacheExpiration { set; get; }


        /// <summary>
        /// 服务端缓存过期时间
        /// </summary>
        public int ServerCacheExpiration { set; get; }




        /// <summary>
        /// 
        /// </summary>
        /// <param name="clientCacheExpiration">客户端过期时间。单位为秒,默认为600秒(10分钟)</param>
        /// <param name="cerverCacheExpiration">服务端过期时间。单位为秒,默认为7200秒(120分钟)</param>
        public MyOutputCacheAttribute(int clientCacheExpiration = 600, int serverCacheExpiration = 7200)
        {
            this.ClientCacheExpiration = clientCacheExpiration;
            this.ServerCacheExpiration = serverCacheExpiration;
        }



        /// <summary>
        /// 判定是否用缓存中的内容,还是执行action是去取内容
        /// 注:一旦给actionContext.Response赋值了,则会使用这个值来输出响应
        /// </summary>
        /// <param name="actionContext"></param>
        public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
        {

            // ****************************************   穿透缓存 *********************************************
            // 若无缓存,则直接返回
            string cacheKey = getCacheKey(actionContext);
            if (_cache.Contains(cacheKey) == false)
                return;
            if (_cache.Contains(cacheKey + ":etag") == false)
                return;

            // ****************************************   使用缓存  *********************************************
            // 获取缓存中的etag
            var etag = _cache.Get<string>(cacheKey + ":etag");
            // 若etag没有被改变,则返回304,
            if (actionContext.Request.Headers.IfNoneMatch.Any(x => x.Tag == etag))  //IfNoneMatch为空时,返回的是一个集合对象
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.NotModified); // 响应对象还没有生成,需要先生成 // 304 not modified
                actionContext.Response.Headers.CacheControl = GetDefaultCacheControl();
                actionContext.Response.Headers.ETag = new EntityTagHeaderValue(etag);
                return;
            }
            else //从缓存中返回响应
            {
                actionContext.Response = actionContext.Request.CreateResponse(); // 响应对象还没有生成,需要先生成
                // 设置协商缓存:etag
                actionContext.Response.Headers.ETag = new EntityTagHeaderValue(etag); //用缓存中的值(为最新的)更新它
                // 设置user agent的本地缓存
                actionContext.Response.Headers.CacheControl = GetDefaultCacheControl();

                // 从缓存中取中响应内容
                var content = _cache.Get<byte[]>(cacheKey);
                actionContext.Response.Content = new ByteArrayContent(content);
                return;
            }
        }






        /// <summary>
        ///  将输出保存在缓存中。
        /// </summary>
        /// <param name="actionExecutedContext"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async System.Threading.Tasks.Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, System.Threading.CancellationToken cancellationToken)
        {
            // 响应对象已经生成,可以直接调用   actionExecutedContext.Response

            // 设置协商缓存:etag
            EntityTagHeaderValue etag = new EntityTagHeaderValue("\"" + Guid.NewGuid().ToString() + "\"");  // 根据http协议, ETag的值必须用引号包含起来
            actionExecutedContext.Response.Headers.ETag = etag;
            // 设置user agent的本地缓存
            actionExecutedContext.Response.Headers.CacheControl = GetDefaultCacheControl();

            // actionExecutedContext.Response.Headers.Remove("Content-Length"); // 改变了值,它会发生变化。删除它的话,后续的程序会自动地再计算

            // 保存到缓存
            string cacheKey = getCacheKey(actionExecutedContext.ActionContext);
            var contentBytes = await actionExecutedContext.Response.Content.ReadAsByteArrayAsync();

            _cache.Add(cacheKey + ":etag", etag.Tag, DateTimeOffset.Now.AddSeconds(this.ServerCacheExpiration));
            _cache.Add(cacheKey, contentBytes, DateTimeOffset.Now.AddSeconds(this.ServerCacheExpiration));

        }





        /// <summary>
        /// 默认的用户代理本地缓存配置,10分钟的相对过期时间
        /// 对应响应header中的 Cache-Control
        /// 这里设置里面的子项max-age。如:Cache-Control: max-age=3600
        /// </summary>
        /// <returns></returns>
        CacheControlHeaderValue GetDefaultCacheControl()
        {
            CacheControlHeaderValue control = new CacheControlHeaderValue();
            control.MaxAge = TimeSpan.FromSeconds(this.ClientCacheExpiration);  // 它对应响应头中的 cacheControl :max-age=10项

            return control;
        }




        /// <summary>
        /// 
        /// </summary>
        /// <param name="actionContext"></param>
        /// <returns></returns>
        string getCacheKey(System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            string cacheKey = null;

            cacheKey = actionContext.Request.RequestUri.PathAndQuery; // 路径和查询部分,如: /api/values/1?ee=33&oo=5

            // 对路径中的参数进行重排,升序排列

            //string controllerName = actionContext.ControllerContext.ControllerDescriptor.ControllerName;
            //string actionName = actionContext.ActionDescriptor.ActionName;

            //if (actionContext.ActionArguments.ContainsKey("id") == false)
            //{
            //    cacheKey = controllerName + "/" + actionName;
            //}
            //else
            //{
            //    string id = actionContext.ActionArguments["id"].ToString();
            //    cacheKey = controllerName + "/" + actionName + "/" + id;
            //}

            return cacheKey;
        }


    }

 上面的这段代码严格遵循RFC2626中定义的缓存协议。

 

 

定义一个服务器端缓存实现

这里采用MemoryCache,也可以采用memcached, redis之类的。

public class MemoryCacheDefault
{
    private static readonly MemoryCache _cache = MemoryCache.Default;

    public void RemoveStartsWith(string key)
    {
        lock (_cache)
        {
            _cache.Remove(key);
        }
    }

    public T Get<T>(string key) where T : class
    {
        var o = _cache.Get(key) as T;
        return o;
    }



    [Obsolete("Use Get<T> instead")]
    public object Get(string key)
    {
        return _cache.Get(key);
    }





    public void Remove(string key)
    {
        lock (_cache)
        {
            _cache.Remove(key);
        }
    }

    public bool Contains(string key)
    {
        return _cache.Contains(key);
    }

    public void Add(string key, object o, DateTimeOffset expiration, string dependsOnKey = null)
    {
        var cachePolicy = new CacheItemPolicy
        {
            AbsoluteExpiration = expiration
        };

        if (!string.IsNullOrWhiteSpace(dependsOnKey))
        {
            cachePolicy.ChangeMonitors.Add(
                _cache.CreateCacheEntryChangeMonitor(new[] { dependsOnKey })
            );
        }
        lock (_cache)
        {
            _cache.Add(key, o, cachePolicy);
        }
    }


    public IEnumerable<string> AllKeys
    {
        get
        {
            return _cache.Select(x => x.Key);
        }
    }
}

 

将filter应用到action中

    public class ValuesController : ApiController
    {

        [MyOutputCache(10)]
        public string Get(int id)
        {
            return "value" + id.ToString();
        }
    }

 

apache httpclient cache 实现可缓存的http客户端

原文:http://www.cnblogs.com/dehai/p/5063106.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!