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

Commit 97467e1

Browse files
committed
Adding options to specify maximum response body size
1 parent 52f219b commit 97467e1

File tree

6 files changed

+163
-46
lines changed

6 files changed

+163
-46
lines changed

src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingContext.cs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Linq;
88
using System.Text;
99
using System.Threading.Tasks;
10+
using Microsoft.AspNetCore.Builder;
1011
using Microsoft.AspNetCore.Http;
1112
using Microsoft.AspNetCore.Http.Features;
1213
using Microsoft.AspNetCore.Http.Headers;
@@ -25,7 +26,7 @@ internal class ResponseCachingContext
2526

2627
private readonly HttpContext _httpContext;
2728
private readonly IResponseCache _cache;
28-
private readonly ISystemClock _clock;
29+
private readonly ResponseCachingOptions _options;
2930
private readonly ObjectPool<StringBuilder> _builderPool;
3031
private readonly IResponseCachingCacheabilityValidator _cacheabilityValidator;
3132
private readonly IResponseCachingCacheKeySuffixProvider _cacheKeySuffixProvider;
@@ -40,29 +41,19 @@ internal class ResponseCachingContext
4041
private CachedResponse _cachedResponse;
4142
private TimeSpan _cachedResponseValidFor;
4243
internal DateTimeOffset _responseTime;
43-
44-
internal ResponseCachingContext(
45-
HttpContext httpContext,
46-
IResponseCache cache,
47-
ObjectPool<StringBuilder> builderPool,
48-
IResponseCachingCacheabilityValidator cacheabilityValidator,
49-
IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider)
50-
: this(httpContext, cache, new SystemClock(), builderPool, cacheabilityValidator, cacheKeySuffixProvider)
51-
{
52-
}
5344

5445
// Internal for testing
5546
internal ResponseCachingContext(
5647
HttpContext httpContext,
5748
IResponseCache cache,
58-
ISystemClock clock,
49+
ResponseCachingOptions options,
5950
ObjectPool<StringBuilder> builderPool,
6051
IResponseCachingCacheabilityValidator cacheabilityValidator,
6152
IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider)
6253
{
6354
_httpContext = httpContext;
6455
_cache = cache;
65-
_clock = clock;
56+
_options = options;
6657
_builderPool = builderPool;
6758
_cacheabilityValidator = cacheabilityValidator;
6859
_cacheKeySuffixProvider = cacheKeySuffixProvider;
@@ -74,10 +65,7 @@ internal bool CacheResponse
7465
{
7566
if (_cacheResponse == null)
7667
{
77-
// TODO: apparent age vs corrected age value
78-
var responseAge = _responseTime - ResponseHeaders.Date ?? TimeSpan.Zero;
79-
80-
_cacheResponse = ResponseIsCacheable() && EntryIsFresh(ResponseHeaders, responseAge, verifyAgainstRequest: false);
68+
_cacheResponse = ResponseIsCacheable();
8169
}
8270
return _cacheResponse.Value;
8371
}
@@ -367,6 +355,14 @@ internal bool ResponseIsCacheable()
367355
return false;
368356
}
369357

358+
// Check response freshness
359+
// TODO: apparent age vs corrected age value
360+
var responseAge = _responseTime - ResponseHeaders.Date ?? TimeSpan.Zero;
361+
if (!EntryIsFresh(ResponseHeaders, responseAge, verifyAgainstRequest: false))
362+
{
363+
return false;
364+
}
365+
370366
return true;
371367
}
372368

@@ -437,7 +433,7 @@ internal async Task<bool> TryServeFromCacheAsync()
437433
var cachedResponse = cacheEntry as CachedResponse;
438434
var cachedResponseHeaders = new ResponseHeaders(cachedResponse.Headers);
439435

440-
_responseTime = _clock.UtcNow;
436+
_responseTime = _options.SystemClock.UtcNow;
441437
var age = _responseTime - cachedResponse.Created;
442438
age = age > TimeSpan.Zero ? age : TimeSpan.Zero;
443439

