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
This commit is contained in:
parent
a5717aa583
commit
3bf5f6a1ce
|
|
@ -6,21 +6,35 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
public interface IResponseCachingPolicyProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Determine wehther the response cache middleware should be executed for the incoming HTTP request.
|
||||
/// Determine whether the response caching logic should be attempted for the incoming HTTP request.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ResponseCachingContext"/>.</param>
|
||||
/// <returns><c>true</c> if the request is cacheable; otherwise <c>false</c>.</returns>
|
||||
bool IsRequestCacheable(ResponseCachingContext context);
|
||||
/// <returns><c>true</c> if response caching logic should be attempted; otherwise <c>false</c>.</returns>
|
||||
bool AttemptResponseCaching(ResponseCachingContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether the response received by the middleware be cached for future requests.
|
||||
/// Determine whether a cache lookup is allowed for the incoming HTTP request.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ResponseCachingContext"/>.</param>
|
||||
/// <returns><c>true</c> if cache lookup for this request is allowed; otherwise <c>false</c>.</returns>
|
||||
bool AllowCacheLookup(ResponseCachingContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether storage of the response is allowed for the incoming HTTP request.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ResponseCachingContext"/>.</param>
|
||||
/// <returns><c>true</c> if storage of the response for this request is allowed; otherwise <c>false</c>.</returns>
|
||||
bool AllowCacheStorage(ResponseCachingContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether the response received by the middleware can be cached for future requests.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ResponseCachingContext"/>.</param>
|
||||
/// <returns><c>true</c> if the response is cacheable; otherwise <c>false</c>.</returns>
|
||||
bool IsResponseCacheable(ResponseCachingContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether the response retrieved from the response cache is fresh and be served.
|
||||
/// Determine whether the response retrieved from the response cache is fresh and can be served.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ResponseCachingContext"/>.</param>
|
||||
/// <returns><c>true</c> if the cached entry is fresh; otherwise <c>false</c>.</returns>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
private static Action<ILogger, int, Exception> _logResponseWithUnsuccessfulStatusCodeNotCacheable;
|
||||
private static Action<ILogger, Exception> _logNotModifiedIfNoneMatchStar;
|
||||
private static Action<ILogger, EntityTagHeaderValue, Exception> _logNotModifiedIfNoneMatchMatched;
|
||||
private static Action<ILogger, DateTimeOffset, DateTimeOffset, Exception> _logNotModifiedIfUnmodifiedSinceSatisfied;
|
||||
private static Action<ILogger, DateTimeOffset, DateTimeOffset, Exception> _logNotModifiedIfModifiedSinceSatisfied;
|
||||
private static Action<ILogger, Exception> _logNotModifiedServed;
|
||||
private static Action<ILogger, Exception> _logCachedResponseServed;
|
||||
private static Action<ILogger, Exception> _logGatewayTimeoutServed;
|
||||
|
|
@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
private static Action<ILogger, Exception> _logResponseCached;
|
||||
private static Action<ILogger, Exception> _logResponseNotCached;
|
||||
private static Action<ILogger, Exception> _logResponseContentLengthMismatchNotCached;
|
||||
private static Action<ILogger, TimeSpan, TimeSpan, Exception> _logExpirationInfiniteMaxStaleSatisfied;
|
||||
|
||||
static LoggerExtensions()
|
||||
{
|
||||
|
|
@ -70,7 +71,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
_logExpirationMustRevalidate = LoggerMessage.Define<TimeSpan, TimeSpan>(
|
||||
logLevel: LogLevel.Debug,
|
||||
eventId: 7,
|
||||
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.");
|
||||
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.");
|
||||
_logExpirationMaxStaleSatisfied = LoggerMessage.Define<TimeSpan, TimeSpan, TimeSpan>(
|
||||
logLevel: LogLevel.Debug,
|
||||
eventId: 8,
|
||||
|
|
@ -119,10 +120,10 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
logLevel: LogLevel.Debug,
|
||||
eventId: 19,
|
||||
formatString: $"The ETag {{ETag}} in the '{HeaderNames.IfNoneMatch}' header matched the ETag of a cached entry.");
|
||||
_logNotModifiedIfUnmodifiedSinceSatisfied = LoggerMessage.Define<DateTimeOffset, DateTimeOffset>(
|
||||
_logNotModifiedIfModifiedSinceSatisfied = LoggerMessage.Define<DateTimeOffset, DateTimeOffset>(
|
||||
logLevel: LogLevel.Debug,
|
||||
eventId: 20,
|
||||
formatString: $"The last modified date of {{LastModified}} is before the date {{IfUnmodifiedSince}} specified in the '{HeaderNames.IfUnmodifiedSince}' header.");
|
||||
formatString: $"The last modified date of {{LastModified}} is before the date {{IfModifiedSince}} specified in the '{HeaderNames.IfModifiedSince}' header.");
|
||||
_logNotModifiedServed = LoggerMessage.Define(
|
||||
logLevel: LogLevel.Information,
|
||||
eventId: 21,
|
||||
|
|
@ -155,6 +156,10 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
logLevel: LogLevel.Warning,
|
||||
eventId: 28,
|
||||
formatString: $"The response could not be cached for this request because the '{HeaderNames.ContentLength}' did not match the body length.");
|
||||
_logExpirationInfiniteMaxStaleSatisfied = LoggerMessage.Define<TimeSpan, TimeSpan>(
|
||||
logLevel: LogLevel.Debug,
|
||||
eventId: 29,
|
||||
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.");
|
||||
}
|
||||
|
||||
internal static void LogRequestMethodNotCacheable(this ILogger logger, string method)
|
||||
|
|
@ -252,9 +257,9 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
_logNotModifiedIfNoneMatchMatched(logger, etag, null);
|
||||
}
|
||||
|
||||
internal static void LogNotModifiedIfUnmodifiedSinceSatisfied(this ILogger logger, DateTimeOffset lastModified, DateTimeOffset ifUnmodifiedSince)
|
||||
internal static void LogNotModifiedIfModifiedSinceSatisfied(this ILogger logger, DateTimeOffset lastModified, DateTimeOffset ifModifiedSince)
|
||||
{
|
||||
_logNotModifiedIfUnmodifiedSinceSatisfied(logger, lastModified, ifUnmodifiedSince, null);
|
||||
_logNotModifiedIfModifiedSinceSatisfied(logger, lastModified, ifModifiedSince, null);
|
||||
}
|
||||
|
||||
internal static void LogNotModifiedServed(this ILogger logger)
|
||||
|
|
@ -296,5 +301,10 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
{
|
||||
_logResponseContentLengthMismatchNotCached(logger, null);
|
||||
}
|
||||
|
||||
internal static void LogExpirationInfiniteMaxStaleSatisfied(this ILogger logger, TimeSpan age, TimeSpan maxAge)
|
||||
{
|
||||
_logExpirationInfiniteMaxStaleSatisfied(logger, age, maxAge, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
|
||||
internal ILogger Logger { get; }
|
||||
|
||||
internal bool ShouldCacheResponse { get; set; }
|
||||
internal bool ShouldCacheResponse { get; set; }
|
||||
|
||||
internal string BaseKey { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
{
|
||||
public class ResponseCachingPolicyProvider : IResponseCachingPolicyProvider
|
||||
{
|
||||
public virtual bool IsRequestCacheable(ResponseCachingContext context)
|
||||
public virtual bool AttemptResponseCaching(ResponseCachingContext context)
|
||||
{
|
||||
// Verify the method
|
||||
var request = context.HttpContext.Request;
|
||||
|
||||
// Verify the method
|
||||
if (!HttpMethods.IsGet(request.Method) && !HttpMethods.IsHead(request.Method))
|
||||
{
|
||||
context.Logger.LogRequestMethodNotCacheable(request.Method);
|
||||
|
|
@ -27,6 +28,13 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool AllowCacheLookup(ResponseCachingContext context)
|
||||
{
|
||||
var request = context.HttpContext.Request;
|
||||
|
||||
// Verify request cache-control parameters
|
||||
if (!StringValues.IsNullOrEmpty(request.Headers[HeaderNames.CacheControl]))
|
||||
{
|
||||
|
|
@ -50,6 +58,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
return true;
|
||||
}
|
||||
|
||||
public virtual bool AllowCacheStorage(ResponseCachingContext context)
|
||||
{
|
||||
// Check request no-store
|
||||
return !HeaderUtilities.ContainsCacheDirective(context.HttpContext.Request.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.NoStoreString);
|
||||
}
|
||||
|
||||
public virtual bool IsResponseCacheable(ResponseCachingContext context)
|
||||
{
|
||||
var responseCacheControlHeader = context.HttpContext.Response.Headers[HeaderNames.CacheControl];
|
||||
|
|
@ -61,9 +75,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check no-store
|
||||
if (HeaderUtilities.ContainsCacheDirective(context.HttpContext.Request.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.NoStoreString)
|
||||
|| HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.NoStoreString))
|
||||
// Check response no-store
|
||||
if (HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.NoStoreString))
|
||||
{
|
||||
context.Logger.LogResponseWithNoStoreNotCacheable();
|
||||
return false;
|
||||
|
|
@ -187,17 +200,26 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
// Validate max age
|
||||
if (age >= lowestMaxAge)
|
||||
{
|
||||
// Must revalidate
|
||||
if (HeaderUtilities.ContainsCacheDirective(cachedCacheControlHeaders, CacheControlHeaderValue.MustRevalidateString))
|
||||
// Must revalidate or proxy revalidate
|
||||
if (HeaderUtilities.ContainsCacheDirective(cachedCacheControlHeaders, CacheControlHeaderValue.MustRevalidateString)
|
||||
|| HeaderUtilities.ContainsCacheDirective(cachedCacheControlHeaders, CacheControlHeaderValue.ProxyRevalidateString))
|
||||
{
|
||||
context.Logger.LogExpirationMustRevalidate(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
|
||||
// Request allows stale values with no age limit
|
||||
if (maxStaleExist && !requestMaxStale.HasValue)
|
||||
{
|
||||
context.Logger.LogExpirationInfiniteMaxStaleSatisfied(age, lowestMaxAge.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Request allows stale values with age limit
|
||||
if (requestMaxStale.HasValue && age - lowestMaxAge < requestMaxStale)
|
||||
{
|
||||
context.Logger.LogExpirationMaxStaleSatisfied(age, lowestMaxAge.Value, requestMaxStale.Value);
|
||||
|
|
|
|||
|
|
@ -74,39 +74,53 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
var context = new ResponseCachingContext(httpContext, _logger);
|
||||
|
||||
// Should we attempt any caching logic?
|
||||
if (_policyProvider.IsRequestCacheable(context))
|
||||
if (_policyProvider.AttemptResponseCaching(context))
|
||||
{
|
||||
// Can this request be served from cache?
|
||||
if (await TryServeFromCacheAsync(context))
|
||||
if (_policyProvider.AllowCacheLookup(context) && await TryServeFromCacheAsync(context))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Hook up to listen to the response stream
|
||||
ShimResponseStream(context);
|
||||
|
||||
try
|
||||
// Should we store the response to this request?
|
||||
if (_policyProvider.AllowCacheStorage(context))
|
||||
{
|
||||
// Subscribe to OnStarting event
|
||||
httpContext.Response.OnStarting(_onStartingCallback, context);
|
||||
// Hook up to listen to the response stream
|
||||
ShimResponseStream(context);
|
||||
|
||||
await _next(httpContext);
|
||||
try
|
||||
{
|
||||
// Subscribe to OnStarting event
|
||||
httpContext.Response.OnStarting(_onStartingCallback, context);
|
||||
|
||||
// If there was no response body, check the response headers now. We can cache things like redirects.
|
||||
await OnResponseStartingAsync(context);
|
||||
await _next(httpContext);
|
||||
|
||||
// Finalize the cache entry
|
||||
await FinalizeCacheBodyAsync(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnshimResponseStream(context);
|
||||
// If there was no response body, check the response headers now. We can cache things like redirects.
|
||||
await OnResponseStartingAsync(context);
|
||||
|
||||
// Finalize the cache entry
|
||||
await FinalizeCacheBodyAsync(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnshimResponseStream(context);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
// Response should not be captured but add IResponseCachingFeature which may be required when the response is generated
|
||||
AddResponseCachingFeature(httpContext);
|
||||
|
||||
try
|
||||
{
|
||||
await _next(httpContext);
|
||||
}
|
||||
finally
|
||||
{
|
||||
RemoveResponseCachingFeature(httpContext);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<bool> TryServeCachedResponseAsync(ResponseCachingContext context, IResponseCacheEntry cacheEntry)
|
||||
|
|
@ -220,6 +234,12 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
(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))
|
||||
{
|
||||
|
|
@ -279,9 +299,9 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
|
||||
internal async Task FinalizeCacheBodyAsync(ResponseCachingContext context)
|
||||
{
|
||||
var contentLength = context.HttpContext.Response.ContentLength;
|
||||
if (context.ShouldCacheResponse && context.ResponseCachingStream.BufferingEnabled)
|
||||
{
|
||||
var contentLength = context.HttpContext.Response.ContentLength;
|
||||
var bufferStream = context.ResponseCachingStream.GetBufferStream();
|
||||
if (!contentLength.HasValue || contentLength == bufferStream.Length)
|
||||
{
|
||||
|
|
@ -322,6 +342,15 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
}
|
||||
}
|
||||
|
||||
internal static void AddResponseCachingFeature(HttpContext context)
|
||||
{
|
||||
if (context.Features.Get<IResponseCachingFeature>() != null)
|
||||
{
|
||||
throw new InvalidOperationException($"Another instance of {nameof(ResponseCachingFeature)} already exists. Only one instance of {nameof(ResponseCachingMiddleware)} can be configured for an application.");
|
||||
}
|
||||
context.Features.Set<IResponseCachingFeature>(new ResponseCachingFeature());
|
||||
}
|
||||
|
||||
internal void ShimResponseStream(ResponseCachingContext context)
|
||||
{
|
||||
// Shim response stream
|
||||
|
|
@ -337,13 +366,12 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
}
|
||||
|
||||
// Add IResponseCachingFeature
|
||||
if (context.HttpContext.Features.Get<IResponseCachingFeature>() != null)
|
||||
{
|
||||
throw new InvalidOperationException($"Another instance of {nameof(ResponseCachingFeature)} already exists. Only one instance of {nameof(ResponseCachingMiddleware)} can be configured for an application.");
|
||||
}
|
||||
context.HttpContext.Features.Set<IResponseCachingFeature>(new ResponseCachingFeature());
|
||||
AddResponseCachingFeature(context.HttpContext);
|
||||
}
|
||||
|
||||
internal static void RemoveResponseCachingFeature(HttpContext context) =>
|
||||
context.Features.Set<IResponseCachingFeature>(null);
|
||||
|
||||
internal static void UnshimResponseStream(ResponseCachingContext context)
|
||||
{
|
||||
// Unshim response stream
|
||||
|
|
@ -353,7 +381,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
context.HttpContext.Features.Set(context.OriginalSendFileFeature);
|
||||
|
||||
// Remove IResponseCachingFeature
|
||||
context.HttpContext.Features.Set<IResponseCachingFeature>(null);
|
||||
RemoveResponseCachingFeature(context.HttpContext);
|
||||
}
|
||||
|
||||
internal static bool ContentIsNotModified(ResponseCachingContext context)
|
||||
|
|
@ -388,8 +416,8 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
}
|
||||
else
|
||||
{
|
||||
var ifUnmodifiedSince = context.HttpContext.Request.Headers[HeaderNames.IfUnmodifiedSince];
|
||||
if (!StringValues.IsNullOrEmpty(ifUnmodifiedSince))
|
||||
var ifModifiedSince = context.HttpContext.Request.Headers[HeaderNames.IfModifiedSince];
|
||||
if (!StringValues.IsNullOrEmpty(ifModifiedSince))
|
||||
{
|
||||
DateTimeOffset modified;
|
||||
if (!HeaderUtilities.TryParseDate(cachedResponseHeaders[HeaderNames.LastModified], out modified) &&
|
||||
|
|
@ -398,11 +426,11 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
return false;
|
||||
}
|
||||
|
||||
DateTimeOffset unmodifiedSince;
|
||||
if (HeaderUtilities.TryParseDate(ifUnmodifiedSince, out unmodifiedSince) &&
|
||||
modified <= unmodifiedSince)
|
||||
DateTimeOffset modifiedSince;
|
||||
if (HeaderUtilities.TryParseDate(ifModifiedSince, out modifiedSince) &&
|
||||
modified <= modifiedSince)
|
||||
{
|
||||
context.Logger.LogNotModifiedIfUnmodifiedSinceSatisfied(modified, unmodifiedSince);
|
||||
context.Logger.LogNotModifiedIfModifiedSinceSatisfied(modified, modifiedSince);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.ResponseCaching.Internal;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
|
@ -157,14 +159,14 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ContentIsNotModified_IfUnmodifiedSince_FallsbackToDateHeader()
|
||||
public void ContentIsNotModified_IfModifiedSince_FallsbackToDateHeader()
|
||||
{
|
||||
var utcNow = DateTimeOffset.UtcNow;
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.CachedResponseHeaders = new HeaderDictionary();
|
||||
|
||||
context.HttpContext.Request.Headers[HeaderNames.IfUnmodifiedSince] = HeaderUtilities.FormatDate(utcNow);
|
||||
context.HttpContext.Request.Headers[HeaderNames.IfModifiedSince] = HeaderUtilities.FormatDate(utcNow);
|
||||
|
||||
// Verify modifications in the past succeeds
|
||||
context.CachedResponseHeaders[HeaderNames.Date] = HeaderUtilities.FormatDate(utcNow - TimeSpan.FromSeconds(10));
|
||||
|
|
@ -183,19 +185,19 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
// Verify logging
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.NotModifiedIfUnmodifiedSinceSatisfied,
|
||||
LoggedMessage.NotModifiedIfUnmodifiedSinceSatisfied);
|
||||
LoggedMessage.NotModifiedIfModifiedSinceSatisfied,
|
||||
LoggedMessage.NotModifiedIfModifiedSinceSatisfied);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContentIsNotModified_IfUnmodifiedSince_LastModifiedOverridesDateHeader()
|
||||
public void ContentIsNotModified_IfModifiedSince_LastModifiedOverridesDateHeader()
|
||||
{
|
||||
var utcNow = DateTimeOffset.UtcNow;
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.CachedResponseHeaders = new HeaderDictionary();
|
||||
|
||||
context.HttpContext.Request.Headers[HeaderNames.IfUnmodifiedSince] = HeaderUtilities.FormatDate(utcNow);
|
||||
context.HttpContext.Request.Headers[HeaderNames.IfModifiedSince] = HeaderUtilities.FormatDate(utcNow);
|
||||
|
||||
// Verify modifications in the past succeeds
|
||||
context.CachedResponseHeaders[HeaderNames.Date] = HeaderUtilities.FormatDate(utcNow + TimeSpan.FromSeconds(10));
|
||||
|
|
@ -217,20 +219,20 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
// Verify logging
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.NotModifiedIfUnmodifiedSinceSatisfied,
|
||||
LoggedMessage.NotModifiedIfUnmodifiedSinceSatisfied);
|
||||
LoggedMessage.NotModifiedIfModifiedSinceSatisfied,
|
||||
LoggedMessage.NotModifiedIfModifiedSinceSatisfied);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContentIsNotModified_IfNoneMatch_Overrides_IfUnmodifiedSince_ToTrue()
|
||||
public void ContentIsNotModified_IfNoneMatch_Overrides_IfModifiedSince_ToTrue()
|
||||
{
|
||||
var utcNow = DateTimeOffset.UtcNow;
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.CachedResponseHeaders = new HeaderDictionary();
|
||||
|
||||
// This would fail the IfUnmodifiedSince checks
|
||||
context.HttpContext.Request.Headers[HeaderNames.IfUnmodifiedSince] = HeaderUtilities.FormatDate(utcNow);
|
||||
// This would fail the IfModifiedSince checks
|
||||
context.HttpContext.Request.Headers[HeaderNames.IfModifiedSince] = HeaderUtilities.FormatDate(utcNow);
|
||||
context.CachedResponseHeaders[HeaderNames.LastModified] = HeaderUtilities.FormatDate(utcNow + TimeSpan.FromSeconds(10));
|
||||
|
||||
context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch] = EntityTagHeaderValue.Any.ToString();
|
||||
|
|
@ -241,15 +243,15 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ContentIsNotModified_IfNoneMatch_Overrides_IfUnmodifiedSince_ToFalse()
|
||||
public void ContentIsNotModified_IfNoneMatch_Overrides_IfModifiedSince_ToFalse()
|
||||
{
|
||||
var utcNow = DateTimeOffset.UtcNow;
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.CachedResponseHeaders = new HeaderDictionary();
|
||||
|
||||
// This would pass the IfUnmodifiedSince checks
|
||||
context.HttpContext.Request.Headers[HeaderNames.IfUnmodifiedSince] = HeaderUtilities.FormatDate(utcNow);
|
||||
// This would pass the IfModifiedSince checks
|
||||
context.HttpContext.Request.Headers[HeaderNames.IfModifiedSince] = HeaderUtilities.FormatDate(utcNow);
|
||||
context.CachedResponseHeaders[HeaderNames.LastModified] = HeaderUtilities.FormatDate(utcNow - TimeSpan.FromSeconds(10));
|
||||
|
||||
context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch] = "\"E1\"";
|
||||
|
|
@ -328,27 +330,56 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeaders_DoNotUpdateShouldCacheResponse_IfResponseIsNotCacheable()
|
||||
public async Task OnResponseStartingAsync_IfAllowResponseCaptureIsTrue_SetsResponseTime()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, policyProvider: new ResponseCachingPolicyProvider());
|
||||
var clock = new TestClock
|
||||
{
|
||||
UtcNow = DateTimeOffset.UtcNow
|
||||
};
|
||||
var middleware = TestUtils.CreateTestMiddleware(options: new ResponseCachingOptions
|
||||
{
|
||||
SystemClock = clock
|
||||
});
|
||||
var context = TestUtils.CreateTestContext();
|
||||
context.ResponseTime = null;
|
||||
|
||||
Assert.False(context.ShouldCacheResponse);
|
||||
await middleware.OnResponseStartingAsync(context);
|
||||
|
||||
middleware.ShimResponseStream(context);
|
||||
await middleware.FinalizeCacheHeadersAsync(context);
|
||||
|
||||
Assert.False(context.ShouldCacheResponse);
|
||||
Assert.Empty(sink.Writes);
|
||||
Assert.Equal(clock.UtcNow, context.ResponseTime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeaders_UpdateShouldCacheResponse_IfResponseIsCacheable()
|
||||
public async Task OnResponseStartingAsync_IfAllowResponseCaptureIsTrue_SetsResponseTimeOnlyOnce()
|
||||
{
|
||||
var clock = new TestClock
|
||||
{
|
||||
UtcNow = DateTimeOffset.UtcNow
|
||||
};
|
||||
var middleware = TestUtils.CreateTestMiddleware(options: new ResponseCachingOptions
|
||||
{
|
||||
SystemClock = clock
|
||||
});
|
||||
var context = TestUtils.CreateTestContext();
|
||||
var initialTime = clock.UtcNow;
|
||||
context.ResponseTime = null;
|
||||
|
||||
await middleware.OnResponseStartingAsync(context);
|
||||
Assert.Equal(initialTime, context.ResponseTime);
|
||||
|
||||
clock.UtcNow += TimeSpan.FromSeconds(10);
|
||||
|
||||
await middleware.OnResponseStartingAsync(context);
|
||||
Assert.NotEqual(clock.UtcNow, context.ResponseTime);
|
||||
Assert.Equal(initialTime, context.ResponseTime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeadersAsync_UpdateShouldCacheResponse_IfResponseCacheable()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, policyProvider: new ResponseCachingPolicyProvider());
|
||||
var context = TestUtils.CreateTestContext();
|
||||
|
||||
context.HttpContext.Response.Headers[HeaderNames.CacheControl] = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true
|
||||
|
|
@ -363,7 +394,22 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeaders_DefaultResponseValidity_Is10Seconds()
|
||||
public async Task FinalizeCacheHeadersAsync_DoNotUpdateShouldCacheResponse_IfResponseIsNotCacheable()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, policyProvider: new ResponseCachingPolicyProvider());
|
||||
var context = TestUtils.CreateTestContext();
|
||||
|
||||
middleware.ShimResponseStream(context);
|
||||
|
||||
await middleware.FinalizeCacheHeadersAsync(context);
|
||||
|
||||
Assert.False(context.ShouldCacheResponse);
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeadersAsync_DefaultResponseValidity_Is10Seconds()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink);
|
||||
|
|
@ -376,15 +422,21 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeaders_ResponseValidity_UseExpiryIfAvailable()
|
||||
public async Task FinalizeCacheHeadersAsync_ResponseValidity_UseExpiryIfAvailable()
|
||||
{
|
||||
var utcNow = DateTimeOffset.MinValue;
|
||||
var clock = new TestClock
|
||||
{
|
||||
UtcNow = DateTimeOffset.MinValue
|
||||
};
|
||||
var sink = new TestSink();
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink);
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: new ResponseCachingOptions
|
||||
{
|
||||
SystemClock = clock
|
||||
});
|
||||
var context = TestUtils.CreateTestContext();
|
||||
|
||||
context.ResponseTime = utcNow;
|
||||
context.HttpContext.Response.Headers[HeaderNames.Expires] = HeaderUtilities.FormatDate(utcNow + TimeSpan.FromSeconds(11));
|
||||
context.ResponseTime = clock.UtcNow;
|
||||
context.HttpContext.Response.Headers[HeaderNames.Expires] = HeaderUtilities.FormatDate(clock.UtcNow + TimeSpan.FromSeconds(11));
|
||||
|
||||
await middleware.FinalizeCacheHeadersAsync(context);
|
||||
|
||||
|
|
@ -393,17 +445,26 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeaders_ResponseValidity_UseMaxAgeIfAvailable()
|
||||
public async Task FinalizeCacheHeadersAsync_ResponseValidity_UseMaxAgeIfAvailable()
|
||||
{
|
||||
var clock = new TestClock
|
||||
{
|
||||
UtcNow = DateTimeOffset.UtcNow
|
||||
};
|
||||
var sink = new TestSink();
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink);
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: new ResponseCachingOptions
|
||||
{
|
||||
SystemClock = clock
|
||||
});
|
||||
var context = TestUtils.CreateTestContext();
|
||||
|
||||
context.ResponseTime = clock.UtcNow;
|
||||
context.HttpContext.Response.Headers[HeaderNames.CacheControl] = new CacheControlHeaderValue()
|
||||
{
|
||||
MaxAge = TimeSpan.FromSeconds(12)
|
||||
}.ToString();
|
||||
|
||||
context.HttpContext.Response.Headers[HeaderNames.Expires] = HeaderUtilities.FormatDate(context.ResponseTime.Value + TimeSpan.FromSeconds(11));
|
||||
context.HttpContext.Response.Headers[HeaderNames.Expires] = HeaderUtilities.FormatDate(clock.UtcNow + TimeSpan.FromSeconds(11));
|
||||
|
||||
await middleware.FinalizeCacheHeadersAsync(context);
|
||||
|
||||
|
|
@ -412,18 +473,26 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeaders_ResponseValidity_UseSharedMaxAgeIfAvailable()
|
||||
public async Task FinalizeCacheHeadersAsync_ResponseValidity_UseSharedMaxAgeIfAvailable()
|
||||
{
|
||||
var clock = new TestClock
|
||||
{
|
||||
UtcNow = DateTimeOffset.UtcNow
|
||||
};
|
||||
var sink = new TestSink();
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink);
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: new ResponseCachingOptions
|
||||
{
|
||||
SystemClock = clock
|
||||
});
|
||||
var context = TestUtils.CreateTestContext();
|
||||
|
||||
context.ResponseTime = clock.UtcNow;
|
||||
context.HttpContext.Response.Headers[HeaderNames.CacheControl] = new CacheControlHeaderValue()
|
||||
{
|
||||
MaxAge = TimeSpan.FromSeconds(12),
|
||||
SharedMaxAge = TimeSpan.FromSeconds(13)
|
||||
}.ToString();
|
||||
|
||||
context.HttpContext.Response.Headers[HeaderNames.Expires] = HeaderUtilities.FormatDate(context.ResponseTime.Value + TimeSpan.FromSeconds(11));
|
||||
context.HttpContext.Response.Headers[HeaderNames.Expires] = HeaderUtilities.FormatDate(clock.UtcNow + TimeSpan.FromSeconds(11));
|
||||
|
||||
await middleware.FinalizeCacheHeadersAsync(context);
|
||||
|
||||
|
|
@ -432,7 +501,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeaders_UpdateCachedVaryByRules_IfNotEquivalentToPrevious()
|
||||
public async Task FinalizeCacheHeadersAsync_UpdateCachedVaryByRules_IfNotEquivalentToPrevious()
|
||||
{
|
||||
var cache = new TestResponseCache();
|
||||
var sink = new TestSink();
|
||||
|
|
@ -451,19 +520,17 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
};
|
||||
context.CachedVaryByRules = cachedVaryByRules;
|
||||
|
||||
await middleware.TryServeFromCacheAsync(context);
|
||||
await middleware.FinalizeCacheHeadersAsync(context);
|
||||
|
||||
Assert.Equal(1, cache.SetCount);
|
||||
Assert.NotSame(cachedVaryByRules, context.CachedVaryByRules);
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.NoResponseServed,
|
||||
LoggedMessage.VaryByRulesUpdated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeaders_UpdateCachedVaryByRules_IfEquivalentToPrevious()
|
||||
public async Task FinalizeCacheHeadersAsync_UpdateCachedVaryByRules_IfEquivalentToPrevious()
|
||||
{
|
||||
var cache = new TestResponseCache();
|
||||
var sink = new TestSink();
|
||||
|
|
@ -483,7 +550,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
};
|
||||
context.CachedVaryByRules = cachedVaryByRules;
|
||||
|
||||
await middleware.TryServeFromCacheAsync(context);
|
||||
await middleware.FinalizeCacheHeadersAsync(context);
|
||||
|
||||
// An update to the cache is always made but the entry should be the same
|
||||
|
|
@ -491,7 +557,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
Assert.Same(cachedVaryByRules, context.CachedVaryByRules);
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.NoResponseServed,
|
||||
LoggedMessage.VaryByRulesUpdated);
|
||||
}
|
||||
|
||||
|
|
@ -515,7 +580,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
|
||||
[Theory]
|
||||
[MemberData(nameof(NullOrEmptyVaryRules))]
|
||||
public async Task FinalizeCacheHeaders_UpdateCachedVaryByRules_NullOrEmptyRules(StringValues vary)
|
||||
public async Task FinalizeCacheHeadersAsync_UpdateCachedVaryByRules_NullOrEmptyRules(StringValues vary)
|
||||
{
|
||||
var cache = new TestResponseCache();
|
||||
var sink = new TestSink();
|
||||
|
|
@ -528,40 +593,43 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
VaryByQueryKeys = vary
|
||||
});
|
||||
|
||||
await middleware.TryServeFromCacheAsync(context);
|
||||
await middleware.FinalizeCacheHeadersAsync(context);
|
||||
|
||||
// Vary rules should not be updated
|
||||
Assert.Equal(0, cache.SetCount);
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.NoResponseServed);
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeaders_DoNotAddDate_IfSpecified()
|
||||
public async Task FinalizeCacheHeadersAsync_AddsDate_IfNoneSpecified()
|
||||
{
|
||||
var utcNow = DateTimeOffset.MinValue;
|
||||
var clock = new TestClock
|
||||
{
|
||||
UtcNow = DateTimeOffset.UtcNow
|
||||
};
|
||||
var sink = new TestSink();
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink);
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: new ResponseCachingOptions
|
||||
{
|
||||
SystemClock = clock
|
||||
});
|
||||
var context = TestUtils.CreateTestContext();
|
||||
context.ResponseTime = utcNow;
|
||||
|
||||
Assert.True(StringValues.IsNullOrEmpty(context.HttpContext.Response.Headers[HeaderNames.Date]));
|
||||
|
||||
await middleware.FinalizeCacheHeadersAsync(context);
|
||||
|
||||
Assert.Equal(HeaderUtilities.FormatDate(utcNow), context.HttpContext.Response.Headers[HeaderNames.Date]);
|
||||
Assert.Equal(HeaderUtilities.FormatDate(clock.UtcNow), context.HttpContext.Response.Headers[HeaderNames.Date]);
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeaders_AddsDate_IfNoneSpecified()
|
||||
public async Task FinalizeCacheHeadersAsync_DoNotAddDate_IfSpecified()
|
||||
{
|
||||
var utcNow = DateTimeOffset.MinValue;
|
||||
var sink = new TestSink();
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink);
|
||||
var context = TestUtils.CreateTestContext();
|
||||
|
||||
context.HttpContext.Response.Headers[HeaderNames.Date] = HeaderUtilities.FormatDate(utcNow);
|
||||
context.ResponseTime = utcNow + TimeSpan.FromSeconds(10);
|
||||
|
||||
|
|
@ -574,7 +642,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeaders_StoresCachedResponse_InState()
|
||||
public async Task FinalizeCacheHeadersAsync_StoresCachedResponse_InState()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink);
|
||||
|
|
@ -589,20 +657,19 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCacheHeaders_SplitsVaryHeaderByCommas()
|
||||
public async Task FinalizeCacheHeadersAsync_SplitsVaryHeaderByCommas()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink);
|
||||
var context = TestUtils.CreateTestContext();
|
||||
|
||||
context.HttpContext.Response.Headers[HeaderNames.Vary] = "HeaderB, heaDera";
|
||||
|
||||
await middleware.TryServeFromCacheAsync(context);
|
||||
await middleware.FinalizeCacheHeadersAsync(context);
|
||||
|
||||
Assert.Equal(new StringValues(new[] { "HEADERA", "HEADERB" }), context.CachedVaryByRules.Headers);
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.NoResponseServed,
|
||||
LoggedMessage.VaryByRulesUpdated);
|
||||
}
|
||||
|
||||
|
|
@ -614,11 +681,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, cache: cache);
|
||||
var context = TestUtils.CreateTestContext();
|
||||
|
||||
context.ShouldCacheResponse = true;
|
||||
middleware.ShimResponseStream(context);
|
||||
context.HttpContext.Response.ContentLength = 20;
|
||||
|
||||
await context.HttpContext.Response.WriteAsync(new string('0', 20));
|
||||
|
||||
context.ShouldCacheResponse = true;
|
||||
context.CachedResponse = new CachedResponse();
|
||||
context.BaseKey = "BaseKey";
|
||||
context.CachedResponseValidFor = TimeSpan.FromSeconds(10);
|
||||
|
|
@ -639,11 +707,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, cache: cache);
|
||||
var context = TestUtils.CreateTestContext();
|
||||
|
||||
context.ShouldCacheResponse = true;
|
||||
middleware.ShimResponseStream(context);
|
||||
context.HttpContext.Response.ContentLength = 9;
|
||||
|
||||
await context.HttpContext.Response.WriteAsync(new string('0', 10));
|
||||
|
||||
context.ShouldCacheResponse = true;
|
||||
context.CachedResponse = new CachedResponse();
|
||||
context.BaseKey = "BaseKey";
|
||||
context.CachedResponseValidFor = TimeSpan.FromSeconds(10);
|
||||
|
|
@ -664,10 +733,11 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, cache: cache);
|
||||
var context = TestUtils.CreateTestContext();
|
||||
|
||||
context.ShouldCacheResponse = true;
|
||||
middleware.ShimResponseStream(context);
|
||||
|
||||
await context.HttpContext.Response.WriteAsync(new string('0', 10));
|
||||
|
||||
context.ShouldCacheResponse = true;
|
||||
context.CachedResponse = new CachedResponse()
|
||||
{
|
||||
Headers = new HeaderDictionary()
|
||||
|
|
@ -691,10 +761,10 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, cache: cache);
|
||||
var context = TestUtils.CreateTestContext();
|
||||
|
||||
context.ShouldCacheResponse = false;
|
||||
middleware.ShimResponseStream(context);
|
||||
await context.HttpContext.Response.WriteAsync(new string('0', 10));
|
||||
|
||||
context.ShouldCacheResponse = false;
|
||||
|
||||
await middleware.FinalizeCacheBodyAsync(context);
|
||||
|
||||
|
|
@ -712,10 +782,10 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
var middleware = TestUtils.CreateTestMiddleware(testSink: sink, cache: cache);
|
||||
var context = TestUtils.CreateTestContext();
|
||||
|
||||
context.ShouldCacheResponse = true;
|
||||
middleware.ShimResponseStream(context);
|
||||
await context.HttpContext.Response.WriteAsync(new string('0', 10));
|
||||
|
||||
context.ShouldCacheResponse = true;
|
||||
context.ResponseCachingStream.DisableBuffering();
|
||||
|
||||
await middleware.FinalizeCacheBodyAsync(context);
|
||||
|
|
@ -727,16 +797,52 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ShimResponseStream_SecondInvocation_Throws()
|
||||
public void AddResponseCachingFeature_SecondInvocation_Throws()
|
||||
{
|
||||
var middleware = TestUtils.CreateTestMiddleware();
|
||||
var context = TestUtils.CreateTestContext();
|
||||
var httpContext = new DefaultHttpContext();
|
||||
|
||||
// Should not throw
|
||||
middleware.ShimResponseStream(context);
|
||||
ResponseCachingMiddleware.AddResponseCachingFeature(httpContext);
|
||||
|
||||
// Should throw
|
||||
Assert.ThrowsAny<InvalidOperationException>(() => middleware.ShimResponseStream(context));
|
||||
Assert.ThrowsAny<InvalidOperationException>(() => ResponseCachingMiddleware.AddResponseCachingFeature(httpContext));
|
||||
}
|
||||
|
||||
private class FakeResponseFeature : HttpResponseFeature
|
||||
{
|
||||
public override void OnStarting(Func<object, Task> callback, object state) { }
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// If allowResponseCaching is false, other settings will not matter but are included for completeness
|
||||
[InlineData(false, false, false)]
|
||||
[InlineData(false, false, true)]
|
||||
[InlineData(false, true, false)]
|
||||
[InlineData(false, true, true)]
|
||||
[InlineData(true, false, false)]
|
||||
[InlineData(true, false, true)]
|
||||
[InlineData(true, true, false)]
|
||||
[InlineData(true, true, true)]
|
||||
public async Task Invoke_AddsResponseCachingFeature_Always(bool allowResponseCaching, bool allowCacheLookup, bool allowCacheStorage)
|
||||
{
|
||||
var responseCachingFeatureAdded = false;
|
||||
var middleware = TestUtils.CreateTestMiddleware(next: httpContext =>
|
||||
{
|
||||
responseCachingFeatureAdded = httpContext.Features.Get<IResponseCachingFeature>() != null;
|
||||
return TaskCache.CompletedTask;
|
||||
},
|
||||
policyProvider: new TestResponseCachingPolicyProvider
|
||||
{
|
||||
AttemptResponseCachingValue = allowResponseCaching,
|
||||
AllowCacheLookupValue = allowCacheLookup,
|
||||
AllowCacheStorageValue = allowCacheStorage
|
||||
});
|
||||
|
||||
var context = new DefaultHttpContext();
|
||||
context.Features.Set<IHttpResponseFeature>(new FakeResponseFeature());
|
||||
await middleware.Invoke(context);
|
||||
|
||||
Assert.True(responseCachingFeatureAdded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Headers;
|
||||
using Microsoft.AspNetCore.ResponseCaching.Internal;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
|
@ -27,13 +26,13 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
|
||||
[Theory]
|
||||
[MemberData(nameof(CacheableMethods))]
|
||||
public void IsRequestCacheable_CacheableMethods_Allowed(string method)
|
||||
public void AttemptResponseCaching_CacheableMethods_Allowed(string method)
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.HttpContext.Request.Method = method;
|
||||
|
||||
Assert.True(new ResponseCachingPolicyProvider().IsRequestCacheable(context));
|
||||
Assert.True(new ResponseCachingPolicyProvider().AttemptResponseCaching(context));
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
public static TheoryData<string> NonCacheableMethods
|
||||
|
|
@ -56,51 +55,34 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
|
||||
[Theory]
|
||||
[MemberData(nameof(NonCacheableMethods))]
|
||||
public void IsRequestCacheable_UncacheableMethods_NotAllowed(string method)
|
||||
public void AttemptResponseCaching_UncacheableMethods_NotAllowed(string method)
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.HttpContext.Request.Method = method;
|
||||
|
||||
Assert.False(new ResponseCachingPolicyProvider().IsRequestCacheable(context));
|
||||
Assert.False(new ResponseCachingPolicyProvider().AttemptResponseCaching(context));
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.RequestMethodNotCacheable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRequestCacheable_AuthorizationHeaders_NotAllowed()
|
||||
public void AttemptResponseCaching_AuthorizationHeaders_NotAllowed()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.HttpContext.Request.Method = HttpMethods.Get;
|
||||
context.HttpContext.Request.Headers[HeaderNames.Authorization] = "Basic plaintextUN:plaintextPW";
|
||||
|
||||
Assert.False(new ResponseCachingPolicyProvider().IsRequestCacheable(context));
|
||||
Assert.False(new ResponseCachingPolicyProvider().AttemptResponseCaching(context));
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.RequestWithAuthorizationNotCacheable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRequestCacheable_NoCache_NotAllowed()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.HttpContext.Request.Method = HttpMethods.Get;
|
||||
context.HttpContext.Request.Headers[HeaderNames.CacheControl] = new CacheControlHeaderValue()
|
||||
{
|
||||
NoCache = true
|
||||
}.ToString();
|
||||
|
||||
Assert.False(new ResponseCachingPolicyProvider().IsRequestCacheable(context));
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.RequestWithNoCacheNotCacheable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRequestCacheable_NoStore_Allowed()
|
||||
public void AllowCacheStorage_NoStore_Allowed()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
|
|
@ -110,26 +92,43 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
NoStore = true
|
||||
}.ToString();
|
||||
|
||||
Assert.True(new ResponseCachingPolicyProvider().IsRequestCacheable(context));
|
||||
Assert.True(new ResponseCachingPolicyProvider().AllowCacheLookup(context));
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRequestCacheable_LegacyDirectives_NotAllowed()
|
||||
public void AllowCacheLookup_NoCache_NotAllowed()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.HttpContext.Request.Method = HttpMethods.Get;
|
||||
context.HttpContext.Request.Headers[HeaderNames.CacheControl] = new CacheControlHeaderValue()
|
||||
{
|
||||
NoCache = true
|
||||
}.ToString();
|
||||
|
||||
Assert.False(new ResponseCachingPolicyProvider().AllowCacheLookup(context));
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.RequestWithNoCacheNotCacheable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowCacheLookup_LegacyDirectives_NotAllowed()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.HttpContext.Request.Method = HttpMethods.Get;
|
||||
context.HttpContext.Request.Headers[HeaderNames.Pragma] = "no-cache";
|
||||
|
||||
Assert.False(new ResponseCachingPolicyProvider().IsRequestCacheable(context));
|
||||
Assert.False(new ResponseCachingPolicyProvider().AllowCacheLookup(context));
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.RequestWithPragmaNoCacheNotCacheable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRequestCacheable_LegacyDirectives_OverridenByCacheControl()
|
||||
public void AllowCacheLookup_LegacyDirectives_OverridenByCacheControl()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
|
|
@ -137,7 +136,22 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
context.HttpContext.Request.Headers[HeaderNames.Pragma] = "no-cache";
|
||||
context.HttpContext.Request.Headers[HeaderNames.CacheControl] = "max-age=10";
|
||||
|
||||
Assert.True(new ResponseCachingPolicyProvider().IsRequestCacheable(context));
|
||||
Assert.True(new ResponseCachingPolicyProvider().AllowCacheLookup(context));
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowCacheStorage_NoStore_NotAllowed()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.HttpContext.Request.Method = HttpMethods.Get;
|
||||
context.HttpContext.Request.Headers[HeaderNames.CacheControl] = new CacheControlHeaderValue()
|
||||
{
|
||||
NoStore = true
|
||||
}.ToString();
|
||||
|
||||
Assert.False(new ResponseCachingPolicyProvider().AllowCacheStorage(context));
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
|
||||
|
|
@ -184,26 +198,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
LoggedMessage.ResponseWithNoCacheNotCacheable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsResponseCacheable_RequestNoStore_NotAllowed()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.HttpContext.Request.Headers[HeaderNames.CacheControl] = new CacheControlHeaderValue()
|
||||
{
|
||||
NoStore = true
|
||||
}.ToString();
|
||||
context.HttpContext.Response.Headers[HeaderNames.CacheControl] = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true
|
||||
}.ToString();
|
||||
|
||||
Assert.False(new ResponseCachingPolicyProvider().IsResponseCacheable(context));
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.ResponseWithNoStoreNotCacheable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsResponseCacheable_ResponseNoStore_NotAllowed()
|
||||
{
|
||||
|
|
@ -289,6 +283,9 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(StatusCodes.Status100Continue)]
|
||||
[InlineData(StatusCodes.Status101SwitchingProtocols)]
|
||||
[InlineData(StatusCodes.Status102Processing)]
|
||||
[InlineData(StatusCodes.Status201Created)]
|
||||
[InlineData(StatusCodes.Status202Accepted)]
|
||||
[InlineData(StatusCodes.Status203NonAuthoritative)]
|
||||
|
|
@ -296,6 +293,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
[InlineData(StatusCodes.Status205ResetContent)]
|
||||
[InlineData(StatusCodes.Status206PartialContent)]
|
||||
[InlineData(StatusCodes.Status207MultiStatus)]
|
||||
[InlineData(StatusCodes.Status208AlreadyReported)]
|
||||
[InlineData(StatusCodes.Status226IMUsed)]
|
||||
[InlineData(StatusCodes.Status300MultipleChoices)]
|
||||
[InlineData(StatusCodes.Status301MovedPermanently)]
|
||||
[InlineData(StatusCodes.Status302Found)]
|
||||
|
|
@ -325,9 +324,14 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
[InlineData(StatusCodes.Status417ExpectationFailed)]
|
||||
[InlineData(StatusCodes.Status418ImATeapot)]
|
||||
[InlineData(StatusCodes.Status419AuthenticationTimeout)]
|
||||
[InlineData(StatusCodes.Status421MisdirectedRequest)]
|
||||
[InlineData(StatusCodes.Status422UnprocessableEntity)]
|
||||
[InlineData(StatusCodes.Status423Locked)]
|
||||
[InlineData(StatusCodes.Status424FailedDependency)]
|
||||
[InlineData(StatusCodes.Status426UpgradeRequired)]
|
||||
[InlineData(StatusCodes.Status428PreconditionRequired)]
|
||||
[InlineData(StatusCodes.Status429TooManyRequests)]
|
||||
[InlineData(StatusCodes.Status431RequestHeaderFieldsTooLarge)]
|
||||
[InlineData(StatusCodes.Status451UnavailableForLegalReasons)]
|
||||
[InlineData(StatusCodes.Status500InternalServerError)]
|
||||
[InlineData(StatusCodes.Status501NotImplemented)]
|
||||
|
|
@ -337,6 +341,9 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
[InlineData(StatusCodes.Status505HttpVersionNotsupported)]
|
||||
[InlineData(StatusCodes.Status506VariantAlsoNegotiates)]
|
||||
[InlineData(StatusCodes.Status507InsufficientStorage)]
|
||||
[InlineData(StatusCodes.Status508LoopDetected)]
|
||||
[InlineData(StatusCodes.Status510NotExtended)]
|
||||
[InlineData(StatusCodes.Status511NetworkAuthenticationRequired)]
|
||||
public void IsResponseCacheable_NonSuccessStatusCodes_NotAllowed(int statusCode)
|
||||
{
|
||||
var sink = new TestSink();
|
||||
|
|
@ -687,6 +694,29 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
LoggedMessage.ExpirationMaxStaleSatisfied);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsCachedEntryFresh_MaxStaleInfiniteOverridesFreshness_ToFresh()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.HttpContext.Request.Headers[HeaderNames.CacheControl] = new CacheControlHeaderValue()
|
||||
{
|
||||
MaxAge = TimeSpan.FromSeconds(5),
|
||||
MaxStale = true // No value specified means a MaxStaleLimit of infinity
|
||||
}.ToString();
|
||||
context.CachedResponseHeaders = new HeaderDictionary();
|
||||
context.CachedResponseHeaders[HeaderNames.CacheControl] = new CacheControlHeaderValue()
|
||||
{
|
||||
MaxAge = TimeSpan.FromSeconds(5),
|
||||
}.ToString();
|
||||
context.CachedEntryAge = TimeSpan.FromSeconds(6);
|
||||
|
||||
Assert.True(new ResponseCachingPolicyProvider().IsCachedEntryFresh(context));
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.ExpirationInfiniteMaxStaleSatisfied);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsCachedEntryFresh_MaxStaleOverridesFreshness_ButStillNotFresh()
|
||||
{
|
||||
|
|
@ -735,5 +765,30 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
sink.Writes,
|
||||
LoggedMessage.ExpirationMustRevalidate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsCachedEntryFresh_ProxyRevalidateOverridesRequestMaxStale_ToNotFresh()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var context = TestUtils.CreateTestContext(sink);
|
||||
context.HttpContext.Request.Headers[HeaderNames.CacheControl] = new CacheControlHeaderValue()
|
||||
{
|
||||
MaxAge = TimeSpan.FromSeconds(5),
|
||||
MaxStale = true, // This value must be set to true in order to specify MaxStaleLimit
|
||||
MaxStaleLimit = TimeSpan.FromSeconds(2)
|
||||
}.ToString();
|
||||
context.CachedResponseHeaders = new HeaderDictionary();
|
||||
context.CachedResponseHeaders[HeaderNames.CacheControl] = new CacheControlHeaderValue()
|
||||
{
|
||||
MaxAge = TimeSpan.FromSeconds(5),
|
||||
MustRevalidate = true
|
||||
}.ToString();
|
||||
context.CachedEntryAge = TimeSpan.FromSeconds(6);
|
||||
|
||||
Assert.False(new ResponseCachingPolicyProvider().IsCachedEntryFresh(context));
|
||||
TestUtils.AssertLoggedMessages(
|
||||
sink.Writes,
|
||||
LoggedMessage.ExpirationMustRevalidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,10 +123,16 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
using (var server = new TestServer(builder))
|
||||
{
|
||||
var client = server.CreateClient();
|
||||
client.DefaultRequestHeaders.CacheControl =
|
||||
new System.Net.Http.Headers.CacheControlHeaderValue { NoCache = true };
|
||||
|
||||
var initialResponse = await client.SendAsync(TestUtils.CreateRequest(method, ""));
|
||||
|
||||
// verify the response is cached
|
||||
var cachedResponse = await client.SendAsync(TestUtils.CreateRequest(method, ""));
|
||||
await AssertCachedResponseAsync(initialResponse, cachedResponse);
|
||||
|
||||
// assert cached response no longer served
|
||||
client.DefaultRequestHeaders.CacheControl =
|
||||
new System.Net.Http.Headers.CacheControlHeaderValue { NoCache = true };
|
||||
var subsequentResponse = await client.SendAsync(TestUtils.CreateRequest(method, ""));
|
||||
|
||||
await AssertFreshResponseAsync(initialResponse, subsequentResponse);
|
||||
|
|
@ -146,10 +152,16 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
using (var server = new TestServer(builder))
|
||||
{
|
||||
var client = server.CreateClient();
|
||||
client.DefaultRequestHeaders.Pragma.Clear();
|
||||
client.DefaultRequestHeaders.Pragma.Add(new System.Net.Http.Headers.NameValueHeaderValue("no-cache"));
|
||||
|
||||
var initialResponse = await client.SendAsync(TestUtils.CreateRequest(method, ""));
|
||||
|
||||
// verify the response is cached
|
||||
var cachedResponse = await client.SendAsync(TestUtils.CreateRequest(method, ""));
|
||||
await AssertCachedResponseAsync(initialResponse, cachedResponse);
|
||||
|
||||
// assert cached response no longer served
|
||||
client.DefaultRequestHeaders.Pragma.Clear();
|
||||
client.DefaultRequestHeaders.Pragma.Add(new System.Net.Http.Headers.NameValueHeaderValue("no-cache"));
|
||||
var subsequentResponse = await client.SendAsync(TestUtils.CreateRequest(method, ""));
|
||||
|
||||
await AssertFreshResponseAsync(initialResponse, subsequentResponse);
|
||||
|
|
@ -565,7 +577,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async void ServesCachedContent_IfSubsequentRequest_ContainsNoStore()
|
||||
public async void ServesCachedContent_IfSubsequentRequestContainsNoStore()
|
||||
{
|
||||
var builders = TestUtils.CreateBuildersWithResponseCaching();
|
||||
|
||||
|
|
@ -587,7 +599,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async void ServesFreshContent_IfInitialRequestContains_NoStore()
|
||||
public async void ServesFreshContent_IfInitialRequestContainsNoStore()
|
||||
{
|
||||
var builders = TestUtils.CreateBuildersWithResponseCaching();
|
||||
|
||||
|
|
@ -608,6 +620,31 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void ServesFreshContent_IfInitialResponseContainsNoStore()
|
||||
{
|
||||
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
|
||||
{
|
||||
var headers = context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
NoStore = true
|
||||
};
|
||||
await TestUtils.TestRequestDelegate(context);
|
||||
});
|
||||
|
||||
foreach (var builder in builders)
|
||||
{
|
||||
using (var server = new TestServer(builder))
|
||||
{
|
||||
var client = server.CreateClient();
|
||||
var initialResponse = await client.GetAsync("");
|
||||
var subsequentResponse = await client.GetAsync("");
|
||||
|
||||
await AssertFreshResponseAsync(initialResponse, subsequentResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void Serves304_IfIfModifiedSince_Satisfied()
|
||||
{
|
||||
|
|
@ -619,7 +656,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
{
|
||||
var client = server.CreateClient();
|
||||
var initialResponse = await client.GetAsync("");
|
||||
client.DefaultRequestHeaders.IfUnmodifiedSince = DateTimeOffset.MaxValue;
|
||||
client.DefaultRequestHeaders.IfModifiedSince = DateTimeOffset.MaxValue;
|
||||
var subsequentResponse = await client.GetAsync("");
|
||||
|
||||
initialResponse.EnsureSuccessStatusCode();
|
||||
|
|
@ -639,7 +676,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
{
|
||||
var client = server.CreateClient();
|
||||
var initialResponse = await client.GetAsync("");
|
||||
client.DefaultRequestHeaders.IfUnmodifiedSince = DateTimeOffset.MinValue;
|
||||
client.DefaultRequestHeaders.IfModifiedSince = DateTimeOffset.MinValue;
|
||||
var subsequentResponse = await client.GetAsync("");
|
||||
|
||||
await AssertCachedResponseAsync(initialResponse, subsequentResponse);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ using Microsoft.Extensions.Options;
|
|||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Xunit;
|
||||
using ISystemClock = Microsoft.AspNetCore.ResponseCaching.Internal.ISystemClock;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||
{
|
||||
|
|
@ -42,11 +43,19 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
var uniqueId = Guid.NewGuid().ToString();
|
||||
headers.CacheControl = new CacheControlHeaderValue
|
||||
if (headers.CacheControl == null)
|
||||
{
|
||||
Public = true,
|
||||
MaxAge = string.IsNullOrEmpty(expires) ? TimeSpan.FromSeconds(10) : (TimeSpan?)null
|
||||
};
|
||||
headers.CacheControl = new CacheControlHeaderValue
|
||||
{
|
||||
Public = true,
|
||||
MaxAge = string.IsNullOrEmpty(expires) ? TimeSpan.FromSeconds(10) : (TimeSpan?)null
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
headers.CacheControl.Public = true;
|
||||
headers.CacheControl.MaxAge = string.IsNullOrEmpty(expires) ? TimeSpan.FromSeconds(10) : (TimeSpan?)null;
|
||||
}
|
||||
headers.Date = DateTimeOffset.UtcNow;
|
||||
headers.Headers["X-Value"] = uniqueId;
|
||||
|
||||
|
|
@ -103,12 +112,17 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
internal static ResponseCachingMiddleware CreateTestMiddleware(
|
||||
RequestDelegate next = null,
|
||||
IResponseCache cache = null,
|
||||
ResponseCachingOptions options = null,
|
||||
TestSink testSink = null,
|
||||
IResponseCachingKeyProvider keyProvider = null,
|
||||
IResponseCachingPolicyProvider policyProvider = null)
|
||||
{
|
||||
if (next == null)
|
||||
{
|
||||
next = httpContext => TaskCache.CompletedTask;
|
||||
}
|
||||
if (cache == null)
|
||||
{
|
||||
cache = new TestResponseCache();
|
||||
|
|
@ -127,7 +141,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
}
|
||||
|
||||
return new ResponseCachingMiddleware(
|
||||
httpContext => TaskCache.CompletedTask,
|
||||
next,
|
||||
Options.Create(options),
|
||||
testSink == null ? (ILoggerFactory)NullLoggerFactory.Instance : new TestLoggerFactory(testSink, true),
|
||||
policyProvider,
|
||||
|
|
@ -188,7 +202,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
internal static LoggedMessage ResponseWithUnsuccessfulStatusCodeNotCacheable => new LoggedMessage(17, LogLevel.Debug);
|
||||
internal static LoggedMessage NotModifiedIfNoneMatchStar => new LoggedMessage(18, LogLevel.Debug);
|
||||
internal static LoggedMessage NotModifiedIfNoneMatchMatched => new LoggedMessage(19, LogLevel.Debug);
|
||||
internal static LoggedMessage NotModifiedIfUnmodifiedSinceSatisfied => new LoggedMessage(20, LogLevel.Debug);
|
||||
internal static LoggedMessage NotModifiedIfModifiedSinceSatisfied => new LoggedMessage(20, LogLevel.Debug);
|
||||
internal static LoggedMessage NotModifiedServed => new LoggedMessage(21, LogLevel.Information);
|
||||
internal static LoggedMessage CachedResponseServed => new LoggedMessage(22, LogLevel.Information);
|
||||
internal static LoggedMessage GatewayTimeoutServed => new LoggedMessage(23, LogLevel.Information);
|
||||
|
|
@ -197,6 +211,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
internal static LoggedMessage ResponseCached => new LoggedMessage(26, LogLevel.Information);
|
||||
internal static LoggedMessage ResponseNotCached => new LoggedMessage(27, LogLevel.Information);
|
||||
internal static LoggedMessage ResponseContentLengthMismatchNotCached => new LoggedMessage(28, LogLevel.Warning);
|
||||
internal static LoggedMessage ExpirationInfiniteMaxStaleSatisfied => new LoggedMessage(29, LogLevel.Debug);
|
||||
|
||||
private LoggedMessage(int evenId, LogLevel logLevel)
|
||||
{
|
||||
|
|
@ -218,11 +233,21 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
|
||||
internal class TestResponseCachingPolicyProvider : IResponseCachingPolicyProvider
|
||||
{
|
||||
public bool IsCachedEntryFresh(ResponseCachingContext context) => true;
|
||||
public bool AllowCacheLookupValue { get; set; } = false;
|
||||
public bool AllowCacheStorageValue { get; set; } = false;
|
||||
public bool AttemptResponseCachingValue { get; set; } = false;
|
||||
public bool IsCachedEntryFreshValue { get; set; } = true;
|
||||
public bool IsResponseCacheableValue { get; set; } = true;
|
||||
|
||||
public bool IsRequestCacheable(ResponseCachingContext context) => true;
|
||||
public bool AllowCacheLookup(ResponseCachingContext context) => AllowCacheLookupValue;
|
||||
|
||||
public bool IsResponseCacheable(ResponseCachingContext context) => true;
|
||||
public bool AllowCacheStorage(ResponseCachingContext context) => AllowCacheStorageValue;
|
||||
|
||||
public bool AttemptResponseCaching(ResponseCachingContext context) => AttemptResponseCachingValue;
|
||||
|
||||
public bool IsCachedEntryFresh(ResponseCachingContext context) => IsCachedEntryFreshValue;
|
||||
|
||||
public bool IsResponseCacheable(ResponseCachingContext context) => IsResponseCacheableValue;
|
||||
}
|
||||
|
||||
internal class TestResponseCachingKeyProvider : IResponseCachingKeyProvider
|
||||
|
|
@ -284,4 +309,9 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
return TaskCache.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
internal class TestClock : ISystemClock
|
||||
{
|
||||
public DateTimeOffset UtcNow { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue