Adding options to specify maximum response body size

This commit is contained in:
John Luo 2016-08-31 17:33:01 -07:00
parent 3b0f01a8ec
commit d72ef128dd
7 changed files with 255 additions and 381 deletions

View File

@ -11,10 +11,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
internal class ResponseCacheStream : Stream internal class ResponseCacheStream : Stream
{ {
private readonly Stream _innerStream; private readonly Stream _innerStream;
private readonly long _maxBufferSize;
public ResponseCacheStream(Stream innerStream) public ResponseCacheStream(Stream innerStream, long maxBufferSize)
{ {
_innerStream = innerStream; _innerStream = innerStream;
_maxBufferSize = maxBufferSize;
} }
public MemoryStream BufferedStream { get; } = new MemoryStream(); public MemoryStream BufferedStream { get; } = new MemoryStream();
@ -38,6 +40,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
public void DisableBuffering() public void DisableBuffering()
{ {
BufferingEnabled = false; BufferingEnabled = false;
BufferedStream.SetLength(0);
BufferedStream.Capacity = 0;
BufferedStream.Dispose(); BufferedStream.Dispose();
} }
@ -77,7 +81,14 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
if (BufferingEnabled) if (BufferingEnabled)
{ {
BufferedStream.Write(buffer, offset, count); if (BufferedStream.Length + count > _maxBufferSize)
{
DisableBuffering();
}
else
{
BufferedStream.Write(buffer, offset, count);
}
} }
} }
@ -95,7 +106,14 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
if (BufferingEnabled) if (BufferingEnabled)
{ {
await BufferedStream.WriteAsync(buffer, offset, count, cancellationToken); if (BufferedStream.Length + count > _maxBufferSize)
{
DisableBuffering();
}
else
{
await BufferedStream.WriteAsync(buffer, offset, count, cancellationToken);
}
} }
} }
@ -113,7 +131,14 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
if (BufferingEnabled) if (BufferingEnabled)
{ {
BufferedStream.WriteByte(value); if (BufferedStream.Length + 1 > _maxBufferSize)
{
DisableBuffering();
}
else
{
BufferedStream.WriteByte(value);
}
} }
} }

View File

