Skip to content
This repository was archived by the owner on Nov 22, 2018. It is now read-only.

Commit 6db4df4

Browse files
committed
Restructure response caching middleware flow
- Always add IresponseCachingFeatu8re before calling the next middleware #81 - Use If-Modified-Since instead of the incorrect If-Unmodified-Since header #83 - Handle proxy-revalidate in the same way as must-revalidate #83 - Handle max-stale with no specified limit #83 - Bypass cache lookup for no-cache but store the response #83 - Bypass response capturing and buffering when no-store is specified #83 - Replace IsRequestCacheable cache policy with three new independent policy checks to reflect these changes - Modify middleware flow to accommodate cache policy updates
1 parent a5717aa commit 6db4df4

File tree

9 files changed

+478
-176
lines changed

9 files changed

+478
-176
lines changed

src/Microsoft.AspNetCore.ResponseCaching/Internal/Interfaces/IResponseCachingPolicyProvider.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,35 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
66
public interface IResponseCachingPolicyProvider
77
{
88
/// <summary>
9-
/// Determine wehther the response cache middleware should be executed for the incoming HTTP request.
9+
/// Determine whether the response caching logic should be attempted for the incoming HTTP request.
1010
/// </summary>
1111
/// <param name="context">The <see cref="ResponseCachingContext"/>.</param>
12-
/// <returns><c>true</c> if the request is cacheable; otherwise <c>false</c>.</returns>
13-
bool IsRequestCacheable(ResponseCachingContext context);
12+
/// <returns><c>true</c> if response caching logic should be attempted; otherwise <c>false</c>.</returns>
13+
bool AttemptResponseCaching(ResponseCachingContext context);
1414

1515
/// <summary>
16-
/// Determine whether the response received by the middleware be cached for future requests.
16+
/// Determine whether a cache lookup is allowed for the incoming HTTP request.
17+
/// </summary>
18+
/// <param name="context">The <see cref="ResponseCachingContext"/>.</param>
19+
/// <returns><c>true</c> if cache lookup for this request is allowed; otherwise <c>false</c>.</returns>
20+
bool AllowCacheLookup(ResponseCachingContext context);
21+
22+
/// <summary>
23+
/// Determine whether storage of the response is allowed for the incoming HTTP request.
24+
/// </summary>
25+
/// <param name="context">The <see cref="ResponseCachingContext"/>.</param>
26+
/// <returns><c>true</c> if storage of the response for this request is allowed; otherwise <c>false</c>.</returns>
27+
bool AllowCacheStorage(ResponseCachingContext context);
28+
29+
/// <summary>
30+
/// Determine whether the response received by the middleware can be cached for future requests.
1731
/// </summary>
1832
/// <param name="context">The <see cref="ResponseCachingContext"/>.</param>
1933
/// <returns><c>true</c> if the response is cacheable; otherwise <c>false</c>.</returns>
2034
bool IsResponseCacheable(ResponseCachingContext context);
2135

2236
/// <summary>
23-
/// Determine whether the response retrieved from the response cache is fresh and be served.
37+
/// Determine whether the response retrieved from the response cache is fresh and can be served.
2438
/// </summary>
2539
/// <param name="context">The <see cref="ResponseCachingContext"/>.</param>
2640
/// <returns><c>true</c> if the cached entry is fresh; otherwise <c>false</c>.</returns>

src/Microsoft.AspNetCore.ResponseCaching/Internal/LoggerExtensions.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ internal static class LoggerExtensions
3131
private static Action<ILogger, int, Exception> _logResponseWithUnsuccessfulStatusCodeNotCacheable;
3232
private static Action<ILogger, Exception> _logNotModifiedIfNoneMatchStar;
3333
private static Action<ILogger, EntityTagHeaderValue, Exception> _logNotModifiedIfNoneMatchMatched;
34-
private static Action<ILogger, DateTimeOffset, DateTimeOffset, Exception> _logNotModifiedIfUnmodifiedSinceSatisfied;
34+
private static Action<ILogger, DateTimeOffset, DateTimeOffset, Exception> _logNotModifiedIfModifiedSinceSatisfied;
3535
private static Action<ILogger, Exception> _logNotModifiedServed;
3636
private static Action<ILogger, Exception> _logCachedResponseServed;
3737
private static Action<ILogger, Exception> _logGatewayTimeoutServed;
@@ -40,6 +40,7 @@ internal static class LoggerExtensions
4040
private static Action<ILogger, Exception> _logResponseCached;
4141
private static Action<ILogger, Exception> _logResponseNotCached;
4242
private static Action<ILogger, Exception> _logResponseContentLengthMismatchNotCached;
43+
private static Action<ILogger, TimeSpan, TimeSpan, Exception> _logExpirationInfiniteMaxStaleSatisfied;
4344

4445
static LoggerExtensions()
4546
{
@@ -70,7 +71,7 @@ static LoggerExtensions()
7071
_logExpirationMustRevalidate = LoggerMessage.Define<TimeSpan, TimeSpan>(
7172
logLevel: LogLevel.Debug,
7273
eventId: 7,
73-
formatString: "The age of the entry is {Age} and has exceeded the maximum age of {MaxAge} specified by the 'max-age' cache directive. It must be revalidated because the 'must-revalidate' cache directive is specified.");
74+
formatString: "The age of the entry is {Age} and has exceeded the maximum age of {MaxAge} specified by the 'max-age' cache directive. It must be revalidated because the 'must-revalidate' or 'proxy-revalidate' cache directive is specified.");
7475
_logExpirationMaxStaleSatisfied = LoggerMessage.Define<TimeSpan, TimeSpan, TimeSpan>(
7576
logLevel: LogLevel.Debug,
7677
eventId: 8,
@@ -119,10 +120,10 @@ static LoggerExtensions()
119120
logLevel: LogLevel.Debug,
120121
eventId: 19,
121122
formatString: $"The ETag {{ETag}} in the '{HeaderNames.IfNoneMatch}' header matched the ETag of a cached entry.");
122-
_logNotModifiedIfUnmodifiedSinceSatisfied = LoggerMessage.Define<DateTimeOffset, DateTimeOffset>(
123+
_logNotModifiedIfModifiedSinceSatisfied = LoggerMessage.Define<DateTimeOffset, DateTimeOffset>(
123124
logLevel: LogLevel.Debug,
124125
eventId: 20,
125-
formatString: $"The last modified date of {{LastModified}} is before the date {{IfUnmodifiedSince}} specified in the '{HeaderNames.IfUnmodifiedSince}' header.");
126+
formatString: $"The last modified date of {{LastModified}} is before the date {{IfModifiedSince}} specified in the '{HeaderNames.IfModifiedSince}' header.");
126127
_logNotModifiedServed = LoggerMessage.Define(
127128
logLevel: LogLevel.Information,
128129
eventId: 21,
@@ -155,6 +156,10 @@ static LoggerExtensions()
155156
logLevel: LogLevel.Warning,
156157
eventId: 28,
157158
formatString: $"The response could not be cached for this request because the '{HeaderNames.ContentLength}' did not match the body length.");
159+
_logExpirationInfiniteMaxStaleSatisfied = LoggerMessage.Define<TimeSpan, TimeSpan>(
160+
logLevel: LogLevel.Debug,
161+
eventId: 29,
162+
formatString: "The age of the entry is {Age} and has exceeded the maximum age of {MaxAge} specified by the 'max-age' cache directive. However, the 'max-stale' cache directive was specified without an assigned value and a stale response of any age is accepted.");
158163
}
159164

