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); }
// 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) { }
public bool IsCachedEntryFresh(ResponseCachingContext context) { return((context.ResponseTime.Value - context.CachedEntryAge.Value) >= LastPostUpdate); }
public bool AllowCacheStorage(ResponseCachingContext context) { // cache-control: no-store ? return(!HeaderUtilities.ContainsCacheDirective(context.HttpContext.Request.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.NoStoreString)); }
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); }
public string CreateBaseKey(ResponseCachingContext context) { return(_baseKey); }
public IEnumerable <string> CreateLookupVaryByKeys(ResponseCachingContext context) { return(new string[] { CreateStorageVaryByKey(context) }); }
/// <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); }