public virtual bool IsResponseCacheable(ResponseCachingContext context) { var responseCacheControlHeader = context.HttpContext.Response.Headers[HeaderNames.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[HeaderNames.SetCookie])) { context.Logger.ResponseWithSetCookieNotCacheable(); return(false); } // Do not cache responses varying by * var varyHeader = response.Headers[HeaderNames.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 virtual bool AllowCacheStorage(ResponseCachingContext context) { // Check request no-store return(!HeaderUtilities.ContainsCacheDirective(context.HttpContext.Request.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.NoStoreString)); }
// 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 if (varyByRules?.Headers.Count > 0) { // Append a group separator for the header segment of the cache key builder.Append(KeyDelimiter) .Append('H'); for (var i = 0; i < varyByRules.Headers.Count; i++) { var header = varyByRules.Headers[i]; var headerValues = context.HttpContext.Request.Headers[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]; 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 virtual bool IsCachedEntryFresh(ResponseCachingContext context) { var age = context.CachedEntryAge.Value; var cachedCacheControlHeaders = context.CachedResponseHeaders[HeaderNames.CacheControl]; var requestCacheControlHeaders = context.HttpContext.Request.Headers[HeaderNames.CacheControl]; // Add min-fresh requirements TimeSpan?minFresh; if (HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MinFreshString, out 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[HeaderNames.Expires].ToString(), out expires) && context.ResponseTime.Value >= expires) { context.Logger.ExpirationExpiresExceeded(context.ResponseTime.Value, expires); return(false); } } } return(true); }
public IEnumerable <string> CreateLookupVaryByKeys(ResponseCachingContext context) { return(new string[] { CreateStorageVaryByKey(context) }); }