@ -7,6 +7,7 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Headers; using Microsoft.AspNetCore.Http.Headers;
@ -25,7 +26,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
private readonly HttpContext _httpContext; private readonly HttpContext _httpContext;
private readonly IResponseCache _cache; private readonly IResponseCache _cache;
private readonly ISystemClock _clock; private readonly ResponseCachingOptions _options;
private readonly ObjectPool<StringBuilder> _builderPool; private readonly ObjectPool<StringBuilder> _builderPool;
private readonly IResponseCachingCacheabilityValidator _cacheabilityValidator; private readonly IResponseCachingCacheabilityValidator _cacheabilityValidator;
private readonly IResponseCachingCacheKeyModifier _cacheKeyModifier; private readonly IResponseCachingCacheKeyModifier _cacheKeyModifier;
@ -40,29 +41,19 @@ namespace Microsoft.AspNetCore.ResponseCaching
private CachedResponse _cachedResponse; private CachedResponse _cachedResponse;
private TimeSpan _cachedResponseValidFor; private TimeSpan _cachedResponseValidFor;
internal DateTimeOffset _responseTime; internal DateTimeOffset _responseTime;
internal ResponseCachingContext(
HttpContext httpContext,
IResponseCache cache,
ObjectPool<StringBuilder> builderPool,
IResponseCachingCacheabilityValidator cacheabilityValidator,
IResponseCachingCacheKeyModifier cacheKeyModifier)
: this(httpContext, cache, new SystemClock(), builderPool, cacheabilityValidator, cacheKeyModifier)
{
}
// Internal for testing // Internal for testing
internal ResponseCachingContext( internal ResponseCachingContext(
HttpContext httpContext, HttpContext httpContext,
IResponseCache cache, IResponseCache cache,
ISystemClock clock, ResponseCachingOptions options,
ObjectPool<StringBuilder> builderPool, ObjectPool<StringBuilder> builderPool,
IResponseCachingCacheabilityValidator cacheabilityValidator, IResponseCachingCacheabilityValidator cacheabilityValidator,
IResponseCachingCacheKeyModifier cacheKeyModifier) IResponseCachingCacheKeyModifier cacheKeyModifier)
{ {
_httpContext = httpContext; _httpContext = httpContext;
_cache = cache; _cache = cache;
_clock = clock; _options = options;
_builderPool = builderPool; _builderPool = builderPool;
_cacheabilityValidator = cacheabilityValidator; _cacheabilityValidator = cacheabilityValidator;
_cacheKeyModifier = cacheKeyModifier; _cacheKeyModifier = cacheKeyModifier;
@ -74,10 +65,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
{ {
if (_cacheResponse == null) if (_cacheResponse == null)
{ {
// TODO: apparent age vs corrected age value _cacheResponse = ResponseIsCacheable();
var responseAge = _responseTime - ResponseHeaders.Date ?? TimeSpan.Zero;
_cacheResponse = ResponseIsCacheable() && EntryIsFresh(ResponseHeaders, responseAge, verifyAgainstRequest: false);
} }
return _cacheResponse.Value; return _cacheResponse.Value;
} }
@ -363,6 +351,14 @@ namespace Microsoft.AspNetCore.ResponseCaching
return false; return false;
} }
// Check response freshness
// TODO: apparent age vs corrected age value
var responseAge = _responseTime - ResponseHeaders.Date ?? TimeSpan.Zero;
if (!EntryIsFresh(ResponseHeaders, responseAge, verifyAgainstRequest: false))
{
return false;
}
return true; return true;
} }
@ -433,7 +429,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
var cachedResponse = cacheEntry as CachedResponse; var cachedResponse = cacheEntry as CachedResponse;
var cachedResponseHeaders = new ResponseHeaders(cachedResponse.Headers); var cachedResponseHeaders = new ResponseHeaders(cachedResponse.Headers);
_responseTime = _clock.UtcNow; _responseTime = _options.SystemClock.UtcNow;
var age = _responseTime - cachedResponse.Created; var age = _responseTime - cachedResponse.Created;
age = age > TimeSpan.Zero ? age : TimeSpan.Zero; age = age > TimeSpan.Zero ? age : TimeSpan.Zero;
@ -607,7 +603,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
if (!ResponseStarted) if (!ResponseStarted)
{ {
ResponseStarted = true; ResponseStarted = true;
_responseTime = _clock.UtcNow; _responseTime = _options.SystemClock.UtcNow;
FinalizeCachingHeaders(); FinalizeCachingHeaders();
} }
@ -619,7 +615,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
// Shim response stream // Shim response stream
OriginalResponseStream = _httpContext.Response.Body; OriginalResponseStream = _httpContext.Response.Body;
ResponseCacheStream = new ResponseCacheStream(OriginalResponseStream); ResponseCacheStream = new ResponseCacheStream(OriginalResponseStream, _options.MaximumCachedBodySize);
_httpContext.Response.Body = ResponseCacheStream; _httpContext.Response.Body = ResponseCacheStream;
// Shim IHttpSendFileFeature // Shim IHttpSendFileFeature

View File

