-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Sync output cache with API review #43121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3d0201b
dd692b5
9638b39
08d83ad
f860adf
ca649e4
5c81ab1
568c87c
83abb45
64fd032
0e70c1e
4c80ace
c7d718c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Globalization; | ||
using System.Linq; | ||
using System.Text; | ||
using Microsoft.Extensions.ObjectPool; | ||
|
@@ -28,7 +29,7 @@ internal OutputCacheKeyProvider(ObjectPoolProvider poolProvider, IOptions<Output | |
_options = options.Value; | ||
} | ||
|
||
// GET<delimiter>SCHEME<delimiter>HOST:PORT/PATHBASE/PATH<delimiter>H<delimiter>HeaderName=HeaderValue<delimiter>Q<delimiter>QueryName=QueryValue1<subdelimiter>QueryValue2 | ||
// GET<delimiter>SCHEME<delimiter>HOST:PORT/PATHBASE/PATH<delimiter>H<delimiter>HeaderName=HeaderValue<delimiter>Q<delimiter>QueryName=QueryValue1<subdelimiter>QueryValue2<delimiter>R<delimiter>RouteName1=RouteValue1<subdelimiter>RouteName2=RouteValue2 | ||
public string CreateStorageKey(OutputCacheContext context) | ||
{ | ||
ArgumentNullException.ThrowIfNull(_builderPool); | ||
|
@@ -79,8 +80,8 @@ public string CreateStorageKey(OutputCacheContext context) | |
} | ||
} | ||
|
||
// Vary by headers | ||
var headersCount = varyByRules?.Headers.Count ?? 0; | ||
// Vary by header names | ||
var headersCount = varyByRules?.HeaderNames.Count ?? 0; | ||
if (headersCount > 0) | ||
{ | ||
// Append a group separator for the header segment of the cache key | ||
|
@@ -90,7 +91,7 @@ public string CreateStorageKey(OutputCacheContext context) | |
var requestHeaders = context.HttpContext.Request.Headers; | ||
for (var i = 0; i < headersCount; i++) | ||
{ | ||
var header = varyByRules!.Headers[i] ?? string.Empty; | ||
var header = varyByRules!.HeaderNames[i] ?? string.Empty; | ||
var headerValues = requestHeaders[header]; | ||
builder.Append(KeyDelimiter) | ||
.Append(header) | ||
|
@@ -166,6 +167,29 @@ public string CreateStorageKey(OutputCacheContext context) | |
} | ||
} | ||
|
||
// Vary by route value names | ||
var routeValueNamesCount = varyByRules?.RouteValueNames.Count ?? 0; | ||
if (routeValueNamesCount > 0) | ||
{ | ||
// Append a group separator for the route values segment of the cache key | ||
builder.Append(KeyDelimiter) | ||
.Append('R'); | ||
|
||
for (var i = 0; i < routeValueNamesCount; i++) | ||
{ | ||
// The lookup key can't be null | ||
var routeValueName = varyByRules!.RouteValueNames[i] ?? string.Empty; | ||
|
||
// RouteValueNames returns null if the key doesn't exist | ||
var routeValueValue = context.HttpContext.Request.RouteValues[routeValueName]; | ||
|
||
builder.Append(KeyDelimiter) | ||
.Append(routeValueName) | ||
.Append('=') | ||
.Append(Convert.ToString(routeValueValue, CultureInfo.InvariantCulture)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A route value is an |
||
} | ||
} | ||
|
||
return builder.ToString(); | ||
} | ||
finally | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -92,7 +92,7 @@ public Task Invoke(HttpContext httpContext) | |
|
||
private async Task InvokeAwaited(HttpContext httpContext, IReadOnlyList<IOutputCachePolicy> policies) | ||
{ | ||
var context = new OutputCacheContext(httpContext, _store, _options, _logger); | ||
var context = new OutputCacheContext { HttpContext = httpContext }; | ||
|
||
// Add IOutputCacheFeature | ||
AddOutputCacheFeature(context); | ||
|
@@ -247,7 +247,7 @@ internal async Task<bool> TryServeCachedResponseAsync(OutputCacheContext context | |
// Validate expiration | ||
if (context.CachedEntryAge <= TimeSpan.Zero) | ||
{ | ||
context.Logger.ExpirationExpiresExceeded(context.ResponseTime!.Value); | ||
_logger.ExpirationExpiresExceeded(context.ResponseTime!.Value); | ||
context.IsCacheEntryFresh = false; | ||
} | ||
|
||
|
@@ -316,7 +316,7 @@ internal async Task<bool> TryServeFromCacheAsync(OutputCacheContext cacheContext | |
// TODO: should it be part of the cache implementations or can we assume all caches would benefit from it? | ||
// It makes sense for caches that use IO (disk, network) or need to deserialize the state but could also be a global option | ||
|
||
var cacheEntry = await _outputCacheEntryDispatcher.ScheduleAsync(cacheContext.CacheKey, cacheContext, static async (key, cacheContext) => await OutputCacheEntryFormatter.GetAsync(key, cacheContext.Store, cacheContext.HttpContext.RequestAborted)); | ||
var cacheEntry = await _outputCacheEntryDispatcher.ScheduleAsync(cacheContext.CacheKey, (Store: _store, CacheContext: cacheContext), static async (key, state) => await OutputCacheEntryFormatter.GetAsync(key, state.Store, state.CacheContext.HttpContext.RequestAborted)); | ||
|
||
if (await TryServeCachedResponseAsync(cacheContext, cacheEntry, policies)) | ||
{ | ||
|
@@ -341,32 +341,33 @@ internal void CreateCacheKey(OutputCacheContext context) | |
return; | ||
} | ||
|
||
var varyHeaders = context.CacheVaryByRules.Headers; | ||
var varyHeaderNames = context.CacheVaryByRules.HeaderNames; | ||
var varyRouteValueNames = context.CacheVaryByRules.RouteValueNames; | ||
var varyQueryKeys = context.CacheVaryByRules.QueryKeys; | ||
var varyByCustomKeys = context.CacheVaryByRules.VaryByCustom; | ||
var varyByCustomKeys = context.CacheVaryByRules.HasVaryByCustom ? context.CacheVaryByRules.VaryByCustom : null; | ||
var varyByPrefix = context.CacheVaryByRules.VaryByPrefix; | ||
|
||
// Check if any vary rules exist | ||
if (!StringValues.IsNullOrEmpty(varyHeaders) || !StringValues.IsNullOrEmpty(varyQueryKeys) || !StringValues.IsNullOrEmpty(varyByPrefix) || varyByCustomKeys?.Count > 0) | ||
if (!StringValues.IsNullOrEmpty(varyHeaderNames) || !StringValues.IsNullOrEmpty(varyRouteValueNames) || !StringValues.IsNullOrEmpty(varyQueryKeys) || !StringValues.IsNullOrEmpty(varyByPrefix) || varyByCustomKeys?.Count > 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line is pretty long, may be better to wrap it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have another PR that will remove it, so I will ignore it for now (if I may). |
||
{ | ||
// Normalize order and casing of vary by rules | ||
var normalizedVaryHeaders = GetOrderCasingNormalizedStringValues(varyHeaders); | ||
var normalizedVaryHeaderNames = GetOrderCasingNormalizedStringValues(varyHeaderNames); | ||
var normalizedVaryRouteValueNames = GetOrderCasingNormalizedStringValues(varyRouteValueNames); | ||
var normalizedVaryQueryKeys = GetOrderCasingNormalizedStringValues(varyQueryKeys); | ||
var normalizedVaryByCustom = GetOrderCasingNormalizedDictionary(varyByCustomKeys); | ||
|
||
// Update vary rules with normalized values | ||
context.CacheVaryByRules = new CacheVaryByRules | ||
{ | ||
VaryByPrefix = varyByPrefix + normalizedVaryByCustom, | ||
Headers = normalizedVaryHeaders, | ||
QueryKeys = normalizedVaryQueryKeys | ||
}; | ||
context.CacheVaryByRules.VaryByCustom.Clear(); | ||
context.CacheVaryByRules.VaryByPrefix = varyByPrefix + normalizedVaryByCustom; | ||
context.CacheVaryByRules.HeaderNames = normalizedVaryHeaderNames; | ||
context.CacheVaryByRules.RouteValueNames = normalizedVaryRouteValueNames; | ||
context.CacheVaryByRules.QueryKeys = normalizedVaryQueryKeys; | ||
|
||
// TODO: Add same condition on LogLevel in Response Caching | ||
// Always overwrite the CachedVaryByRules to update the expiry information | ||
if (_logger.IsEnabled(LogLevel.Debug)) | ||
{ | ||
_logger.VaryByRulesUpdated(normalizedVaryHeaders.ToString(), normalizedVaryQueryKeys.ToString()); | ||
_logger.VaryByRulesUpdated(normalizedVaryHeaderNames.ToString(), normalizedVaryQueryKeys.ToString(), normalizedVaryRouteValueNames.ToString()); | ||
} | ||
} | ||
|
||
|
@@ -516,7 +517,7 @@ internal static void UnshimResponseStream(OutputCacheContext context) | |
RemoveOutputCacheFeature(context.HttpContext); | ||
} | ||
|
||
internal static bool ContentIsNotModified(OutputCacheContext context) | ||
internal bool ContentIsNotModified(OutputCacheContext context) | ||
{ | ||
var cachedResponseHeaders = context.CachedResponse.Headers; | ||
var ifNoneMatchHeader = context.HttpContext.Request.Headers.IfNoneMatch; | ||
|
@@ -525,7 +526,7 @@ internal static bool ContentIsNotModified(OutputCacheContext context) | |
{ | ||
if (ifNoneMatchHeader.Count == 1 && StringSegment.Equals(ifNoneMatchHeader[0], EntityTagHeaderValue.Any.Tag, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
context.Logger.NotModifiedIfNoneMatchStar(); | ||
_logger.NotModifiedIfNoneMatchStar(); | ||
return true; | ||
} | ||
|
||
|
@@ -538,7 +539,7 @@ internal static bool ContentIsNotModified(OutputCacheContext context) | |
var requestETag = ifNoneMatchEtags[i]; | ||
if (eTag.Compare(requestETag, useStrongComparison: false)) | ||
{ | ||
context.Logger.NotModifiedIfNoneMatchMatched(requestETag); | ||
_logger.NotModifiedIfNoneMatchMatched(requestETag); | ||
return true; | ||
} | ||
} | ||
|
@@ -558,7 +559,7 @@ internal static bool ContentIsNotModified(OutputCacheContext context) | |
if (HeaderUtilities.TryParseDate(ifModifiedSince.ToString(), out var modifiedSince) && | ||
modified <= modifiedSince) | ||
{ | ||
context.Logger.NotModifiedIfModifiedSinceSatisfied(modified, modifiedSince); | ||
_logger.NotModifiedIfModifiedSinceSatisfied(modified, modifiedSince); | ||
return true; | ||
} | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.