public bool AttemptResponseCaching(ResponseCachingContext context) => AttemptResponseCachingValue;
 public bool IsCachedEntryFresh(ResponseCachingContext context) => IsCachedEntryFreshValue;
 public bool AllowCacheLookup(ResponseCachingContext context) => AllowCacheLookupValue;
 public bool AllowCacheStorage(ResponseCachingContext context) => AllowCacheStorageValue;
 public override bool AllowCacheLookup(ResponseCachingContext context)
 {
     return(true);
 }
Beispiel #6
0
    // BaseKey<delimiter>H<delimiter>HeaderName=HeaderValue<delimiter>Q<delimiter>QueryName=QueryValue1<subdelimiter>QueryValue2
    public string CreateStorageVaryByKey(ResponseCachingContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        var varyByRules = context.CachedVaryByRules;

        if (varyByRules == null)
        {
            throw new InvalidOperationException($"{nameof(CachedVaryByRules)} must not be null on the {nameof(ResponseCachingContext)}");
        }

        if (StringValues.IsNullOrEmpty(varyByRules.Headers) && StringValues.IsNullOrEmpty(varyByRules.QueryKeys))
        {
            return(varyByRules.VaryByKeyPrefix);
        }

        var request = context.HttpContext.Request;
        var builder = _builderPool.Get();

        try
        {
            // Prepend with the Guid of the CachedVaryByRules
            builder.Append(varyByRules.VaryByKeyPrefix);

            // Vary by headers
            var headersCount = varyByRules?.Headers.Count ?? 0;
            if (headersCount > 0)
            {
                // Append a group separator for the header segment of the cache key
                builder.Append(KeyDelimiter)
                .Append('H');

                var requestHeaders = context.HttpContext.Request.Headers;
                for (var i = 0; i < headersCount; i++)
                {
                    var header       = varyByRules !.Headers[i] ?? string.Empty;
                    var headerValues = requestHeaders[header];
                    builder.Append(KeyDelimiter)
                    .Append(header)
                    .Append('=');

                    var headerValuesArray = headerValues.ToArray();
                    Array.Sort(headerValuesArray, StringComparer.Ordinal);

                    for (var j = 0; j < headerValuesArray.Length; j++)
                    {
                        builder.Append(headerValuesArray[j]);
                    }
                }
            }

            // Vary by query keys
            if (varyByRules?.QueryKeys.Count > 0)
            {
                // Append a group separator for the query key segment of the cache key
                builder.Append(KeyDelimiter)
                .Append('Q');

                if (varyByRules.QueryKeys.Count == 1 && string.Equals(varyByRules.QueryKeys[0], "*", StringComparison.Ordinal))
                {
                    // Vary by all available query keys
                    var queryArray = context.HttpContext.Request.Query.ToArray();
                    // Query keys are aggregated case-insensitively whereas the query values are compared ordinally.
                    Array.Sort(queryArray, QueryKeyComparer.OrdinalIgnoreCase);

                    for (var i = 0; i < queryArray.Length; i++)
                    {
                        builder.Append(KeyDelimiter)
                        .AppendUpperInvariant(queryArray[i].Key)
                        .Append('=');

                        var queryValueArray = queryArray[i].Value.ToArray();
                        Array.Sort(queryValueArray, StringComparer.Ordinal);

                        for (var j = 0; j < queryValueArray.Length; j++)
                        {
                            if (j > 0)
                            {
                                builder.Append(KeySubDelimiter);
                            }

                            builder.Append(queryValueArray[j]);
                        }
                    }
                }
                else
                {
                    for (var i = 0; i < varyByRules.QueryKeys.Count; i++)
                    {
                        var queryKey       = varyByRules.QueryKeys[i] ?? string.Empty;
                        var queryKeyValues = context.HttpContext.Request.Query[queryKey];
                        builder.Append(KeyDelimiter)
                        .Append(queryKey)
                        .Append('=');

                        var queryValueArray = queryKeyValues.ToArray();
                        Array.Sort(queryValueArray, StringComparer.Ordinal);

                        for (var j = 0; j < queryValueArray.Length; j++)
                        {
                            if (j > 0)
                            {
                                builder.Append(KeySubDelimiter);
                            }

                            builder.Append(queryValueArray[j]);
                        }
                    }
                }
            }

            return(builder.ToString());
        }
        finally
        {
            _builderPool.Return(builder);
        }
    }
 public bool AllowCacheStorage(ResponseCachingContext context)
 {
     // Check request no-store
     return(!HeaderUtilities.ContainsCacheDirective(context.HttpContext.Request.Headers.CacheControl, CacheControlHeaderValue.NoStoreString));
 }
 public string CreateStorageVaryByKey(ResponseCachingContext context)
 {
     throw new NotImplementedException();
 }
    internal async Task <bool> TryServeCachedResponseAsync(ResponseCachingContext context, IResponseCacheEntry?cacheEntry)
    {
        if (!(cacheEntry is CachedResponse cachedResponse))
        {
            return(false);
        }

        context.CachedResponse        = cachedResponse;
        context.CachedResponseHeaders = cachedResponse.Headers;
        context.ResponseTime          = _options.SystemClock.UtcNow;
        var cachedEntryAge = context.ResponseTime.Value - context.CachedResponse.Created;

        context.CachedEntryAge = cachedEntryAge > TimeSpan.Zero ? cachedEntryAge : TimeSpan.Zero;

        if (_policyProvider.IsCachedEntryFresh(context))
        {
            // Check conditional request rules
            if (ContentIsNotModified(context))
            {
                _logger.NotModifiedServed();
                context.HttpContext.Response.StatusCode = StatusCodes.Status304NotModified;

                if (context.CachedResponseHeaders != null)
                {
                    foreach (var key in HeadersToIncludeIn304)
                    {
                        if (context.CachedResponseHeaders.TryGetValue(key, out var values))
                        {
                            context.HttpContext.Response.Headers[key] = values;
                        }
                    }
                }
            }
            else
            {
                var response = context.HttpContext.Response;
                // Copy the cached status code and response headers
                response.StatusCode = context.CachedResponse.StatusCode;
                foreach (var header in context.CachedResponse.Headers)
                {
                    response.Headers[header.Key] = header.Value;
                }

                // Note: int64 division truncates result and errors may be up to 1 second. This reduction in
                // accuracy of age calculation is considered appropriate since it is small compared to clock
                // skews and the "Age" header is an estimate of the real age of cached content.
                response.Headers.Age = HeaderUtilities.FormatNonNegativeInt64(context.CachedEntryAge.Value.Ticks / TimeSpan.TicksPerSecond);

                // Copy the cached response body
                var body = context.CachedResponse.Body;
                if (body.Length > 0)
                {
                    try
                    {
                        await body.CopyToAsync(response.BodyWriter, context.HttpContext.RequestAborted);
                    }
                    catch (OperationCanceledException)
                    {
                        context.HttpContext.Abort();
                    }
                }
                _logger.CachedResponseServed();
            }
            return(true);
        }

        return(false);
    }
    public bool IsCachedEntryFresh(ResponseCachingContext context)
    {
        var age = context.CachedEntryAge !.Value;
        var cachedCacheControlHeaders  = context.CachedResponseHeaders.CacheControl;
        var requestCacheControlHeaders = context.HttpContext.Request.Headers.CacheControl;

        // Add min-fresh requirements
        if (HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MinFreshString, out var minFresh))
        {
            age += minFresh.Value;
            context.Logger.ExpirationMinFreshAdded(minFresh.Value);
        }

        // Validate shared max age, this overrides any max age settings for shared caches
        TimeSpan?cachedSharedMaxAge;

        HeaderUtilities.TryParseSeconds(cachedCacheControlHeaders, CacheControlHeaderValue.SharedMaxAgeString, out cachedSharedMaxAge);

        if (age >= cachedSharedMaxAge)
        {
            // shared max age implies must revalidate
            context.Logger.ExpirationSharedMaxAgeExceeded(age, cachedSharedMaxAge.Value);
            return(false);
        }
        else if (!cachedSharedMaxAge.HasValue)
        {
            TimeSpan?requestMaxAge;
            HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MaxAgeString, out requestMaxAge);

            TimeSpan?cachedMaxAge;
            HeaderUtilities.TryParseSeconds(cachedCacheControlHeaders, CacheControlHeaderValue.MaxAgeString, out cachedMaxAge);

            var lowestMaxAge = cachedMaxAge < requestMaxAge ? cachedMaxAge : requestMaxAge ?? cachedMaxAge;
            // Validate max age
            if (age >= lowestMaxAge)
            {
                // Must revalidate or proxy revalidate
                if (HeaderUtilities.ContainsCacheDirective(cachedCacheControlHeaders, CacheControlHeaderValue.MustRevalidateString) ||
                    HeaderUtilities.ContainsCacheDirective(cachedCacheControlHeaders, CacheControlHeaderValue.ProxyRevalidateString))
                {
                    context.Logger.ExpirationMustRevalidate(age, lowestMaxAge.Value);
                    return(false);
                }

                TimeSpan?requestMaxStale;
                var      maxStaleExist = HeaderUtilities.ContainsCacheDirective(requestCacheControlHeaders, CacheControlHeaderValue.MaxStaleString);
                HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MaxStaleString, out requestMaxStale);

                // Request allows stale values with no age limit
                if (maxStaleExist && !requestMaxStale.HasValue)
                {
                    context.Logger.ExpirationInfiniteMaxStaleSatisfied(age, lowestMaxAge.Value);
                    return(true);
                }

                // Request allows stale values with age limit
                if (requestMaxStale.HasValue && age - lowestMaxAge < requestMaxStale)
                {
                    context.Logger.ExpirationMaxStaleSatisfied(age, lowestMaxAge.Value, requestMaxStale.Value);
                    return(true);
                }

                context.Logger.ExpirationMaxAgeExceeded(age, lowestMaxAge.Value);
                return(false);
            }
            else if (!cachedMaxAge.HasValue && !requestMaxAge.HasValue)
            {
                // Validate expiration
                DateTimeOffset expires;
                if (HeaderUtilities.TryParseDate(context.CachedResponseHeaders.Expires.ToString(), out expires) &&
                    context.ResponseTime !.Value >= expires)
                {
                    context.Logger.ExpirationExpiresExceeded(context.ResponseTime.Value, expires);
                    return(false);
                }
            }
        }

        return(true);
    }
 /// <summary>
 /// 默认的基于ResourceFilter的缓存过滤Filter
 /// </summary>
 /// <param name="context"></param>
 /// <param name="cachingDiagnosticsAccessor"></param>
 public DefaultResourceCacheFilter(ResponseCachingContext context,
                                   CachingDiagnosticsAccessor cachingDiagnosticsAccessor)
     : base(context, cachingDiagnosticsAccessor)
 {
 }
