/* * Try to find this request in the cache. If so, return it. Otherwise, * store the cache key for use on Leave. */ /// <include file='doc\OutputCacheModule.uex' path='docs/doc[@for="OutputCacheModule.OnEnter"]/*' /> /// <devdoc> /// <para>Raises the <see langword='Enter'/> /// event, which searches the output cache for an item to satisfy the HTTP request. </para> /// </devdoc> internal /*public*/ void OnEnter(Object source, EventArgs eventArgs) { HttpApplication app; HttpContext context; string key; HttpRequest request; HttpResponse response; Object item; CachedRawResponse cachedRawResponse; HttpCachePolicySettings settings; int i, n; bool sendBody; HttpValidationStatus validationStatus, validationStatusFinal; ValidationCallbackInfo callbackInfo; string ifModifiedSinceHeader; DateTime utcIfModifiedSince; string etag; string[] etags; int send304; string cacheControl; string[] cacheDirectives = null; string pragma; string[] pragmaDirectives = null; string directive; int maxage; int minfresh; int age; int fresh; bool hasValidationPolicy; CachedVary cachedVary; CacheInternal cacheInternal; Debug.Trace("OutputCacheModuleEnter", "Beginning OutputCacheModule::Enter"); app = (HttpApplication)source; context = app.Context; request = context.Request; response = context.Response; _key = null; _recordedCacheMiss = false; _method = CacheRequestMethod.Invalid; /* * Check if the request can be resolved for this method. */ switch (request.HttpMethod) { case "HEAD": _method = CacheRequestMethod.Head; break; case "GET": _method = CacheRequestMethod.Get; break; case "POST": _method = CacheRequestMethod.Post; break; default: Debug.Trace("OutputCacheModuleEnter", "Output cache miss, Http method not GET, POST, or HEAD" + "\nReturning from OutputCacheModule::Enter"); return; } /* * Create a lookup key. Remember the key for use inside Leave() */ _key = key = CreateOutputCachedItemKey(context, null); Debug.Assert(_key != null, "_key != null"); /* * Lookup the cached item. */ cacheInternal = HttpRuntime.CacheInternal; item = cacheInternal.Get(key); if (item == null) { Debug.Trace("OutputCacheModuleEnter", "Output cache miss, item not found.\n\tkey=" + key + "\nReturning from OutputCacheModule::Enter"); return; } cachedVary = item as CachedVary; if (cachedVary != null) { /* * This cached output has a Vary policy. Create a new key based * on the vary headers in cachedRawResponse and try again. */ Debug.Trace("OutputCacheModuleEnter", "Output cache found CachedVary, \n\tkey=" + key); key = CreateOutputCachedItemKey(context, cachedVary); if (key == null) { Debug.Trace("OutputCacheModuleEnter", "Output cache miss, key could not be created for vary-by item." + "\nReturning from OutputCacheModule::Enter"); return; } item = cacheInternal.Get(key); if (item == null) { Debug.Trace("OutputCacheModuleEnter", "Output cache miss, item not found.\n\tkey=" + key + "\nReturning from OutputCacheModule::Enter"); return; } } Debug.Assert(item.GetType() == typeof(CachedRawResponse), "item.GetType() == typeof(CachedRawResponse)"); cachedRawResponse = (CachedRawResponse)item; settings = cachedRawResponse._settings; if (cachedVary == null && !settings.IgnoreParams) { /* * This cached output has no vary policy, so make sure it doesn't have a query string or form post. */ if (_method == CacheRequestMethod.Post) { Debug.Trace("OutputCacheModuleEnter", "Output cache item found but method is POST and no VaryByParam specified." + "\n\tkey=" + key + "\nReturning from OutputCacheModule::Enter"); RecordCacheMiss(); return; } string queryStringText = request.QueryStringText; if (queryStringText != null && queryStringText.Length > 0) { Debug.Trace("OutputCacheModuleEnter", "Output cache item found but contains a querystring and no VaryByParam specified." + "\n\tkey=" + key + "\nReturning from OutputCacheModule::Enter"); RecordCacheMiss(); return; } } hasValidationPolicy = settings.HasValidationPolicy(); /* * Determine whether the client can accept a cached copy, and * get values of other cache control directives. * * We do this after lookup so we don't have to crack the headers * if the item is not found. Cracking the headers is expensive. */ if (!hasValidationPolicy) { cacheControl = request.Headers["Cache-Control"]; if (cacheControl != null) { cacheDirectives = cacheControl.Split(s_fieldSeparators); for (i = 0; i < cacheDirectives.Length; i++) { directive = cacheDirectives[i]; if (directive == "no-cache") { Debug.Trace("OutputCacheModuleEnter", "Skipping lookup because of Cache-Control: no-cache directive." + "\nReturning from OutputCacheModule::Enter"); RecordCacheMiss(); return; } if (directive.StartsWith("max-age=")) { try { maxage = Convert.ToInt32(directive.Substring(8)); } catch { maxage = -1; } if (maxage >= 0) { age = (int)((context.UtcTimestamp.Ticks - settings.UtcTimestampCreated.Ticks) / TimeSpan.TicksPerSecond); if (age >= maxage) { Debug.Trace("OutputCacheModuleEnter", "Not returning found item due to Cache-Control: max-age directive." + "\nReturning from OutputCacheModule::Enter"); RecordCacheMiss(); return; } } } else if (directive.StartsWith("min-fresh=")) { try { minfresh = Convert.ToInt32(directive.Substring(10)); } catch { minfresh = -1; } if (minfresh >= 0 && settings.IsExpiresSet && !settings.SlidingExpiration) { fresh = (int)((settings.UtcExpires.Ticks - context.UtcTimestamp.Ticks) / TimeSpan.TicksPerSecond); if (fresh < minfresh) { Debug.Trace("OutputCacheModuleEnter", "Not returning found item due to Cache-Control: min-fresh directive." + "\nReturning from OutputCacheModule::Enter"); RecordCacheMiss(); return; } } } } } pragma = request.Headers["Pragma"]; if (pragma != null) { pragmaDirectives = pragma.Split(s_fieldSeparators); for (i = 0; i < pragmaDirectives.Length; i++) { if (pragmaDirectives[i] == "no-cache") { Debug.Trace("OutputCacheModuleEnter", "Skipping lookup because of Pragma: no-cache directive." + "\nReturning from OutputCacheModule::Enter"); RecordCacheMiss(); return; } } } } else if (settings.ValidationCallbackInfo != null) { /* * Check if the item is still valid. */ validationStatus = HttpValidationStatus.Valid; validationStatusFinal = validationStatus; for (i = 0, n = settings.ValidationCallbackInfo.Length; i < n; i++) { callbackInfo = settings.ValidationCallbackInfo[i]; try { callbackInfo.handler(context, callbackInfo.data, ref validationStatus); } catch (Exception e) { validationStatus = HttpValidationStatus.Invalid; HttpApplicationFactory.RaiseError(e); } switch (validationStatus) { case HttpValidationStatus.Invalid: Debug.Trace("OutputCacheModuleEnter", "Output cache item found but callback invalidated it." + "\n\tkey=" + key + "\nReturning from OutputCacheModule::Enter"); cacheInternal.Remove(key); RecordCacheMiss(); return; case HttpValidationStatus.IgnoreThisRequest: validationStatusFinal = HttpValidationStatus.IgnoreThisRequest; break; case HttpValidationStatus.Valid: break; default: Debug.Trace("OutputCacheModuleEnter", "Invalid validation status, ignoring it, status=" + validationStatus + "\n\tkey=" + key); validationStatus = validationStatusFinal; break; } } if (validationStatusFinal == HttpValidationStatus.IgnoreThisRequest) { Debug.Trace("OutputCacheModuleEnter", "Output cache item found but callback status is IgnoreThisRequest." + "\n\tkey=" + key + "\nReturning from OutputCacheModule::Enter"); RecordCacheMiss(); return; } Debug.Assert(validationStatusFinal == HttpValidationStatus.Valid, "validationStatusFinal == HttpValidationStatus.Valid"); } /* * Try to satisfy a conditional request. The cached response * must satisfy all conditions that are present. * * We can only satisfy a conditional request if the response * is buffered and has no substitution blocks. * * N.B. RFC 2616 says conditional requests only occur * with the GET method, but we try to satisfy other * verbs (HEAD, POST) as well. */ send304 = -1; if (response.IsBuffered() && !cachedRawResponse._rawResponse.HasSubstBlocks) { /* Check "If-Modified-Since" header */ ifModifiedSinceHeader = request.IfModifiedSince; if (ifModifiedSinceHeader != null) { send304 = 0; try { utcIfModifiedSince = HttpDate.UtcParse(ifModifiedSinceHeader); if (settings.IsLastModifiedSet && settings.UtcLastModified <= utcIfModifiedSince && utcIfModifiedSince <= context.UtcTimestamp) { send304 = 1; } } catch { Debug.Trace("OutputCacheModuleEnter", "Ignore If-Modified-Since header, invalid format: " + ifModifiedSinceHeader); } } /* Check "If-None-Match" header */ if (send304 != 0) { etag = request.IfNoneMatch; if (etag != null) { send304 = 0; etags = etag.Split(s_fieldSeparators); for (i = 0, n = etags.Length; i < n; i++) { if (i == 0 && etags[i].Equals("*")) { send304 = 1; break; } if (etags[i].Equals(settings.ETag)) { send304 = 1; break; } } } } } if (send304 == 1) { /* * Send 304 Not Modified */ Debug.Trace("OutputCacheModuleEnter", "Output cache hit & conditional request satisfied, status=304." + "\n\tkey=" + key + "\nReturning from OutputCacheModule::Enter"); response.ClearAll(); response.StatusCode = 304; } else { /* * Send the full response. */ #if DBG if (send304 == -1) { Debug.Trace("OutputCacheModuleEnter", "Output cache hit.\n\tkey=" + key + "\nReturning from OutputCacheModule::Enter"); } else { Debug.Trace("OutputCacheModuleEnter", "Output cache hit but conditional request not satisfied.\n\tkey=" + key + "\nReturning from OutputCacheModule::Enter"); } #endif sendBody = (_method != CacheRequestMethod.Head); // UseSnapshot calls ClearAll response.UseSnapshot(cachedRawResponse._rawResponse, sendBody); } response.Cache.ResetFromHttpCachePolicySettings(settings, context.UtcTimestamp); PerfCounters.IncrementCounter(AppPerfCounter.OUTPUT_CACHE_RATIO_BASE); PerfCounters.IncrementCounter(AppPerfCounter.OUTPUT_CACHE_HITS); _key = null; _recordedCacheMiss = false; _method = CacheRequestMethod.Invalid; app.CompleteRequest(); }
internal static string CreateOutputCachedItemKey( string path, CacheRequestMethod method, HttpContext context, CachedVary cachedVary) { StringBuilder sb; int i, j, n; string name, value; string[] a; byte[] buf, hash; HttpRequest request; NameValueCollection col; int contentLength; if (method == CacheRequestMethod.Post) { sb = new StringBuilder("System.Web.Http.HttpRawResponse\nM=3\n"); } else { sb = new StringBuilder("System.Web.Http.HttpRawResponse\nM=2\n"); } sb.Append(CultureInfo.InvariantCulture.TextInfo.ToLower(path)); /* key for cached vary item has additional information */ if (cachedVary != null) { request = context.Request; /* params part */ for (j = 0; j <= 2; j++) { switch (j) { case 0: sb.Append("\nVH"); a = cachedVary._headers; col = request.ServerVariables; break; case 1: sb.Append("\nVPQ"); a = cachedVary._params; col = request.QueryString; break; case 2: default: sb.Append("\nVPF"); a = cachedVary._params; col = request.Form; if (method != CacheRequestMethod.Post) { col = null; } break; } if (col == null) { continue; } /* handle all params case (VaryByParams[*] = true) */ if (a == null && cachedVary._varyByAllParams && j != 0) { a = col.AllKeys; for (i = a.Length - 1; i >= 0; i--) { if (a[i] != null) { a[i] = CultureInfo.InvariantCulture.TextInfo.ToLower(a[i]); } } Array.Sort(a, InvariantComparer.Default); } if (a != null) { for (i = 0, n = a.Length; i < n; i++) { name = a[i]; value = col[name]; if (value == null) { value = NULL_VARYBY_VALUE; } sb.Append("\tPN:"); sb.Append(name); sb.Append("\tPV:"); sb.Append(value); } } } /* custom string part */ sb.Append("\nVC"); if (cachedVary._varyByCustom != null) { sb.Append("\tCN:"); sb.Append(cachedVary._varyByCustom); sb.Append("\tCV:"); try { value = context.ApplicationInstance.GetVaryByCustomString( context, cachedVary._varyByCustom); if (value == null) { value = NULL_VARYBY_VALUE; } } catch (Exception e) { value = ERROR_VARYBY_VALUE; HttpApplicationFactory.RaiseError(e); } sb.Append(value); } /* * if VaryByParms=*, and method is not a form, then * use a cryptographically strong hash of the data as * part of the key. */ sb.Append("\nVPD"); if (method == CacheRequestMethod.Post && cachedVary._varyByAllParams && request.Form.Count == 0) { contentLength = request.ContentLength; if (contentLength > MAX_POST_KEY_LENGTH || contentLength < 0) { return(null); } if (contentLength > 0) { buf = ((HttpInputStream)request.InputStream).Data; if (buf == null) { return(null); } hash = MachineKey.HashData(buf, s_hashModifier, 0, buf.Length); value = Convert.ToBase64String(hash); sb.Append(value); } } sb.Append("\nEOV"); } return(sb.ToString()); }