160165
internal static void LogRequestMethodNotCacheable(this ILogger logger, string method)
@@ -252,9 +257,9 @@ internal static void LogNotModifiedIfNoneMatchMatched(this ILogger logger, Entit
252257
_logNotModifiedIfNoneMatchMatched(logger, etag, null);
253258
}
254259

255-
internal static void LogNotModifiedIfUnmodifiedSinceSatisfied(this ILogger logger, DateTimeOffset lastModified, DateTimeOffset ifUnmodifiedSince)
260+
internal static void LogNotModifiedIfModifiedSinceSatisfied(this ILogger logger, DateTimeOffset lastModified, DateTimeOffset ifModifiedSince)
256261
{
257-
_logNotModifiedIfUnmodifiedSinceSatisfied(logger, lastModified, ifUnmodifiedSince, null);
262+
_logNotModifiedIfModifiedSinceSatisfied(logger, lastModified, ifModifiedSince, null);
258263
}
259264

260265
internal static void LogNotModifiedServed(this ILogger logger)
@@ -296,5 +301,10 @@ internal static void LogResponseContentLengthMismatchNotCached(this ILogger logg
296301
{
297302
_logResponseContentLengthMismatchNotCached(logger, null);
298303
}
304+
305+
internal static void LogExpirationInfiniteMaxStaleSatisfied(this ILogger logger, TimeSpan age, TimeSpan maxAge)
306+
{
307+
_logExpirationInfiniteMaxStaleSatisfied(logger, age, maxAge, null);
308+
}
299309
}
300310
}

