// // System.Web.Caching.OutputCacheModule // // Authors: // Jackson Harper (jackson@ximian.com) // Marek Habersack // // (C) 2003-2009 Novell, Inc (http://www.novell.com) // // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Collections.Generic; using System.Configuration.Provider; using System.IO; using System.Text; using System.Web; using System.Web.Hosting; using System.Web.UI; using System.Web.Util; using System.Web.Compilation; namespace System.Web.Caching { sealed class OutputCacheModule : IHttpModule { CacheItemRemovedCallback response_removed; static object keysCacheLock = new object (); Dictionary keysCache; Dictionary entriesToInvalidate; public OutputCacheModule () { } OutputCacheProvider FindCacheProvider (HttpApplication app) { HttpContext ctx = HttpContext.Current; if (app == null) { app = ctx != null ? ctx.ApplicationInstance : null; if (app == null) throw new InvalidOperationException ("Unable to find output cache provider."); } string providerName = app.GetOutputCacheProviderName (ctx); if (String.IsNullOrEmpty (providerName)) throw new ProviderException ("Invalid OutputCacheProvider name. Name must not be null or an empty string."); OutputCacheProvider ret = OutputCache.GetProvider (providerName); if (ret == null) throw new ProviderException (String.Format ("OutputCacheProvider named '{0}' cannot be found.", providerName)); return ret; } public void Dispose () { } public void Init (HttpApplication context) { context.ResolveRequestCache += new EventHandler(OnResolveRequestCache); context.UpdateRequestCache += new EventHandler(OnUpdateRequestCache); response_removed = new CacheItemRemovedCallback (OnRawResponseRemoved); } void OnBuildManagerRemoveEntry (BuildManagerRemoveEntryEventArgs args) { string entry = args.EntryName; HttpContext context = args.Context; string cacheValue; lock (keysCacheLock) { if (!keysCache.TryGetValue (entry, out cacheValue)) return; keysCache.Remove (entry); if (context == null) { if (entriesToInvalidate == null) { entriesToInvalidate = new Dictionary (StringComparer.Ordinal); entriesToInvalidate.Add (entry, cacheValue); return; } else if (!entriesToInvalidate.ContainsKey (entry)) { entriesToInvalidate.Add (entry, cacheValue); return; } } } OutputCacheProvider provider = FindCacheProvider (context != null ? context.ApplicationInstance : null); provider.Remove (entry); if (!String.IsNullOrEmpty (cacheValue)) provider.Remove (cacheValue); } void OnResolveRequestCache (object o, EventArgs args) { HttpApplication app = o as HttpApplication; HttpContext context = app != null ? app.Context : null; if (context == null) return; OutputCacheProvider provider = FindCacheProvider (app); string vary_key = context.Request.FilePath; CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy; string key; CachedRawResponse c; if (varyby == null) return; key = varyby.CreateKey (vary_key, context); c = provider.Get (key) as CachedRawResponse; if (c == null) return; lock (keysCacheLock) { string invValue; if (entriesToInvalidate != null && entriesToInvalidate.TryGetValue (vary_key, out invValue) && String.Compare (invValue, key, StringComparison.Ordinal) == 0) { provider.Remove (vary_key); provider.Remove (key); entriesToInvalidate.Remove (vary_key); return; } } ArrayList callbacks = c.Policy.ValidationCallbacks; if (callbacks != null && callbacks.Count > 0) { bool isValid = true; bool isIgnored = false; foreach (Pair p in callbacks) { HttpCacheValidateHandler validate = (HttpCacheValidateHandler)p.First; object data = p.Second; HttpValidationStatus status = HttpValidationStatus.Valid; try { validate (context, data, ref status); } catch { // MS.NET hides the exception isValid = false; break; } if (status == HttpValidationStatus.Invalid) { isValid = false; break; } else if (status == HttpValidationStatus.IgnoreThisRequest) { isIgnored = true; } } if (!isValid) { OnRawResponseRemoved (key, c, CacheItemRemovedReason.Removed); return; } else if (isIgnored) return; } HttpResponse response = context.Response; response.ClearContent (); IList cachedData = c.GetData (); if (cachedData != null) { Encoding outEnc = WebEncoding.ResponseEncoding; foreach (CachedRawResponse.DataItem d in cachedData) { if (d.Length > 0) { response.BinaryWrite (d.Buffer, 0, (int)d.Length); continue; } if (d.Callback == null) continue; string s = d.Callback (context); if (s == null || s.Length == 0) continue; byte[] bytes = outEnc.GetBytes (s); response.BinaryWrite (bytes, 0, bytes.Length); } } response.ClearHeaders (); response.SetCachedHeaders (c.Headers); response.StatusCode = c.StatusCode; response.StatusDescription = c.StatusDescription; app.CompleteRequest (); } void OnUpdateRequestCache (object o, EventArgs args) { HttpApplication app = o as HttpApplication; HttpContext context = app != null ? app.Context : null; HttpResponse response = context != null ? context.Response : null; if (response != null && response.IsCached && response.StatusCode == 200 && !context.Trace.IsEnabled) DoCacheInsert (context, app, response); } void DoCacheInsert (HttpContext context, HttpApplication app, HttpResponse response) { string vary_key = context.Request.FilePath; string key; OutputCacheProvider provider = FindCacheProvider (app); CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy; CachedRawResponse prev = null; bool lookup = true; string cacheKey = null, cacheValue = null; HttpCachePolicy cachePolicy = response.Cache; if (varyby == null) { varyby = new CachedVaryBy (cachePolicy, vary_key); provider.Add (vary_key, varyby, Cache.NoAbsoluteExpiration); lookup = false; cacheKey = vary_key; } key = varyby.CreateKey (vary_key, context); if (lookup) prev = provider.Get (key) as CachedRawResponse; if (prev == null) { CachedRawResponse c = response.GetCachedResponse (); if (c != null) { string [] keys = new string [] { vary_key }; DateTime utcExpiry, absoluteExpiration; TimeSpan slidingExpiration; c.VaryBy = varyby; varyby.ItemList.Add (key); if (cachePolicy.Sliding) { slidingExpiration = TimeSpan.FromSeconds (cachePolicy.Duration); absoluteExpiration = Cache.NoAbsoluteExpiration; utcExpiry = DateTime.UtcNow + slidingExpiration; } else { slidingExpiration = Cache.NoSlidingExpiration; absoluteExpiration = cachePolicy.Expires; utcExpiry = absoluteExpiration.ToUniversalTime (); } provider.Set (key, c, utcExpiry); HttpRuntime.InternalCache.Insert (key, c, new CacheDependency (null, keys), absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, response_removed); cacheValue = key; } } if (cacheKey != null) { lock (keysCacheLock) { if (keysCache == null) { BuildManager.RemoveEntry += new BuildManagerRemoveEntryEventHandler (OnBuildManagerRemoveEntry); keysCache = new Dictionary (StringComparer.Ordinal); keysCache.Add (cacheKey, cacheValue); } else if (!keysCache.ContainsKey (cacheKey)) keysCache.Add (cacheKey, cacheValue); } } } void OnRawResponseRemoved (string key, object value, CacheItemRemovedReason reason) { CachedRawResponse c = value as CachedRawResponse; CachedVaryBy varyby = c != null ? c.VaryBy : null; if (varyby == null) return; List itemList = varyby.ItemList; OutputCacheProvider provider = FindCacheProvider (null); itemList.Remove (key); provider.Remove (key); if (itemList.Count != 0) return; provider.Remove (varyby.Key); } } }