这里的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,记得一定要执行这一句,否则会报错。
定义一个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