src/Microsoft.AspNetCore.ResponseCaching/Internal/ResponseCachingContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ internal ResponseCachingContext(HttpContext httpContext, ILogger logger)
3737

3838
internal ILogger Logger { get; }
3939

40-
internal bool ShouldCacheResponse { get; set; }
40+
internal bool ShouldCacheResponse { get; set; }
4141

4242
internal string BaseKey { get; set; }
4343

src/Microsoft.AspNetCore.ResponseCaching/Internal/ResponseCachingPolicyProvider.cs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
1010
{
1111
public class ResponseCachingPolicyProvider : IResponseCachingPolicyProvider
1212
{
13-
public virtual bool IsRequestCacheable(ResponseCachingContext context)
13+
public virtual bool AttemptResponseCaching(ResponseCachingContext context)
1414
{
15-
// Verify the method
1615
var request = context.HttpContext.Request;
16+
17+
// Verify the method
1718
if (!HttpMethods.IsGet(request.Method) && !HttpMethods.IsHead(request.Method))
1819
{
1920
context.Logger.LogRequestMethodNotCacheable(request.Method);
@@ -27,6 +28,13 @@ public virtual bool IsRequestCacheable(ResponseCachingContext context)
2728
return false;
2829
}
2930

31+
return true;
32+
}
33+
34+
public virtual bool AllowCacheLookup(ResponseCachingContext context)
35+
{
36+
var request = context.HttpContext.Request;
37+
3038
// Verify request cache-control parameters
3139
if (!StringValues.IsNullOrEmpty(request.Headers[HeaderNames.CacheControl]))
3240
{
@@ -50,6 +58,12 @@ public virtual bool IsRequestCacheable(ResponseCachingContext context)
5058
return true;
5159
}
5260

61+
public virtual bool AllowCacheStorage(ResponseCachingContext context)
62+
{
63+
// Check request no-store
64+
return !HeaderUtilities.ContainsCacheDirective(context.HttpContext.Request.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.NoStoreString);
65+
}
66+
5367
public virtual bool IsResponseCacheable(ResponseCachingContext context)
5468
{
5569
var responseCacheControlHeader = context.HttpContext.Response.Headers[HeaderNames.CacheControl];
@@ -61,9 +75,8 @@ public virtual bool IsResponseCacheable(ResponseCachingContext context)
6175
return false;
6276
}
6377

64-
// Check no-store
65-
if (HeaderUtilities.ContainsCacheDirective(context.HttpContext.Request.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.NoStoreString)
66-
|| HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.NoStoreString))
78+
// Check response no-store
79+
if (HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.NoStoreString))
6780
{
6881
context.Logger.LogResponseWithNoStoreNotCacheable();
6982
return false;
@@ -187,17 +200,26 @@ public virtual bool IsCachedEntryFresh(ResponseCachingContext context)
187200
// Validate max age
188201
if (age >= lowestMaxAge)
189202
{
190-
// Must revalidate
191-
if (HeaderUtilities.ContainsCacheDirective(cachedCacheControlHeaders, CacheControlHeaderValue.MustRevalidateString))
203+
// Must revalidate or proxy revalidate
204+
if (HeaderUtilities.ContainsCacheDirective(cachedCacheControlHeaders, CacheControlHeaderValue.MustRevalidateString)
205+
|| HeaderUtilities.ContainsCacheDirective(cachedCacheControlHeaders, CacheControlHeaderValue.ProxyRevalidateString))
192206
{
193207
context.Logger.LogExpirationMustRevalidate(age, lowestMaxAge.Value);
194208
return false;
195209
}
196210

197211
TimeSpan? requestMaxStale;
212+
var maxStaleExist = HeaderUtilities.ContainsCacheDirective(requestCacheControlHeaders, CacheControlHeaderValue.MaxStaleString);
198213
HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MaxStaleString, out requestMaxStale);
199214

200-
// Request allows stale values
215+
// Request allows stale values with no age limit
216+
if (maxStaleExist && !requestMaxStale.HasValue)
217+
{
218+
context.Logger.LogExpirationInfiniteMaxStaleSatisfied(age, lowestMaxAge.Value);
219+
return true;
220+
}
221+
222+
// Request allows stale values with age limit
201223
if (requestMaxStale.HasValue && age - lowestMaxAge < requestMaxStale)
202224
{
203225
context.Logger.LogExpirationMaxStaleSatisfied(age, lowestMaxAge.Value, requestMaxStale.Value);

0 commit comments

Comments
 (0)