@@ -602,6 +598,12 @@ internal void FinalizeCachingBody()
602598
{
603599
_cachedResponse.Body = ResponseCacheStream.BufferedStream.ToArray();
604600

601+
// Check if the body is too large to be cached
602+
if (_options.MaximumCachedBodySize < _cachedResponse.Body.Length)
603+
{
604+
return;
605+
}
606+
605607
_cache.Set(_cacheKey, _cachedResponse, _cachedResponseValidFor);
606608
}
607609
}
@@ -611,7 +613,7 @@ internal void OnResponseStarting()
611613
if (!ResponseStarted)
612614
{
613615
ResponseStarted = true;
614-
_responseTime = _clock.UtcNow;
616+
_responseTime = _options.SystemClock.UtcNow;
615617

616618
FinalizeCachingHeaders();
617619
}

src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using Microsoft.AspNetCore.ResponseCaching;
6+
using Microsoft.Extensions.Options;
67

78
namespace Microsoft.AspNetCore.Builder
89
{
@@ -17,5 +18,19 @@ public static IApplicationBuilder UseResponseCaching(this IApplicationBuilder ap
1718

1819
return app.UseMiddleware<ResponseCachingMiddleware>();
1920
}
21+
22+
public static IApplicationBuilder UseResponseCaching(this IApplicationBuilder app, ResponseCachingOptions options)
23+
{
24+
if (app == null)
25+
{
26+
throw new ArgumentNullException(nameof(app));
27+
}
28+
if (options == null)
29+
{
30+
throw new ArgumentNullException(nameof(options));
31+
}
32+
33+
return app.UseMiddleware<ResponseCachingMiddleware>(Options.Create(options));
34+
}
2035
}
2136
}

src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingMiddleware.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Text;
66
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Builder;
78
using Microsoft.AspNetCore.Http;
89
using Microsoft.Extensions.ObjectPool;
910
using Microsoft.Extensions.Options;
@@ -20,13 +21,15 @@ public class ResponseCachingMiddleware
2021

2122
private readonly RequestDelegate _next;
2223
private readonly IResponseCache _cache;
24+
private readonly ResponseCachingOptions _options;
2325
private readonly ObjectPool<StringBuilder> _builderPool;
2426
private readonly IResponseCachingCacheabilityValidator _cacheabilityValidator;
2527
private readonly IResponseCachingCacheKeySuffixProvider _cacheKeySuffixProvider;
2628

