Tigger caching header finalization on write #92

This commit is contained in:
John Luo 2017-02-08 16:06:20 -08:00
parent d43f05189a
commit 7190412979
8 changed files with 243 additions and 158 deletions

View File

@ -8,7 +8,10 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
{
public interface IResponseCache
{
IResponseCacheEntry Get(string key);
Task<IResponseCacheEntry> GetAsync(string key);
void Set(string key, IResponseCacheEntry entry, TimeSpan validFor);
Task SetAsync(string key, IResponseCacheEntry entry, TimeSpan validFor);
}
}

View File

@ -4,7 +4,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Net.Http.Headers;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.ResponseCaching.Internal
{
@ -22,34 +22,39 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
_cache = cache;
}
public Task<IResponseCacheEntry> GetAsync(string key)
public IResponseCacheEntry Get(string key)
{
var entry = _cache.Get(key);
var memoryCachedResponse = entry as MemoryCachedResponse;
if (memoryCachedResponse != null)
{
return Task.FromResult<IResponseCacheEntry>(new CachedResponse
return new CachedResponse
{
Created = memoryCachedResponse.Created,
StatusCode = memoryCachedResponse.StatusCode,
Headers = memoryCachedResponse.Headers,
Body = new SegmentReadStream(memoryCachedResponse.BodySegments, memoryCachedResponse.BodyLength)
});
};
}
else
{
return Task.FromResult(entry as IResponseCacheEntry);
return entry as IResponseCacheEntry;
}
}
public async Task SetAsync(string key, IResponseCacheEntry entry, TimeSpan validFor)
public Task<IResponseCacheEntry> GetAsync(string key)
{
return Task.FromResult(Get(key));
}
public void Set(string key, IResponseCacheEntry entry, TimeSpan validFor)
{
var cachedResponse = entry as CachedResponse;
if (cachedResponse != null)
{
var segmentStream = new SegmentWriteStream(StreamUtilities.BodySegmentSize);
await cachedResponse.Body.CopyToAsync(segmentStream);
cachedResponse.Body.CopyTo(segmentStream);
_cache.Set(
key,
@ -77,5 +82,11 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
});
}
}
public Task SetAsync(string key, IResponseCacheEntry entry, TimeSpan validFor)
{
Set(key, entry, validFor);
return TaskCache.CompletedTask;
}
}
}

View File

