背景:一个公共站点中的数据,供其它子站点共享,为了提高性能,简单实现了Http 1.1的缓存功能
特点:可以缓存Html数据到内存中;缓存具有过期时间;缓存过期后,通过再确认的方式来决定是否更新缓存;浏览器刷新后,无论缓存是否过期都会强制再验证;
未实现的包括:不能通过no-store来强制清空缓存,缓存再确认时没有验证Etag
/// <summary> /// 启用缓存的HttpRequest /// </summary> public static class CacheHttpRequest { private static Dictionary<String,HtmlLocalCache> _caches = new Dictionary<string,HtmlLocalCache>(); private static object AddCacheLock = new Object(); public static String GetHtml(String url) { HtmlLocalCache cache; //如果有缓存,且缓存尚未过期,或不需要重新验证,则直接返回内容 if(_caches.TryGetValue(url,out cache)) { if(!cache.NeedRevalidate()) { return cache.Html; } } var webRequest = (HttpWebRequest)WebRequest.Create(url); //如果有缓存,且需要重新验证,则设置它的IMS信息,为了简便,这里没有验证ETag if(cache != null) { webRequest.IfModifiedSince = cache.LastModified; } HttpWebResponse response = null; Stream stream = null; try { response = webRequest.GetHttpResponse(); //如果服务器资源没有修改,则修改缓存信息后,返回内容 if (cache != null && response.StatusCode == HttpStatusCode.NotModified) { cache.UpdateCacheProperty(response); return cache.Html; } //服务器资源已经修改,重新获取内容,并放入缓存 if (response.StatusCode == HttpStatusCode.OK) { stream = response.GetResponseStream(); TextReader reader = new StreamReader(stream); cache = new HtmlLocalCache(); cache.SetCacheProperty(response); cache.Html = reader.ReadToEnd(); AddHtmlToCache(url, cache); return cache.Html; } return ""; } finally { if(response != null) response.Close(); if(stream != null) stream.Dispose(); } } private static void AddHtmlToCache(string url, HtmlLocalCache cache) { lock (AddCacheLock) { if (!_caches.ContainsKey(url)) { _caches.Add(url, cache); } } } /// <summary> /// 缓存数据 /// </summary> private class HtmlLocalCache { public String Html; public DateTime LastModified; public DateTime? ExpiredTime; public DateTime HttpDate; private Object _updateLock = new Object(); public Boolean NeedRevalidate() { if (ExpiredTime == null) return true; if (FromRefresh()) return true; return DateTime.Now > ExpiredTime; } /// <summary> /// 看是否是页面刷新 /// </summary> /// <returns></returns> private static bool FromRefresh() { string requestCacheControl = HttpContext.Current.Request.Headers["Cache-Control"]; String pragma = HttpContext.Current.Request.Headers["Pragma"]; Boolean isRefresh = (pragma != null && pragma.Equals("no-cache", StringComparison.OrdinalIgnoreCase) || requestCacheControl != null && requestCacheControl.Equals("no-cache", StringComparison.OrdinalIgnoreCase)); if (isRefresh) return true; return false; } private void SetCacheControl(string cacheControl) { if (String.IsNullOrEmpty(cacheControl)) return; if(cacheControl.Contains("max-age")) { Double maxAge = Double.Parse(cacheControl.Substring(cacheControl.IndexOf('=')+1)); ExpiredTime = HttpDate.AddSeconds(maxAge); } } public void SetCacheProperty(HttpWebResponse response) { LastModified = response.LastModified; HttpDate = Convert.ToDateTime(response.Headers["Date"]); //使用cache-control来控制缓存过期时间,不使用expires SetCacheControl(response.Headers["Cache-Control"]); } public void UpdateCacheProperty(HttpWebResponse response) { lock (_updateLock) { SetCacheProperty(response); } } } }