@ -3,6 +3,7 @@
using System; using System;
using Microsoft.AspNetCore.ResponseCaching; using Microsoft.AspNetCore.ResponseCaching;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Builder namespace Microsoft.AspNetCore.Builder
{ {
@ -17,5 +18,19 @@ namespace Microsoft.AspNetCore.Builder
return app.UseMiddleware<ResponseCachingMiddleware>(); return app.UseMiddleware<ResponseCachingMiddleware>();
} }
public static IApplicationBuilder UseResponseCaching(this IApplicationBuilder app, ResponseCachingOptions options)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return app.UseMiddleware<ResponseCachingMiddleware>(Options.Create(options));
}
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -20,13 +21,15 @@ namespace Microsoft.AspNetCore.ResponseCaching
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly IResponseCache _cache; private readonly IResponseCache _cache;
private readonly ResponseCachingOptions _options;
private readonly ObjectPool<StringBuilder> _builderPool; private readonly ObjectPool<StringBuilder> _builderPool;
private readonly IResponseCachingCacheabilityValidator _cacheabilityValidator; private readonly IResponseCachingCacheabilityValidator _cacheabilityValidator;
private readonly IResponseCachingCacheKeyModifier _cacheKeyModifier; private readonly IResponseCachingCacheKeyModifier _cacheKeyModifier;
public ResponseCachingMiddleware( public ResponseCachingMiddleware(
RequestDelegate next, RequestDelegate next,
IResponseCache cache, IResponseCache cache,
IOptions<ResponseCachingOptions> options,
ObjectPoolProvider poolProvider, ObjectPoolProvider poolProvider,
IResponseCachingCacheabilityValidator cacheabilityValidator, IResponseCachingCacheabilityValidator cacheabilityValidator,
IResponseCachingCacheKeyModifier cacheKeyModifier) IResponseCachingCacheKeyModifier cacheKeyModifier)
@ -39,6 +42,10 @@ namespace Microsoft.AspNetCore.ResponseCaching
{ {
throw new ArgumentNullException(nameof(cache)); throw new ArgumentNullException(nameof(cache));
} }
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (poolProvider == null) if (poolProvider == null)
{ {
throw new ArgumentNullException(nameof(poolProvider)); throw new ArgumentNullException(nameof(poolProvider));
@ -54,6 +61,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
_next = next; _next = next;
_cache = cache; _cache = cache;
_options = options.Value;
_builderPool = poolProvider.CreateStringBuilderPool(); _builderPool = poolProvider.CreateStringBuilderPool();
_cacheabilityValidator = cacheabilityValidator; _cacheabilityValidator = cacheabilityValidator;
_cacheKeyModifier = cacheKeyModifier; _cacheKeyModifier = cacheKeyModifier;
@ -64,6 +72,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
var cachingContext = new ResponseCachingContext( var cachingContext = new ResponseCachingContext(
context, context,
_cache, _cache,
_options,
_builderPool, _builderPool,
_cacheabilityValidator, _cacheabilityValidator,
_cacheKeyModifier); _cacheKeyModifier);

View File

@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.ComponentModel;
using Microsoft.AspNetCore.ResponseCaching.Internal;
namespace Microsoft.AspNetCore.Builder
{
public class ResponseCachingOptions
{
/// <summary>
/// The largest cacheable size for the response body in bytes.
/// </summary>
public long MaximumCachedBodySize { get; set; } = 1024 * 1024;
/// <summary>
/// For testing purposes only.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
internal ISystemClock SystemClock { get; set; } = new SystemClock();
}
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Headers; using Microsoft.AspNetCore.Http.Headers;
@ -859,7 +860,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
return new ResponseCachingContext( return new ResponseCachingContext(
httpContext, httpContext,
new TestResponseCache(), new TestResponseCache(),
new SystemClock(), new ResponseCachingOptions(),
new DefaultObjectPool<StringBuilder>(new StringBuilderPooledObjectPolicy()), new DefaultObjectPool<StringBuilder>(new StringBuilderPooledObjectPolicy()),
cacheabilityValidator, cacheabilityValidator,
cacheKeyModifier); cacheKeyModifier);

View File

@ -21,19 +21,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact] [Fact]
public async void ServesCachedContent_IfAvailable() public async void ServesCachedContent_IfAvailable()
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching();
{
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
await context.Response.WriteAsync(uniqueId);
});
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
{ {
@ -48,19 +36,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact] [Fact]
public async void ServesFreshContent_IfNotAvailable() public async void ServesFreshContent_IfNotAvailable()
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching();
{
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
await context.Response.WriteAsync(uniqueId);
});
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
{ {
@ -77,17 +53,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching(async (context) =>
{ {
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
context.Response.Headers[HeaderNames.Vary] = HeaderNames.From; context.Response.Headers[HeaderNames.Vary] = HeaderNames.From;
await context.Response.WriteAsync(uniqueId); await DefaultRequestDelegate(context);
}); });
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
@ -106,17 +73,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching(async (context) =>
{ {
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
context.Response.Headers[HeaderNames.Vary] = HeaderNames.From; context.Response.Headers[HeaderNames.Vary] = HeaderNames.From;
await context.Response.WriteAsync(uniqueId); await DefaultRequestDelegate(context);
}); });
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
@ -136,17 +94,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching(async (context) =>
{ {
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
context.GetResponseCachingFeature().VaryByParams = "param"; context.GetResponseCachingFeature().VaryByParams = "param";
await context.Response.WriteAsync(uniqueId); await DefaultRequestDelegate(context);
}); });
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
@ -162,28 +111,10 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact] [Fact]
public async void ServesCachedContent_IfVaryByParamsExplicit_Matches_ParamNameCaseInsensitive() public async void ServesCachedContent_IfVaryByParamsExplicit_Matches_ParamNameCaseInsensitive()
{ {
var builder = CreateBuilderWithResponseCaching( var builder = CreateBuilderWithResponseCaching(async (context) =>
app =>
{ {
app.Use(async (context, next) =>
{
context.Features.Set<IHttpSendFileFeature>(new DummySendFileFeature());
await next.Invoke();
});
},
async (context) =>
{
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
context.GetResponseCachingFeature().VaryByParams = new[] { "ParamA", "paramb" }; context.GetResponseCachingFeature().VaryByParams = new[] { "ParamA", "paramb" };
await context.Response.WriteAsync(uniqueId); await DefaultRequestDelegate(context);
}); });
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
@ -199,28 +130,10 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact] [Fact]
public async void ServesCachedContent_IfVaryByParamsStar_Matches_ParamNameCaseInsensitive() public async void ServesCachedContent_IfVaryByParamsStar_Matches_ParamNameCaseInsensitive()
{ {
var builder = CreateBuilderWithResponseCaching( var builder = CreateBuilderWithResponseCaching(async (context) =>
app =>
{ {
app.Use(async (context, next) =>
{
context.Features.Set<IHttpSendFileFeature>(new DummySendFileFeature());
await next.Invoke();
});
},
async (context) =>
{
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
context.GetResponseCachingFeature().VaryByParams = new[] { "*" }; context.GetResponseCachingFeature().VaryByParams = new[] { "*" };
await context.Response.WriteAsync(uniqueId); await DefaultRequestDelegate(context);
}); });
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
@ -238,17 +151,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching(async (context) =>
{ {
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
context.GetResponseCachingFeature().VaryByParams = new[] { "ParamB", "ParamA" }; context.GetResponseCachingFeature().VaryByParams = new[] { "ParamB", "ParamA" };
await context.Response.WriteAsync(uniqueId); await DefaultRequestDelegate(context);
}); });
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
@ -266,17 +170,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching(async (context) =>
{ {
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
context.GetResponseCachingFeature().VaryByParams = new[] { "*" }; context.GetResponseCachingFeature().VaryByParams = new[] { "*" };
await context.Response.WriteAsync(uniqueId); await DefaultRequestDelegate(context);
}); });
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
@ -294,17 +189,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching(async (context) =>
{ {
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
context.GetResponseCachingFeature().VaryByParams = "param"; context.GetResponseCachingFeature().VaryByParams = "param";
await context.Response.WriteAsync(uniqueId); await DefaultRequestDelegate(context);
}); });
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
@ -322,17 +208,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching(async (context) =>
{ {
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
context.GetResponseCachingFeature().VaryByParams = new[] { "ParamA", "ParamB" }; context.GetResponseCachingFeature().VaryByParams = new[] { "ParamA", "ParamB" };
await context.Response.WriteAsync(uniqueId); await DefaultRequestDelegate(context);
}); });
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
@ -350,17 +227,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching(async (context) =>
{ {
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
context.GetResponseCachingFeature().VaryByParams = new[] { "*" }; context.GetResponseCachingFeature().VaryByParams = new[] { "*" };
await context.Response.WriteAsync(uniqueId); await DefaultRequestDelegate(context);
}); });
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
@ -376,19 +244,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact] [Fact]
public async void ServesFreshContent_IfRequestRequirements_NotMet() public async void ServesFreshContent_IfRequestRequirements_NotMet()
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching();
{
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
await context.Response.WriteAsync(uniqueId);
});
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
{ {
@ -407,19 +263,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact] [Fact]
public async void Serves504_IfOnlyIfCachedHeader_IsSpecified() public async void Serves504_IfOnlyIfCachedHeader_IsSpecified()
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching();
{
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
await context.Response.WriteAsync(uniqueId);
});
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
{ {
@ -441,17 +285,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var builder = CreateBuilderWithResponseCaching(async (context) =>
{ {
var uniqueId = Guid.NewGuid().ToString(); var headers = context.Response.Headers[HeaderNames.SetCookie] = "cookieName=cookieValue";
var headers = context.Response.GetTypedHeaders(); await DefaultRequestDelegate(context);
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
headers.Headers[HeaderNames.SetCookie] = "cookieName=cookieValue";
await context.Response.WriteAsync(uniqueId);
}); });
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
@ -480,28 +315,14 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact] [Fact]
public async void ServesCachedContent_IfIHttpSendFileFeature_NotUsed() public async void ServesCachedContent_IfIHttpSendFileFeature_NotUsed()
{ {
var builder = CreateBuilderWithResponseCaching( var builder = CreateBuilderWithResponseCaching(app =>
app => {
app.Use(async (context, next) =>
{ {
app.Use(async (context, next) => context.Features.Set<IHttpSendFileFeature>(new DummySendFileFeature());
{ await next.Invoke();
context.Features.Set<IHttpSendFileFeature>(new DummySendFileFeature());
await next.Invoke();
});
},
async (context) =>
{
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
await context.Response.WriteAsync(uniqueId);
}); });
});
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
{ {
@ -527,17 +348,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
}, },
async (context) => async (context) =>
{ {
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
await context.Features.Get<IHttpSendFileFeature>().SendFileAsync("dummy", 0, 0, CancellationToken.None); await context.Features.Get<IHttpSendFileFeature>().SendFileAsync("dummy", 0, 0, CancellationToken.None);
await context.Response.WriteAsync(uniqueId); await DefaultRequestDelegate(context);
}); });
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
@ -553,20 +365,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact] [Fact]
public async void ServesCachedContent_IfSubsequentRequest_ContainsNoStore() public async void ServesCachedContent_IfSubsequentRequest_ContainsNoStore()
{ {
var builder = CreateBuilderWithResponseCaching( var builder = CreateBuilderWithResponseCaching();
async (context) =>
{
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
await context.Response.WriteAsync(uniqueId);
});
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
{ {
@ -585,20 +384,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
[Fact] [Fact]
public async void ServesFreshContent_IfInitialRequestContains_NoStore() public async void ServesFreshContent_IfInitialRequestContains_NoStore()
{ {
var builder = CreateBuilderWithResponseCaching( var builder = CreateBuilderWithResponseCaching();
async (context) =>
{
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
await context.Response.WriteAsync(uniqueId);
});
using (var server = new TestServer(builder)) using (var server = new TestServer(builder))
{ {
@ -614,6 +400,116 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
} }
} }
[Fact]
public async void Serves304_IfIfModifiedSince_Satisfied()
{
var builder = CreateBuilderWithResponseCaching();
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var initialResponse = await client.GetAsync("");
client.DefaultRequestHeaders.IfUnmodifiedSince = DateTimeOffset.MaxValue;
var subsequentResponse = await client.GetAsync("");
initialResponse.EnsureSuccessStatusCode();
Assert.Equal(System.Net.HttpStatusCode.NotModified, subsequentResponse.StatusCode);
}
}
[Fact]
public async void ServesCachedContent_IfIfModifiedSince_NotSatisfied()
{
var builder = CreateBuilderWithResponseCaching();
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var initialResponse = await client.GetAsync("");
client.DefaultRequestHeaders.IfUnmodifiedSince = DateTimeOffset.MinValue;
var subsequentResponse = await client.GetAsync("");
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
}
}
[Fact]
public async void Serves304_IfIfNoneMatch_Satisfied()
{
var builder = CreateBuilderWithResponseCaching(async (context) =>
{
var headers = context.Response.GetTypedHeaders().ETag = new EntityTagHeaderValue("\"E1\"");
await DefaultRequestDelegate(context);
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var initialResponse = await client.GetAsync("");
client.DefaultRequestHeaders.IfNoneMatch.Add(new System.Net.Http.Headers.EntityTagHeaderValue("\"E1\""));
var subsequentResponse = await client.GetAsync("");
initialResponse.EnsureSuccessStatusCode();
Assert.Equal(System.Net.HttpStatusCode.NotModified, subsequentResponse.StatusCode);
}
}
[Fact]
public async void ServesCachedContent_IfIfNoneMatch_NotSatisfied()
{
var builder = CreateBuilderWithResponseCaching(async (context) =>
{
var headers = context.Response.GetTypedHeaders().ETag = new EntityTagHeaderValue("\"E1\"");
await DefaultRequestDelegate(context);
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var initialResponse = await client.GetAsync("");
client.DefaultRequestHeaders.IfNoneMatch.Add(new System.Net.Http.Headers.EntityTagHeaderValue("\"E2\""));
var subsequentResponse = await client.GetAsync("");
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
}
}
[Fact]
public async void ServesCachedContent_IfBodySize_IsCacheable()
{
var builder = CreateBuilderWithResponseCaching(new ResponseCachingOptions()
{
MaximumCachedBodySize = 100
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var initialResponse = await client.GetAsync("");
var subsequentResponse = await client.GetAsync("");
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
}
}
[Fact]
public async void ServesFreshContent_IfBodySize_IsNotCacheable()
{
var builder = CreateBuilderWithResponseCaching(new ResponseCachingOptions()
{
MaximumCachedBodySize = 1
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var initialResponse = await client.GetAsync("");
var subsequentResponse = await client.GetAsync("/different");
await AssertResponseNotCachedAsync(initialResponse, subsequentResponse);
}
}
private static async Task AssertResponseCachedAsync(HttpResponseMessage initialResponse, HttpResponseMessage subsequentResponse) private static async Task AssertResponseCachedAsync(HttpResponseMessage initialResponse, HttpResponseMessage subsequentResponse)
{ {
initialResponse.EnsureSuccessStatusCode(); initialResponse.EnsureSuccessStatusCode();
@ -636,126 +532,36 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
Assert.NotEqual(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync()); Assert.NotEqual(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
} }
[Fact] private static RequestDelegate DefaultRequestDelegate = async (context) =>
public async void Serves304_IfIfModifiedSince_Satisfied()
{ {
var builder = CreateBuilderWithResponseCaching(async (context) => var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{ {
var uniqueId = Guid.NewGuid().ToString(); Public = true,
var headers = context.Response.GetTypedHeaders(); MaxAge = TimeSpan.FromSeconds(10)
headers.CacheControl = new CacheControlHeaderValue() };
{ headers.Date = DateTimeOffset.UtcNow;
Public = true, headers.Headers["X-Value"] = uniqueId;
MaxAge = TimeSpan.FromSeconds(10) await context.Response.WriteAsync(uniqueId);
}; };
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
await context.Response.WriteAsync(uniqueId);
});
using (var server = new TestServer(builder)) private static IWebHostBuilder CreateBuilderWithResponseCaching() =>
{ CreateBuilderWithResponseCaching(app => { }, new ResponseCachingOptions(), DefaultRequestDelegate);
var client = server.CreateClient();
var initialResponse = await client.GetAsync("");
client.DefaultRequestHeaders.IfUnmodifiedSince = DateTimeOffset.MaxValue;
var subsequentResponse = await client.GetAsync("");
initialResponse.EnsureSuccessStatusCode(); private static IWebHostBuilder CreateBuilderWithResponseCaching(ResponseCachingOptions options) =>
Assert.Equal(System.Net.HttpStatusCode.NotModified, subsequentResponse.StatusCode); CreateBuilderWithResponseCaching(app => { }, options, DefaultRequestDelegate);
}
}
[Fact]
public async void ServesCachedContent_IfIfModifiedSince_NotSatisfied()
{
var builder = CreateBuilderWithResponseCaching(async (context) =>
{
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
await context.Response.WriteAsync(uniqueId);
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var initialResponse = await client.GetAsync("");
client.DefaultRequestHeaders.IfUnmodifiedSince = DateTimeOffset.MinValue;
var subsequentResponse = await client.GetAsync("");
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
}
}
[Fact]
public async void Serves304_IfIfNoneMatch_Satisfied()
{
var builder = CreateBuilderWithResponseCaching(async (context) =>
{
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
headers.ETag = new EntityTagHeaderValue("\"E1\"");
await context.Response.WriteAsync(uniqueId);
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var initialResponse = await client.GetAsync("");
client.DefaultRequestHeaders.IfNoneMatch.Add(new System.Net.Http.Headers.EntityTagHeaderValue("\"E1\""));
var subsequentResponse = await client.GetAsync("");
initialResponse.EnsureSuccessStatusCode();
Assert.Equal(System.Net.HttpStatusCode.NotModified, subsequentResponse.StatusCode);
}
}
[Fact]
public async void ServesCachedContent_IfIfNoneMatch_NotSatisfied()
{
var builder = CreateBuilderWithResponseCaching(async (context) =>
{
var uniqueId = Guid.NewGuid().ToString();
var headers = context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
headers.Date = DateTimeOffset.UtcNow;
headers.Headers["X-Value"] = uniqueId;
headers.ETag = new EntityTagHeaderValue("\"E1\"");
await context.Response.WriteAsync(uniqueId);
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var initialResponse = await client.GetAsync("");
client.DefaultRequestHeaders.IfNoneMatch.Add(new System.Net.Http.Headers.EntityTagHeaderValue("\"E2\""));
var subsequentResponse = await client.GetAsync("");
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
}
}
private static IWebHostBuilder CreateBuilderWithResponseCaching(RequestDelegate requestDelegate) => private static IWebHostBuilder CreateBuilderWithResponseCaching(RequestDelegate requestDelegate) =>
CreateBuilderWithResponseCaching(app => { }, requestDelegate); CreateBuilderWithResponseCaching(app => { }, new ResponseCachingOptions(), requestDelegate);
private static IWebHostBuilder CreateBuilderWithResponseCaching(Action<IApplicationBuilder> configureDelegate, RequestDelegate requestDelegate) private static IWebHostBuilder CreateBuilderWithResponseCaching(Action<IApplicationBuilder> configureDelegate) =>
CreateBuilderWithResponseCaching(configureDelegate, new ResponseCachingOptions(), DefaultRequestDelegate);
private static IWebHostBuilder CreateBuilderWithResponseCaching(Action<IApplicationBuilder> configureDelegate, RequestDelegate requestDelegate) =>
CreateBuilderWithResponseCaching(configureDelegate, new ResponseCachingOptions(), requestDelegate);
private static IWebHostBuilder CreateBuilderWithResponseCaching(Action<IApplicationBuilder> configureDelegate, ResponseCachingOptions options, RequestDelegate requestDelegate)
{ {
return new WebHostBuilder() return new WebHostBuilder()
.ConfigureServices(services => .ConfigureServices(services =>
@ -765,7 +571,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
.Configure(app => .Configure(app =>
{ {
configureDelegate(app); configureDelegate(app);
app.UseResponseCaching(); app.UseResponseCaching(options);
app.Run(requestDelegate); app.Run(requestDelegate);
}); });
} }