2729
public ResponseCachingMiddleware(
28-
RequestDelegate next,
30+
RequestDelegate next,
2931
IResponseCache cache,
32+
IOptions<ResponseCachingOptions> options,
3033
ObjectPoolProvider poolProvider,
3134
IResponseCachingCacheabilityValidator cacheabilityValidator,
3235
IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider)
@@ -39,6 +42,10 @@ public ResponseCachingMiddleware(
3942
{
4043
throw new ArgumentNullException(nameof(cache));
4144
}
45+
if (options == null)
46+
{
47+
throw new ArgumentNullException(nameof(options));
48+
}
4249
if (poolProvider == null)
4350
{
4451
throw new ArgumentNullException(nameof(poolProvider));
@@ -54,6 +61,7 @@ public ResponseCachingMiddleware(
5461

5562
_next = next;
5663
_cache = cache;
64+
_options = options.Value;
5765
_builderPool = poolProvider.CreateStringBuilderPool();
5866
_cacheabilityValidator = cacheabilityValidator;
5967
_cacheKeySuffixProvider = cacheKeySuffixProvider;
@@ -64,6 +72,7 @@ public async Task Invoke(HttpContext context)
6472
var cachingContext = new ResponseCachingContext(
6573
context,
6674
_cache,
75+
_options,
6776
_builderPool,
6877
_cacheabilityValidator,
6978
_cacheKeySuffixProvider);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.ComponentModel;
5+
using Microsoft.AspNetCore.ResponseCaching.Internal;
6+
7+
namespace Microsoft.AspNetCore.Builder
8+
{
9+
public class ResponseCachingOptions
10+
{
11+
/// <summary>
12+
/// The largest cacheable size for the response body in bytes.
13+
/// </summary>
14+
public int? MaximumCachedBodySize { get; set; }
15+
16+
/// <summary>
17+
/// For testing purposes only.
18+
/// </summary>
19+
[EditorBrowsable(EditorBrowsableState.Never)]
20+
internal ISystemClock SystemClock { get; set; } = new SystemClock();
21+
}
22+
}

test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCachingContextTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Text;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Builder;
910
using Microsoft.AspNetCore.Http;
1011
using Microsoft.AspNetCore.Http.Features;
1112
using Microsoft.AspNetCore.Http.Headers;
@@ -859,7 +860,7 @@ private static ResponseCachingContext CreateTestContext(
859860
return new ResponseCachingContext(
860861
httpContext,
861862
new TestResponseCache(),
862-
new SystemClock(),
863+
new ResponseCachingOptions(),
863864
new DefaultObjectPool<StringBuilder>(new StringBuilderPooledObjectPolicy()),
864865
cacheabilityValidator,
865866
cacheKeySuffixProvider);

test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCachingTests.cs

Lines changed: 93 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -614,28 +614,6 @@ public async void ServesFreshContent_IfInitialRequestContains_NoStore()
614614
}
615615
}
616616

617-
private static async Task AssertResponseCachedAsync(HttpResponseMessage initialResponse, HttpResponseMessage subsequentResponse)
618-
{
619-
initialResponse.EnsureSuccessStatusCode();
620-
subsequentResponse.EnsureSuccessStatusCode();
621-
622-
foreach (var header in initialResponse.Headers)
623-
{
624-
Assert.Equal(initialResponse.Headers.GetValues(header.Key), subsequentResponse.Headers.GetValues(header.Key));
625-
}
626-
Assert.True(subsequentResponse.Headers.Contains(HeaderNames.Age));
627-
Assert.Equal(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
628-
}
629-
630-
private static async Task AssertResponseNotCachedAsync(HttpResponseMessage initialResponse, HttpResponseMessage subsequentResponse)
631-
{
632-
initialResponse.EnsureSuccessStatusCode();
633-
subsequentResponse.EnsureSuccessStatusCode();
634-
635-
Assert.False(subsequentResponse.Headers.Contains(HeaderNames.Age));
636-
Assert.NotEqual(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
637-
}
638-
639617
[Fact]
640618
public async void Serves304_IfIfModifiedSince_Satisfied()
641619
{
@@ -752,10 +730,100 @@ public async void ServesCachedContent_IfIfNoneMatch_NotSatisfied()
752730
}
753731
}
754732

733+
[Fact]
734+
public async void ServesCachedContent_IfBodySize_IsCacheable()
735+
{
736+
var builder = CreateBuilderWithResponseCaching(new ResponseCachingOptions()
737+
{
738+
MaximumCachedBodySize = 100
739+
},
740+
async (context) =>
741+
{
742+
var uniqueId = Guid.NewGuid().ToString();
743+
var headers = context.Response.GetTypedHeaders();
744+
headers.CacheControl = new CacheControlHeaderValue()
745+
{
746+
Public = true,
747+
MaxAge = TimeSpan.FromSeconds(10)
748+
};
749+
headers.Date = DateTimeOffset.UtcNow;
750+
headers.Headers["X-Value"] = uniqueId;
751+
await context.Response.WriteAsync(uniqueId);
752+
});
753+
754+
using (var server = new TestServer(builder))
755+
{
756+
var client = server.CreateClient();
757+
var initialResponse = await client.GetAsync("");
758+
var subsequentResponse = await client.GetAsync("");
759+
760+
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
761+
}
762+
}
763+
764+
[Fact]
765+
public async void ServesFreshContent_IfBodySize_IsNotCacheable()
766+
{
767+
var builder = CreateBuilderWithResponseCaching(new ResponseCachingOptions()
768+
{
769+
MaximumCachedBodySize = 1
770+
},
771+
async (context) =>
772+
{
773+
var uniqueId = Guid.NewGuid().ToString();
774+
var headers = context.Response.GetTypedHeaders();
775+
headers.CacheControl = new CacheControlHeaderValue()
776+
{
777+
Public = true,
778+
MaxAge = TimeSpan.FromSeconds(10)
779+
};
780+
headers.Date = DateTimeOffset.UtcNow;
781+
headers.Headers["X-Value"] = uniqueId;
782+
await context.Response.WriteAsync(uniqueId);
783+
});
784+
785+
using (var server = new TestServer(builder))
786+
{
787+
var client = server.CreateClient();
788+
var initialResponse = await client.GetAsync("");
789+
var subsequentResponse = await client.GetAsync("/different");
790+
791+
await AssertResponseNotCachedAsync(initialResponse, subsequentResponse);
792+
}
793+
}
794+
795+
private static async Task AssertResponseCachedAsync(HttpResponseMessage initialResponse, HttpResponseMessage subsequentResponse)
796+
{
797+
initialResponse.EnsureSuccessStatusCode();
798+
subsequentResponse.EnsureSuccessStatusCode();
799+
800+
foreach (var header in initialResponse.Headers)
801+
{
802+
Assert.Equal(initialResponse.Headers.GetValues(header.Key), subsequentResponse.Headers.GetValues(header.Key));
803+
}
804+
Assert.True(subsequentResponse.Headers.Contains(HeaderNames.Age));
805+
Assert.Equal(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
806+
}
807+
808+
private static async Task AssertResponseNotCachedAsync(HttpResponseMessage initialResponse, HttpResponseMessage subsequentResponse)
809+
{
810+
initialResponse.EnsureSuccessStatusCode();
811+
subsequentResponse.EnsureSuccessStatusCode();
812+
813+
Assert.False(subsequentResponse.Headers.Contains(HeaderNames.Age));
814+
Assert.NotEqual(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
815+
}
816+
755817
private static IWebHostBuilder CreateBuilderWithResponseCaching(RequestDelegate requestDelegate) =>
756-
CreateBuilderWithResponseCaching(app => { }, requestDelegate);
818+
CreateBuilderWithResponseCaching(app => { }, new ResponseCachingOptions(), requestDelegate);
819+
820+
private static IWebHostBuilder CreateBuilderWithResponseCaching(Action<IApplicationBuilder> configureDelegate, RequestDelegate requestDelegate) =>
821+
CreateBuilderWithResponseCaching(configureDelegate, new ResponseCachingOptions(), requestDelegate);
822+
823+
private static IWebHostBuilder CreateBuilderWithResponseCaching(ResponseCachingOptions options, RequestDelegate requestDelegate) =>
824+
CreateBuilderWithResponseCaching(app => { }, options, requestDelegate);
757825

758-
private static IWebHostBuilder CreateBuilderWithResponseCaching(Action<IApplicationBuilder> configureDelegate, RequestDelegate requestDelegate)
826+
private static IWebHostBuilder CreateBuilderWithResponseCaching(Action<IApplicationBuilder> configureDelegate, ResponseCachingOptions options, RequestDelegate requestDelegate)
759827
{
760828
return new WebHostBuilder()
761829
.ConfigureServices(services =>
@@ -765,7 +833,7 @@ private static IWebHostBuilder CreateBuilderWithResponseCaching(Action<IApplicat
765833
.Configure(app =>
766834
{
767835
configureDelegate(app);
768-
app.UseResponseCaching();
836+
app.UseResponseCaching(options);
769837
app.Run(requestDelegate);
770838
});
771839
}

0 commit comments

Comments
 (0)