a2d4374243b50e294b0ecda3f4f6273b9e1a68e0
[mono.git] / mcs / class / System.Web / System.Web.Caching / OutputCacheModule.cs
1 //
2 // System.Web.Caching.OutputCacheModule
3 //
4 // Authors:
5 //  Jackson Harper (jackson@ximian.com)
6 //
7 // (C) 2003 Novell, Inc (http://www.novell.com)
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System.IO;
32 using System.Web;
33 using System.Web.UI;
34 using System.Web.Util;
35 using System.Collections;
36 using System.Web.Compilation;
37
38 #if NET_2_0
39 using System.Collections.Generic;
40 #endif
41
42 namespace System.Web.Caching {
43         
44         internal sealed class OutputCacheModule : IHttpModule {
45
46                 CacheItemRemovedCallback response_removed;
47                 
48 #if NET_2_0
49                 static object keysCacheLock = new object ();
50                 Dictionary <string, string> keysCache;
51                 Dictionary <string, string> entriesToInvalidate;
52 #endif
53                 
54                 public OutputCacheModule ()
55                 {
56                 }
57
58                 public void Dispose ()
59                 {
60                 }
61
62                 public void Init (HttpApplication app)
63                 {
64                         app.ResolveRequestCache += new EventHandler(OnResolveRequestCache);
65                         app.UpdateRequestCache += new EventHandler(OnUpdateRequestCache);
66 #if NET_2_0
67                         keysCache = new Dictionary <string, string> ();
68                         entriesToInvalidate = new Dictionary <string, string> ();
69                         BuildManager.RemoveEntry += new BuildManagerRemoveEntryEventHandler (OnBuildManagerRemoveEntry);
70 #endif
71                         response_removed = new CacheItemRemovedCallback (OnRawResponseRemoved);
72                 }
73
74 #if NET_2_0
75                 void OnBuildManagerRemoveEntry (BuildManagerRemoveEntryEventArgs args)
76                 {
77                         string entry = args.EntryName;
78                         HttpContext context = args.Context;
79                         string cacheValue;
80                         
81                         lock (keysCacheLock) {
82                                 if (!keysCache.TryGetValue (entry, out cacheValue))
83                                         return;
84                                 
85                                 keysCache.Remove (entry);
86                                 if (context == null && !entriesToInvalidate.ContainsKey (entry)) {
87                                         entriesToInvalidate.Add (entry, cacheValue);
88                                         return;
89                                 }
90                         }
91
92                         context.Cache.Remove (entry);
93                         if (!String.IsNullOrEmpty (cacheValue))
94                                 context.InternalCache.Remove (cacheValue);
95                 }
96 #endif
97
98                 void OnResolveRequestCache (object o, EventArgs args)
99                 {
100                         HttpApplication app = (HttpApplication) o;
101                         HttpContext context = app.Context;
102                         
103                         string vary_key = context.Request.FilePath;
104                         CachedVaryBy varyby = context.Cache [vary_key] as CachedVaryBy;
105                         string key;
106                         CachedRawResponse c;
107
108                         if (varyby == null)
109                                 return;
110
111                         key = varyby.CreateKey (vary_key, context);
112                         c = context.InternalCache [key] as CachedRawResponse;
113                         if (c == null)
114                                 return;
115
116 #if NET_2_0
117                         lock (keysCacheLock) {
118                                 string invValue;
119                                 if (entriesToInvalidate.TryGetValue (vary_key, out invValue) && String.Compare (invValue, key, StringComparison.Ordinal) == 0) {
120                                         context.Cache.Remove (vary_key);
121                                         context.InternalCache.Remove (key);
122                                         entriesToInvalidate.Remove (vary_key);
123                                         return;
124                                 }
125                         }
126 #endif
127                         
128                         ArrayList callbacks = c.Policy.ValidationCallbacks;
129                         if (callbacks != null && callbacks.Count > 0) {
130                                 bool isValid = true;
131                                 bool isIgnored = false;
132
133                                 foreach (Pair p in callbacks) {
134                                         HttpCacheValidateHandler validate = (HttpCacheValidateHandler)p.First;
135                                         object data = p.Second;
136                                         HttpValidationStatus status = HttpValidationStatus.Valid;
137
138                                         try {
139                                                 validate (context, data, ref status);
140                                         } catch {
141                                                 // MS.NET hides the exception
142                                                 isValid = false;
143                                                 break;
144                                         }
145
146                                         if (status == HttpValidationStatus.Invalid) {
147                                                 isValid = false;
148                                                 break;
149                                         } else if (status == HttpValidationStatus.IgnoreThisRequest) {
150                                                 isIgnored = true;
151                                         }
152                                 }
153
154                                 if (!isValid) {
155                                         OnRawResponseRemoved (key, c, CacheItemRemovedReason.Removed);
156                                         return;
157                                 } else if (isIgnored) {
158                                         return;
159                                 }
160                         }
161                         
162                         context.Response.ClearContent ();
163                         context.Response.BinaryWrite (c.GetData (), 0, c.ContentLength);
164
165                         context.Response.ClearHeaders ();
166                         context.Response.SetCachedHeaders (c.Headers);
167
168                         context.Response.StatusCode = c.StatusCode;
169                         context.Response.StatusDescription = c.StatusDescription;
170                                 
171                         app.CompleteRequest ();
172                 }
173
174                 void OnUpdateRequestCache (object o, EventArgs args)
175                 {
176                         HttpApplication app = (HttpApplication) o;
177                         HttpContext context = app.Context;
178
179                         if (context.Response.IsCached && context.Response.StatusCode == 200 && 
180                             !context.Trace.IsEnabled)
181                                 DoCacheInsert (context);
182
183                 }
184
185                 void DoCacheInsert (HttpContext context)
186                 {
187                         string vary_key = context.Request.FilePath;
188                         string key;
189                         CachedVaryBy varyby = context.Cache [vary_key] as CachedVaryBy;
190                         CachedRawResponse prev = null;
191                         bool lookup = true;
192 #if NET_2_0
193                         string cacheKey = null, cacheValue = null;
194 #endif
195                         
196                         if (varyby == null) {
197                                 string path = context.Request.MapPath (vary_key);
198                                 string [] files = new string [] { path };
199                                 string [] keys = new string [0];
200                                 varyby = new CachedVaryBy (context.Response.Cache, vary_key);
201                                 context.Cache.Insert (vary_key, varyby,
202                                                               new CacheDependency (files, keys),
203                                                               Cache.NoAbsoluteExpiration,
204                                                               Cache.NoSlidingExpiration,
205                                                               CacheItemPriority.Normal, null);
206                                 lookup = false;
207 #if NET_2_0
208                                 cacheKey = vary_key;
209 #endif
210                         } 
211
212                         key = varyby.CreateKey (vary_key, context);
213
214                         if (lookup)
215                                 prev = context.InternalCache [key] as CachedRawResponse;
216                         
217                         if (prev == null) {
218                                 CachedRawResponse c = context.Response.GetCachedResponse ();
219                                 string [] files = new string [] { };
220                                 string [] keys = new string [] { vary_key };
221                                 bool sliding = context.Response.Cache.Sliding;
222
223                                 context.InternalCache.Insert (key, c, new CacheDependency (files, keys),
224                                                               (sliding ? Cache.NoAbsoluteExpiration :
225                                                                context.Response.Cache.Expires),
226                                                               (sliding ? TimeSpan.FromSeconds (
227                                                                       context.Response.Cache.Duration) :
228                                                                Cache.NoSlidingExpiration),
229                                                               CacheItemPriority.Normal, response_removed);
230                                 c.VaryBy = varyby;
231                                 varyby.ItemList.Add (key);
232 #if NET_2_0
233                                 cacheValue = key;
234 #endif
235                         }
236                         
237 #if NET_2_0
238                         if (cacheKey != null) {
239                                 lock (keysCacheLock) {
240                                         if (!keysCache.ContainsKey (cacheKey))
241                                                 keysCache.Add (cacheKey, cacheValue);
242                                 }
243                         }
244 #endif
245                 }
246
247                 void OnRawResponseRemoved (string key, object value, CacheItemRemovedReason reason)
248                 {
249                         CachedRawResponse c = (CachedRawResponse) value;
250
251                         c.VaryBy.ItemList.Remove (key);                 
252                         if (c.VaryBy.ItemList.Count != 0)
253                                 return;                 
254
255                         HttpRuntime.Cache.Remove (c.VaryBy.Key);
256                 }
257         }
258 }
259