//
// Authors:
// Jackson Harper (jackson@ximian.com)
+// Marek Habersack <mhabersack@novell.com>
//
-// (C) 2003 Novell, Inc (http://www.novell.com)
+// (C) 2003-2009 Novell, Inc (http://www.novell.com)
//
//
// 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.Collections;
+using System.Web.Compilation;
-namespace System.Web.Caching {
-
- internal sealed class OutputCacheModule : IHttpModule {
-
- private CacheItemRemovedCallback response_removed;
+namespace System.Web.Caching
+{
+ sealed class OutputCacheModule : IHttpModule
+ {
+ CacheItemRemovedCallback response_removed;
+ static object keysCacheLock = new object ();
+ Dictionary <string, string> keysCache;
+ Dictionary <string, string> 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 app)
+ public void Init (HttpApplication context)
{
- app.ResolveRequestCache += new EventHandler(OnResolveRequestCache);
- app.UpdateRequestCache += new EventHandler(OnUpdateRequestCache);
-
+ 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 <string, string> (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 = (HttpApplication) o;
- HttpContext context = app.Context;
-
+ 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 = context.Cache [vary_key] as CachedVaryBy;
+ CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy;
string key;
CachedRawResponse c;
return;
key = varyby.CreateKey (vary_key, context);
- c = context.Cache [key] as CachedRawResponse;
-
+ 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;
if (!isValid) {
OnRawResponseRemoved (key, c, CacheItemRemovedReason.Removed);
return;
- } else if (isIgnored) {
+ } else if (isIgnored)
return;
- }
}
-
- context.Response.ClearContent ();
- context.Response.BinaryWrite (c.GetData (), 0, c.ContentLength);
- context.Response.ClearHeaders ();
- c.DateHeader.Value = TimeUtil.ToUtcTimeString (DateTime.Now);
- context.Response.SetCachedHeaders (c.Headers);
+ 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;
- context.Response.StatusCode = c.StatusCode;
- context.Response.StatusDescription = c.StatusDescription;
+ 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 = (HttpApplication) o;
- HttpContext context = app.Context;
-
- if (context.Response.IsCached && context.Response.StatusCode == 200 &&
- !context.Trace.IsEnabled)
- DoCacheInsert (context);
-
+ 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);
}
- private void DoCacheInsert (HttpContext context)
+ void DoCacheInsert (HttpContext context, HttpApplication app, HttpResponse response)
{
string vary_key = context.Request.FilePath;
string key;
- CachedVaryBy varyby = context.Cache [vary_key] as CachedVaryBy;
+ 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) {
- string path = context.Request.MapPath (vary_key);
- string [] files = new string [] { path };
- string [] keys = new string [0];
- varyby = new CachedVaryBy (context.Response.Cache, vary_key);
- context.Cache.InsertPrivate (vary_key, varyby,
- new CacheDependency (files, keys),
- Cache.NoAbsoluteExpiration,
- Cache.NoSlidingExpiration,
- CacheItemPriority.Normal, 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 = context.Cache [key] as CachedRawResponse;
+ prev = provider.Get (key) as CachedRawResponse;
if (prev == null) {
- CachedRawResponse c = context.Response.GetCachedResponse ();
- string [] files = new string [] { };
- string [] keys = new string [] { vary_key };
- bool sliding = context.Response.Cache.Sliding;
-
- context.Cache.InsertPrivate (key, c, new CacheDependency (files, keys),
- (sliding ? Cache.NoAbsoluteExpiration :
- context.Response.Cache.Expires),
- (sliding ? TimeSpan.FromSeconds (
- context.Response.Cache.Duration) :
- Cache.NoSlidingExpiration),
- CacheItemPriority.Normal, response_removed);
- c.VaryBy = varyby;
- varyby.ItemList.Add (key);
- }
+ 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 <string, string> (StringComparer.Ordinal);
+ keysCache.Add (cacheKey, cacheValue);
+ } else if (!keysCache.ContainsKey (cacheKey))
+ keysCache.Add (cacheKey, cacheValue);
+ }
+ }
}
- private void OnRawResponseRemoved (string key, object value, CacheItemRemovedReason reason)
+ void OnRawResponseRemoved (string key, object value, CacheItemRemovedReason reason)
{
- CachedRawResponse c = (CachedRawResponse) value;
-
- c.VaryBy.ItemList.Remove (key);
- if (c.VaryBy.ItemList.Count != 0)
+ CachedRawResponse c = value as CachedRawResponse;
+ CachedVaryBy varyby = c != null ? c.VaryBy : null;
+ if (varyby == null)
return;
+
+ List <string> itemList = varyby.ItemList;
+ OutputCacheProvider provider = FindCacheProvider (null);
- Cache cache = HttpRuntime.Cache;
- cache.Remove (c.VaryBy.Key);
+ itemList.Remove (key);
+ provider.Remove (key);
+
+ if (itemList.Count != 0)
+ return;
+
+ provider.Remove (varyby.Key);
}
}
}
-