@ -17,7 +17,7 @@
<PackageReference Include="Microsoft.AspNetCore.Http" Version="1.2.0-*" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="1.2.0-*" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.2.0-*" />
<PackageReference Include="Microsoft.Extensions.TaskCache.Sources" Version="1.2.0-*" PrivateAssets="All"/>
<PackageReference Include="Microsoft.Extensions.TaskCache.Sources" Version="1.2.0-*" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -25,7 +25,6 @@ namespace Microsoft.AspNetCore.ResponseCaching
private readonly IResponseCachingPolicyProvider _policyProvider;
private readonly IResponseCache _cache;
private readonly IResponseCachingKeyProvider _keyProvider;
private readonly Func<object, Task> _onStartingCallback;
public ResponseCachingMiddleware(
RequestDelegate next,
@ -66,7 +65,6 @@ namespace Microsoft.AspNetCore.ResponseCaching
_policyProvider = policyProvider;
_cache = cache;
_keyProvider = keyProvider;
_onStartingCallback = state => OnResponseStartingAsync((ResponseCachingContext)state);
}
public async Task Invoke(HttpContext httpContext)
@ -90,13 +88,10 @@ namespace Microsoft.AspNetCore.ResponseCaching
try
{
// Subscribe to OnStarting event
httpContext.Response.OnStarting(_onStartingCallback, context);
await _next(httpContext);
// If there was no response body, check the response headers now. We can cache things like redirects.
await OnResponseStartingAsync(context);
await StartResponseAsync(context);
// Finalize the cache entry
await FinalizeCacheBodyAsync(context);
@ -219,10 +214,17 @@ namespace Microsoft.AspNetCore.ResponseCaching
return false;
}
internal async Task FinalizeCacheHeadersAsync(ResponseCachingContext context)
/// <summary>
/// Finalize cache headers.
/// </summary>
/// <param name="context"></param>
/// <returns><c>true</c> if a vary by entry needs to be stored in the cache; otherwise <c>false</c>.</returns>
private bool OnFinalizeCacheHeaders(ResponseCachingContext context)
{
if (_policyProvider.IsResponseCacheable(context))
{
var storeVaryByEntry = false;
context.ShouldCacheResponse = true;
// Create the cache entry now
@ -262,7 +264,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
// Always overwrite the CachedVaryByRules to update the expiry information
_logger.LogVaryByRulesUpdated(normalizedVaryHeaders, normalizedVaryQueryKeys);
await _cache.SetAsync(context.BaseKey, context.CachedVaryByRules, context.CachedResponseValidFor);
storeVaryByEntry = true;
context.StorageVaryKey = _keyProvider.CreateStorageVaryByKey(context);
}
@ -290,13 +292,31 @@ namespace Microsoft.AspNetCore.ResponseCaching
context.CachedResponse.Headers[header.Key] = header.Value;
}
}
return storeVaryByEntry;
}
else
context.ResponseCachingStream.DisableBuffering();
return false;
}
internal void FinalizeCacheHeaders(ResponseCachingContext context)
{
if (OnFinalizeCacheHeaders(context))
{
context.ResponseCachingStream.DisableBuffering();
_cache.Set(context.BaseKey, context.CachedVaryByRules, context.CachedResponseValidFor);
}
}
internal Task FinalizeCacheHeadersAsync(ResponseCachingContext context)
{
if (OnFinalizeCacheHeaders(context))
{
return _cache.SetAsync(context.BaseKey, context.CachedVaryByRules, context.CachedResponseValidFor);
}
return TaskCache.CompletedTask;
}
internal async Task FinalizeCacheBodyAsync(ResponseCachingContext context)
{
if (context.ShouldCacheResponse && context.ResponseCachingStream.BufferingEnabled)
@ -327,19 +347,38 @@ namespace Microsoft.AspNetCore.ResponseCaching
}
}
internal Task OnResponseStartingAsync(ResponseCachingContext context)
/// <summary>
/// Mark the response as started and set the response time if no reponse was started yet.
/// </summary>
/// <param name="context"></param>
/// <returns><c>true</c> if the response was not started before this call; otherwise <c>false</c>.</returns>
private bool OnStartResponse(ResponseCachingContext context)
{
if (!context.ResponseStarted)
{
context.ResponseStarted = true;
context.ResponseTime = _options.SystemClock.UtcNow;
return true;
}
return false;
}
internal void StartResponse(ResponseCachingContext context)
{
if (OnStartResponse(context))
{
FinalizeCacheHeaders(context);
}
}
internal Task StartResponseAsync(ResponseCachingContext context)
{
if (OnStartResponse(context))
{
return FinalizeCacheHeadersAsync(context);
}
else
{
return TaskCache.CompletedTask;
}
return TaskCache.CompletedTask;
}
internal static void AddResponseCachingFeature(HttpContext context)
@ -355,7 +394,12 @@ namespace Microsoft.AspNetCore.ResponseCaching
{
// Shim response stream
context.OriginalResponseStream = context.HttpContext.Response.Body;
context.ResponseCachingStream = new ResponseCachingStream(context.OriginalResponseStream, _options.MaximumBodySize, StreamUtilities.BodySegmentSize);
context.ResponseCachingStream = new ResponseCachingStream(
context.OriginalResponseStream,
_options.MaximumBodySize,
StreamUtilities.BodySegmentSize,
() => StartResponse(context),
() => StartResponseAsync(context));
context.HttpContext.Response.Body = context.ResponseCachingStream;
// Shim IHttpSendFileFeature

View File

@ -14,12 +14,16 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
private readonly long _maxBufferSize;
private readonly int _segmentSize;
private SegmentWriteStream _segmentWriteStream;
private Action _startResponseCallback;
private Func<Task> _startResponseCallbackAsync;
internal ResponseCachingStream(Stream innerStream, long maxBufferSize, int segmentSize)
internal ResponseCachingStream(Stream innerStream, long maxBufferSize, int segmentSize, Action startResponseCallback, Func<Task> startResponseCallbackAsync)
{
_innerStream = innerStream;
_maxBufferSize = maxBufferSize;
_segmentSize = segmentSize;
_startResponseCallback = startResponseCallback;
_startResponseCallbackAsync = startResponseCallbackAsync;
_segmentWriteStream = new SegmentWriteStream(_segmentSize);
}
@ -71,10 +75,32 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
}
public override void Flush()
=> _innerStream.Flush();
{
try
{
_startResponseCallback();
_innerStream.Flush();
}
catch
{
DisableBuffering();
throw;
}
}
public override Task FlushAsync(CancellationToken cancellationToken)
=> _innerStream.FlushAsync();
public override async Task FlushAsync(CancellationToken cancellationToken)
{
try
{
await _startResponseCallbackAsync();
await _innerStream.FlushAsync();
}
catch
{
DisableBuffering();
throw;
}
}
// Underlying stream is write-only, no need to override other read related methods
public override int Read(byte[] buffer, int offset, int count)
@ -84,6 +110,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
{
try
{
_startResponseCallback();
_innerStream.Write(buffer, offset, count);
}
catch
@ -109,6 +136,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
{
try
{
await _startResponseCallbackAsync();
await _innerStream.WriteAsync(buffer, offset, count, cancellationToken);
}
catch

View File

@ -359,7 +359,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
}
[Fact]
public async Task OnResponseStartingAsync_IfAllowResponseCaptureIsTrue_SetsResponseTime()
public async Task StartResponsegAsync_IfAllowResponseCaptureIsTrue_SetsResponseTime()
{
var clock = new TestClock
{
@ -372,13 +372,13 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
var context = TestUtils.CreateTestContext();
context.ResponseTime = null;
await middleware.OnResponseStartingAsync(context);
await middleware.StartResponseAsync(context);
Assert.Equal(clock.UtcNow, context.ResponseTime);
}
[Fact]
public async Task OnResponseStartingAsync_IfAllowResponseCaptureIsTrue_SetsResponseTimeOnlyOnce()
public async Task StartResponseAsync_IfAllowResponseCaptureIsTrue_SetsResponseTimeOnlyOnce()
{
var clock = new TestClock
{
@ -392,12 +392,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
var initialTime = clock.UtcNow;
context.ResponseTime = null;
await middleware.OnResponseStartingAsync(context);
await middleware.StartResponseAsync(context);
Assert.Equal(initialTime, context.ResponseTime);
clock.UtcNow += TimeSpan.FromSeconds(10);
await middleware.OnResponseStartingAsync(context);
await middleware.StartResponseAsync(context);
Assert.NotEqual(clock.UtcNow, context.ResponseTime);
Assert.Equal(initialTime, context.ResponseTime);
}
@ -790,10 +790,9 @@ 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);

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@ -233,11 +232,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesCachedContent_IfVaryHeader_Matches()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Response.Headers[HeaderNames.Vary] = HeaderNames.From;
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Response.Headers[HeaderNames.Vary] = HeaderNames.From);
foreach (var builder in builders)
{
@ -256,11 +251,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesFreshContent_IfVaryHeader_Mismatches()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Response.Headers[HeaderNames.Vary] = HeaderNames.From;
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Response.Headers[HeaderNames.Vary] = HeaderNames.From);
foreach (var builder in builders)
{
@ -280,11 +271,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesCachedContent_IfVaryQueryKeys_Matches()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "query" };
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "query" });
foreach (var builder in builders)
{
@ -302,11 +289,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesCachedContent_IfVaryQueryKeysExplicit_Matches_QueryKeyCaseInsensitive()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "QueryA", "queryb" };
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "QueryA", "queryb" });
foreach (var builder in builders)
{
@ -324,11 +307,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesCachedContent_IfVaryQueryKeyStar_Matches_QueryKeyCaseInsensitive()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "*" };
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "*" });
foreach (var builder in builders)
{
@ -346,11 +325,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesCachedContent_IfVaryQueryKeyExplicit_Matches_OrderInsensitive()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "QueryB", "QueryA" };
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "QueryB", "QueryA" });
foreach (var builder in builders)
{
@ -368,11 +343,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesCachedContent_IfVaryQueryKeyStar_Matches_OrderInsensitive()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "*" };
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "*" });
foreach (var builder in builders)
{
@ -390,11 +361,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesFreshContent_IfVaryQueryKey_Mismatches()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "query" };
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "query" });
foreach (var builder in builders)
{
@ -412,11 +379,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesFreshContent_IfVaryQueryKeyExplicit_Mismatch_QueryKeyCaseSensitive()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "QueryA", "QueryB" };
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "QueryA", "QueryB" });
foreach (var builder in builders)
{
@ -434,11 +397,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesFreshContent_IfVaryQueryKeyStar_Mismatch_QueryKeyValueCaseSensitive()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "*" };
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Features.Get<IResponseCachingFeature>().VaryByQueryKeys = new[] { "*" });
foreach (var builder in builders)
{
@ -501,11 +460,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesFreshContent_IfSetCookie_IsSpecified()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
var headers = context.Response.Headers[HeaderNames.SetCookie] = "cookieName=cookieValue";
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Response.Headers[HeaderNames.SetCookie] = "cookieName=cookieValue");
foreach (var builder in builders)
{
@ -557,11 +512,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
await next.Invoke();
});
},
requestDelegate: async (context) =>
{
await context.Features.Get<IHttpSendFileFeature>().SendFileAsync("dummy", 0, 0, CancellationToken.None);
await TestUtils.TestRequestDelegate(context);
});
contextAction: async context => await context.Features.Get<IHttpSendFileFeature>().SendFileAsync("dummy", 0, 0, CancellationToken.None));
foreach (var builder in builders)
{
@ -623,14 +574,7 @@ 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);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Response.Headers[HeaderNames.CacheControl] = CacheControlHeaderValue.NoStoreString);
foreach (var builder in builders)
{
@ -687,11 +631,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void Serves304_IfIfNoneMatch_Satisfied()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
var headers = context.Response.GetTypedHeaders().ETag = new EntityTagHeaderValue("\"E1\"");
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Response.GetTypedHeaders().ETag = new EntityTagHeaderValue("\"E1\""));
foreach (var builder in builders)
{
@ -711,11 +651,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesCachedContent_IfIfNoneMatch_NotSatisfied()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
var headers = context.Response.GetTypedHeaders().ETag = new EntityTagHeaderValue("\"E1\"");
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Response.GetTypedHeaders().ETag = new EntityTagHeaderValue("\"E1\""));
foreach (var builder in builders)
{
@ -797,11 +733,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesCachedContent_WithoutReplacingCachedVaryBy_OnCacheMiss()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Response.Headers[HeaderNames.Vary] = HeaderNames.From;
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Response.Headers[HeaderNames.Vary] = HeaderNames.From);
foreach (var builder in builders)
{
@ -823,11 +755,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesFreshContent_IfCachedVaryByUpdated_OnCacheMiss()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Response.Headers[HeaderNames.Vary] = context.Request.Headers[HeaderNames.Pragma];
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Response.Headers[HeaderNames.Vary] = context.Request.Headers[HeaderNames.Pragma]);
foreach (var builder in builders)
{
@ -858,11 +786,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact]
public async void ServesCachedContent_IfCachedVaryByNotUpdated_OnCacheMiss()
{
var builders = TestUtils.CreateBuildersWithResponseCaching(requestDelegate: async (context) =>
{
context.Response.Headers[HeaderNames.Vary] = context.Request.Headers[HeaderNames.Pragma];
await TestUtils.TestRequestDelegate(context);
});
var builders = TestUtils.CreateBuildersWithResponseCaching(contextAction: context => context.Response.Headers[HeaderNames.Vary] = context.Request.Headers[HeaderNames.Pragma]);
foreach (var builder in builders)
{

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
@ -32,7 +33,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
StreamUtilities.BodySegmentSize = 10;
}
internal static RequestDelegate TestRequestDelegate = async context =>
private static bool TestRequestDelegate(HttpContext context, string guid)
{
var headers = context.Response.GetTypedHeaders();
@ -42,7 +43,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
headers.Expires = DateTimeOffset.Now.AddSeconds(int.Parse(expires));
}
var uniqueId = Guid.NewGuid().ToString();
if (headers.CacheControl == null)
{
headers.CacheControl = new CacheControlHeaderValue
@ -57,13 +57,33 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
headers.CacheControl.MaxAge = string.IsNullOrEmpty(expires) ? TimeSpan.FromSeconds(10) : (TimeSpan?)null;
}
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
headers.Headers["X-Value"] = guid;
if (context.Request.Method != "HEAD")
{
return true;
}
return false;
}
internal static async Task TestRequestDelegateWriteAsync(HttpContext context)
{
var uniqueId = Guid.NewGuid().ToString();
if (TestRequestDelegate(context, uniqueId))
{
await context.Response.WriteAsync(uniqueId);
}
};
}
internal static Task TestRequestDelegateWrite(HttpContext context)
{
var uniqueId = Guid.NewGuid().ToString();
if (TestRequestDelegate(context, uniqueId))
{
context.Response.Write(uniqueId);
}
return TaskCache.CompletedTask;
}
internal static IResponseCachingKeyProvider CreateTestKeyProvider()
{
@ -78,37 +98,64 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
internal static IEnumerable<IWebHostBuilder> CreateBuildersWithResponseCaching(
Action<IApplicationBuilder> configureDelegate = null,
ResponseCachingOptions options = null,
RequestDelegate requestDelegate = null)
Action<HttpContext> contextAction = null)
{
return CreateBuildersWithResponseCaching(configureDelegate, options, new RequestDelegate[]
{
context =>
{
contextAction?.Invoke(context);
return TestRequestDelegateWrite(context);
},
context =>
{
contextAction?.Invoke(context);
return TestRequestDelegateWriteAsync(context);
},
});
}
private static IEnumerable<IWebHostBuilder> CreateBuildersWithResponseCaching(
Action<IApplicationBuilder> configureDelegate = null,
ResponseCachingOptions options = null,
IEnumerable<RequestDelegate> requestDelegates = null)
{
if (configureDelegate == null)
{
configureDelegate = app => { };
}
if (requestDelegate == null)
if (requestDelegates == null)
{
requestDelegate = TestRequestDelegate;
requestDelegates = new RequestDelegate[]
{
TestRequestDelegateWriteAsync,
TestRequestDelegateWrite
};
}
// Test with in memory ResponseCache
yield return new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddResponseCaching(responseCachingOptions =>
foreach (var requestDelegate in requestDelegates)
{
// Test with in memory ResponseCache
yield return new WebHostBuilder()
.ConfigureServices(services =>
{
if (options != null)
services.AddResponseCaching(responseCachingOptions =>
{
responseCachingOptions.MaximumBodySize = options.MaximumBodySize;
responseCachingOptions.UseCaseSensitivePaths = options.UseCaseSensitivePaths;
responseCachingOptions.SystemClock = options.SystemClock;
}
if (options != null)
{
responseCachingOptions.MaximumBodySize = options.MaximumBodySize;
responseCachingOptions.UseCaseSensitivePaths = options.UseCaseSensitivePaths;
responseCachingOptions.SystemClock = options.SystemClock;
}
});
})
.Configure(app =>
{
configureDelegate(app);
app.UseResponseCaching();
app.Run(requestDelegate);
});
})
.Configure(app =>
{
configureDelegate(app);
app.UseResponseCaching();
app.Run(requestDelegate);
});
}
}
internal static ResponseCachingMiddleware CreateTestMiddleware(
@ -181,6 +228,25 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
}
}
internal static class HttpResponseWritingExtensions
{
internal static void Write(this HttpResponse response, string text)
{
if (response == null)
{
throw new ArgumentNullException(nameof(response));
}
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
byte[] data = Encoding.UTF8.GetBytes(text);
response.Body.Write(data, 0, data.Length);
}
}
internal class LoggedMessage
{
internal static LoggedMessage RequestMethodNotCacheable => new LoggedMessage(1, LogLevel.Debug);
@ -289,23 +355,33 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
public int GetCount { get; private set; }
public int SetCount { get; private set; }
public Task<IResponseCacheEntry> GetAsync(string key)
public IResponseCacheEntry Get(string key)
{
GetCount++;
try
{
return Task.FromResult(_storage[key]);
return _storage[key];
}
catch
{
return Task.FromResult<IResponseCacheEntry>(null);
return null;
}
}
public Task<IResponseCacheEntry> GetAsync(string key)
{
return Task.FromResult(Get(key));
}
public void Set(string key, IResponseCacheEntry entry, TimeSpan validFor)
{
SetCount++;
_storage[key] = entry;
}
public Task SetAsync(string key, IResponseCacheEntry entry, TimeSpan validFor)
{
SetCount++;
_storage[key] = entry;
Set(key, entry, validFor);
return TaskCache.CompletedTask;
}
}