Beispiel #12
0
 public bool IsCachedEntryFresh(ResponseCachingContext context)
 {
     return((context.ResponseTime.Value - context.CachedEntryAge.Value) >= LastPostUpdate);
 }
Beispiel #13
0
 public bool AllowCacheStorage(ResponseCachingContext context)
 {
     // cache-control: no-store ?
     return(!HeaderUtilities.ContainsCacheDirective(context.HttpContext.Request.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.NoStoreString));
 }
Beispiel #14
0
 public bool IsResponseCacheable(ResponseCachingContext context) => IsResponseCacheableValue;
    public bool IsResponseCacheable(ResponseCachingContext context)
    {
        var responseCacheControlHeader = context.HttpContext.Response.Headers.CacheControl;

        // Only cache pages explicitly marked with public
        if (!HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.PublicString))
        {
            context.Logger.ResponseWithoutPublicNotCacheable();
            return(false);
        }

        // Check response no-store
        if (HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.NoStoreString))
        {
            context.Logger.ResponseWithNoStoreNotCacheable();
            return(false);
        }

        // Check no-cache
        if (HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.NoCacheString))
        {
            context.Logger.ResponseWithNoCacheNotCacheable();
            return(false);
        }

        var response = context.HttpContext.Response;

        // Do not cache responses with Set-Cookie headers
        if (!StringValues.IsNullOrEmpty(response.Headers.SetCookie))
        {
            context.Logger.ResponseWithSetCookieNotCacheable();
            return(false);
        }

        // Do not cache responses varying by *
        var varyHeader = response.Headers.Vary;

        if (varyHeader.Count == 1 && string.Equals(varyHeader, "*", StringComparison.OrdinalIgnoreCase))
        {
            context.Logger.ResponseWithVaryStarNotCacheable();
            return(false);
        }

        // Check private
        if (HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.PrivateString))
        {
            context.Logger.ResponseWithPrivateNotCacheable();
            return(false);
        }

        // Check response code
        if (response.StatusCode != StatusCodes.Status200OK)
        {
            context.Logger.ResponseWithUnsuccessfulStatusCodeNotCacheable(response.StatusCode);
            return(false);
        }

        // Check response freshness
        if (!context.ResponseDate.HasValue)
        {
            if (!context.ResponseSharedMaxAge.HasValue &&
                !context.ResponseMaxAge.HasValue &&
                context.ResponseTime !.Value >= context.ResponseExpires)
            {
                context.Logger.ExpirationExpiresExceeded(context.ResponseTime.Value, context.ResponseExpires.Value);
                return(false);
            }
        }
        else
        {
            var age = context.ResponseTime !.Value - context.ResponseDate.Value;

            // Validate shared max age
            if (age >= context.ResponseSharedMaxAge)
            {
                context.Logger.ExpirationSharedMaxAgeExceeded(age, context.ResponseSharedMaxAge.Value);
                return(false);
            }
            else if (!context.ResponseSharedMaxAge.HasValue)
            {
                // Validate max age
                if (age >= context.ResponseMaxAge)
                {
                    context.Logger.ExpirationMaxAgeExceeded(age, context.ResponseMaxAge.Value);
                    return(false);
                }
                else if (!context.ResponseMaxAge.HasValue)
                {
                    // Validate expiration
                    if (context.ResponseTime.Value >= context.ResponseExpires)
                    {
                        context.Logger.ExpirationExpiresExceeded(context.ResponseTime.Value, context.ResponseExpires.Value);
                        return(false);
                    }
                }
            }
        }

        return(true);
    }
