コード例 #1
0
        /*
         * 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();
        }
コード例 #2
0
        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());
        }