Beispiel #16
0
 public string CreateBaseKey(ResponseCachingContext context)
 {
     return(_baseKey);
 }
Beispiel #17
0
 public IEnumerable <string> CreateLookupVaryByKeys(ResponseCachingContext context)
 {
     return(new string[] { CreateStorageVaryByKey(context) });
 }
Beispiel #18
0
        /// <summary>
        /// Finalize cache headers.
        /// </summary>
        /// <param name="context"></param>
        /// <returns><c>true</c> if a vary by entry needs to be stored in the cache; otherwise <c>false</c>.</returns>
        private bool OnFinalizeCacheHeaders(ResponseCachingContext context)
        {
            if (_policyProvider.IsResponseCacheable(context))
            {
                var storeVaryByEntry = false;
                context.ShouldCacheResponse = true;

                // Create the cache entry now
                var response      = context.HttpContext.Response;
                var varyHeaders   = new StringValues(response.Headers.GetCommaSeparatedValues(HeaderNames.Vary));
                var varyQueryKeys = new StringValues(context.HttpContext.Features.Get <Microsoft.AspNetCore.ResponseCaching.IResponseCachingFeature>()?.VaryByQueryKeys);
                context.CachedResponseValidFor = context.ResponseSharedMaxAge ??
                                                 context.ResponseMaxAge ??
                                                 (context.ResponseExpires - context.ResponseTime.Value) ??
                                                 DefaultExpirationTimeSpan;

                // Generate a base key if none exist
                if (string.IsNullOrEmpty(context.BaseKey))
                {
                    context.BaseKey = _keyProvider.CreateBaseKey(context);
                }

                // Check if any vary rules exist
                if (!StringValues.IsNullOrEmpty(varyHeaders) || !StringValues.IsNullOrEmpty(varyQueryKeys))
                {
                    // Normalize order and casing of vary by rules
                    var normalizedVaryHeaders   = GetOrderCasingNormalizedStringValues(varyHeaders);
                    var normalizedVaryQueryKeys = GetOrderCasingNormalizedStringValues(varyQueryKeys);

                    // Update vary rules if they are different
                    if (context.CachedVaryByRules == null ||
                        !StringValues.Equals(context.CachedVaryByRules.QueryKeys, normalizedVaryQueryKeys) ||
                        !StringValues.Equals(context.CachedVaryByRules.Headers, normalizedVaryHeaders))
                    {
                        context.CachedVaryByRules = new CachedVaryByRules
                        {
                            VaryByKeyPrefix = FastGuid.NewGuid().IdString,
                            Headers         = normalizedVaryHeaders,
                            QueryKeys       = normalizedVaryQueryKeys
                        };
                    }

                    // Always overwrite the CachedVaryByRules to update the expiry information
                    _logger.LogVaryByRulesUpdated(normalizedVaryHeaders, normalizedVaryQueryKeys);
                    storeVaryByEntry = true;

                    context.StorageVaryKey = _keyProvider.CreateStorageVaryByKey(context);
                }

                // Ensure date header is set
                if (!context.ResponseDate.HasValue)
                {
                    context.ResponseDate = context.ResponseTime.Value;
                    // Setting the date on the raw response headers.
                    context.HttpContext.Response.Headers[HeaderNames.Date] = HeaderUtilities.FormatDate(context.ResponseDate.Value);
                }

                // Store the response on the state
                context.CachedResponse = new CachedResponse
                {
                    Created    = context.ResponseDate.Value,
                    StatusCode = context.HttpContext.Response.StatusCode,
                    Headers    = new HeaderDictionary()
                };

                foreach (var header in context.HttpContext.Response.Headers)
                {
                    if (!string.Equals(header.Key, HeaderNames.Age, StringComparison.OrdinalIgnoreCase))
                    {
                        context.CachedResponse.Headers[header.Key] = header.Value;
                    }
                }

                return(storeVaryByEntry);
            }

            context.ResponseCachingStream.DisableBuffering();
            return(false);
        }
 public override bool AllowCacheStorage(ResponseCachingContext context)
 {
     return(true);
 }