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

Commit cbb9833

Browse files
committed
Use private instance of MemoryCache and impose size limit
1 parent cad0e9e commit cbb9833

File tree

7 files changed

+143
-20
lines changed

7 files changed

+143
-20
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public interface IResponseCache
1111
IResponseCacheEntry Get(string key);
1212
Task<IResponseCacheEntry> GetAsync(string key);
1313

14-
void Set(string key, IResponseCacheEntry entry, TimeSpan validFor);
15-
Task SetAsync(string key, IResponseCacheEntry entry, TimeSpan validFor);
14+
void Set(string key, IResponseCacheEntry entry, TimeSpan validFor, long size);
15+
Task SetAsync(string key, IResponseCacheEntry entry, TimeSpan validFor, long size);
1616
}
1717
}

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public Task<IResponseCacheEntry> GetAsync(string key)
4747
return Task.FromResult(Get(key));
4848
}
4949

50-
public void Set(string key, IResponseCacheEntry entry, TimeSpan validFor)
50+
public void Set(string key, IResponseCacheEntry entry, TimeSpan validFor, long size)
5151
{
5252
var cachedResponse = entry as CachedResponse;
5353
if (cachedResponse != null)
@@ -67,7 +67,8 @@ public void Set(string key, IResponseCacheEntry entry, TimeSpan validFor)
6767
},
6868
new MemoryCacheEntryOptions
6969
{
70-
AbsoluteExpirationRelativeToNow = validFor
70+
AbsoluteExpirationRelativeToNow = validFor,
71+
Size = size
7172
});
7273
}
7374
else
@@ -77,14 +78,15 @@ public void Set(string key, IResponseCacheEntry entry, TimeSpan validFor)
7778
entry,
7879
new MemoryCacheEntryOptions
7980
{
80-
AbsoluteExpirationRelativeToNow = validFor
81+
AbsoluteExpirationRelativeToNow = validFor,
82+
Size = size
8183
});
8284
}
8385
}
8486

85-
public Task SetAsync(string key, IResponseCacheEntry entry, TimeSpan validFor)
87+
public Task SetAsync(string key, IResponseCacheEntry entry, TimeSpan validFor, long size)
8688
{
87-
Set(key, entry, validFor);
89+
Set(key, entry, validFor, size);
8890
return Task.CompletedTask;
8991
}
9092
}

src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingMiddleware.cs

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.AspNetCore.Http;
88
using Microsoft.AspNetCore.Http.Features;
99
using Microsoft.AspNetCore.ResponseCaching.Internal;
10+
using Microsoft.Extensions.Caching.Memory;
1011
using Microsoft.Extensions.Logging;
1112
using Microsoft.Extensions.Options;
1213
using Microsoft.Extensions.Primitives;
@@ -26,6 +27,24 @@ public class ResponseCachingMiddleware
2627
private readonly IResponseCachingKeyProvider _keyProvider;
2728

2829
public ResponseCachingMiddleware(
30+
RequestDelegate next,
31+
IOptions<ResponseCachingOptions> options,
32+
ILoggerFactory loggerFactory,
33+
IResponseCachingPolicyProvider policyProvider,
34+
IResponseCachingKeyProvider keyProvider)
35+
: this(
36+
next,
37+
options,
38+
loggerFactory,
39+
policyProvider,
40+
new MemoryResponseCache(new MemoryCache(new MemoryCacheOptions
41+
{
42+
SizeLimit = options.Value.SizeLimit
43+
})), keyProvider)
44+
{ }
45+
46+
// for testing
47+
internal ResponseCachingMiddleware(
2948
RequestDelegate next,
3049
IOptions<ResponseCachingOptions> options,
3150
ILoggerFactory loggerFactory,
@@ -303,15 +322,31 @@ internal void FinalizeCacheHeaders(ResponseCachingContext context)
303322
{
304323
if (OnFinalizeCacheHeaders(context))
305324
{
306-
_cache.Set(context.BaseKey, context.CachedVaryByRules, context.CachedResponseValidFor);
325+
try
326+
{
327+
_cache.Set(
328+
context.BaseKey,
329+
context.CachedVaryByRules,
330+
context.CachedResponseValidFor,
331+
EstimateCachedVaryByRulesySize(context.CachedVaryByRules));
332+
}
333+
catch (OverflowException) { }
307334
}
308335
}
309336

310337
internal Task FinalizeCacheHeadersAsync(ResponseCachingContext context)
311338
{
312339
if (OnFinalizeCacheHeaders(context))
313340
{
314-
return _cache.SetAsync(context.BaseKey, context.CachedVaryByRules, context.CachedResponseValidFor);
341+
try
342+
{
343+
return _cache.SetAsync(
344+
context.BaseKey,
345+
context.CachedVaryByRules,
346+
context.CachedResponseValidFor,
347+
EstimateCachedVaryByRulesySize(context.CachedVaryByRules));
348+
}
349+
catch (OverflowException) { }
315350
}
316351
return Task.CompletedTask;
317352
}
@@ -333,7 +368,16 @@ internal async Task FinalizeCacheBodyAsync(ResponseCachingContext context)
333368

334369
context.CachedResponse.Body = bufferStream;
335370
_logger.LogResponseCached();
336-
await _cache.SetAsync(context.StorageVaryKey ?? context.BaseKey, context.CachedResponse, context.CachedResponseValidFor);
371+
372+
try
373+
{
374+
await _cache.SetAsync(
375+
context.StorageVaryKey ?? context.BaseKey,
376+
context.CachedResponse,
377+
context.CachedResponseValidFor,
378+
EstimateCachedResponseSize(context.CachedResponse));
379+
}
380+
catch (OverflowException) { }
337381
}
338382
else
339383
{
@@ -505,5 +549,72 @@ internal static StringValues GetOrderCasingNormalizedStringValues(StringValues s
505549
return new StringValues(newArray);
506550
}
507551
}
552+
553+
internal static long EstimateCachedResponseSize(CachedResponse cachedResponse)
554+
{
555+
checked
556+
{
557+
// StatusCode
558+
long size = sizeof(int);
559+
560+
// Headers
561+
if (cachedResponse.Headers != null)
562+
{
563+
foreach (var item in cachedResponse.Headers)
564+
{
565+
size += item.Key.Length * sizeof(char) + EstimateStringValuesSize(item.Value);
566+
}
567+
}
568+
569+
// Body
570+
if (cachedResponse.Body != null)
571+
{
572+
size += cachedResponse.Body.Length * sizeof(char);
573+
}
574+
575+
return size;
576+
}
577+
}
578+
579+
internal static long EstimateCachedVaryByRulesySize(CachedVaryByRules cachedVaryByRules)
580+
{
581+
checked
582+
{
583+
var size = 0L;
584+
585+
// VaryByKeyPrefix
586+
if (!string.IsNullOrEmpty(cachedVaryByRules.VaryByKeyPrefix))
587+
{
588+
size = cachedVaryByRules.VaryByKeyPrefix.Length * sizeof(char);
589+
}
590+
591+
// Headers
592+
size += EstimateStringValuesSize(cachedVaryByRules.Headers);
593+
594+
// QueryKeys
595+
size += EstimateStringValuesSize(cachedVaryByRules.QueryKeys);
596+
597+
return size;
598+
}
599+
}
600+
601+
internal static long EstimateStringValuesSize(StringValues stringValues)
602+
{
603+
checked
604+
{
605+
var size = 0L;
606+
607+
for (var i = 0; i < stringValues.Count; i++)
608+
{
609+
var stringValue = stringValues[i];
610+
if (!string.IsNullOrEmpty(stringValue))
611+
{
612+
size += stringValues[i].Length * sizeof(char);
613+
}
614+
}
615+
616+
return size;
617+
}
618+
}
508619
}
509620
}

src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ namespace Microsoft.AspNetCore.ResponseCaching
88
{
99
public class ResponseCachingOptions
1010
{
11+
/// <summary>
12+
/// The size limit for the response cache middleware in bytes. The default is set to 100 MB.
13+
/// </summary>
14+
public long SizeLimit { get; set; } = 100 * 1024 * 1024;
15+
1116
/// <summary>
1217
/// The largest cacheable size for the response body in bytes. The default is set to 64 MB.
1318
/// </summary>

src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingServicesExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ public static IServiceCollection AddResponseCaching(this IServiceCollection serv
2828
services.AddMemoryCache();
2929
services.TryAdd(ServiceDescriptor.Singleton<IResponseCachingPolicyProvider, ResponseCachingPolicyProvider>());
3030
services.TryAdd(ServiceDescriptor.Singleton<IResponseCachingKeyProvider, ResponseCachingKeyProvider>());
31-
services.TryAdd(ServiceDescriptor.Singleton<IResponseCache, MemoryResponseCache>());
3231

3332
return services;
3433
}

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ await cache.SetAsync(
6565
Headers = new HeaderDictionary(),
6666
Body = new SegmentReadStream(new List<byte[]>(0), 0)
6767
},
68-
TimeSpan.Zero);
68+
TimeSpan.Zero,
69+
0);
6970

7071
Assert.True(await middleware.TryServeFromCacheAsync(context));
7172
Assert.Equal(1, cache.GetCount);
@@ -93,7 +94,8 @@ await cache.SetAsync(
9394
},
9495
Body = new SegmentReadStream(new List<byte[]>(0), 0)
9596
},
96-
TimeSpan.Zero);
97+
TimeSpan.Zero,
98+
0);
9799

98100
Assert.True(await middleware.TryServeFromCacheAsync(context));
99101
Assert.Equal("NewValue", context.HttpContext.Response.Headers["MyHeader"]);
@@ -114,7 +116,8 @@ public async Task TryServeFromCacheAsync_VaryByRuleFound_CachedResponseNotFound_
114116
await cache.SetAsync(
115117
"BaseKey",
116118
new CachedVaryByRules(),
117-
TimeSpan.Zero);
119+
TimeSpan.Zero,
120+
0);
118121

119122
Assert.False(await middleware.TryServeFromCacheAsync(context));
120123
Assert.Equal(2, cache.GetCount);
@@ -134,15 +137,17 @@ public async Task TryServeFromCacheAsync_VaryByRuleFound_CachedResponseFound_Suc
134137
await cache.SetAsync(
135138
"BaseKey",
136139
new CachedVaryByRules(),
137-
TimeSpan.Zero);
140+
TimeSpan.Zero,
141+
0);
138142
await cache.SetAsync(
139143
"BaseKeyVaryKey2",
140144
new CachedResponse()
141145
{
142146
Headers = new HeaderDictionary(),
143147
Body = new SegmentReadStream(new List<byte[]>(0), 0)
144148
},
145-
TimeSpan.Zero);
149+
TimeSpan.Zero,
150+
0);
146151

147152
Assert.True(await middleware.TryServeFromCacheAsync(context));
148153
Assert.Equal(3, cache.GetCount);
@@ -166,7 +171,8 @@ await cache.SetAsync(
166171
{
167172
Body = new SegmentReadStream(new List<byte[]>(0), 0)
168173
},
169-
TimeSpan.Zero);
174+
TimeSpan.Zero,
175+
0);
170176

171177
Assert.True(await middleware.TryServeFromCacheAsync(context));
172178
Assert.Equal(1, cache.GetCount);

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -373,15 +373,15 @@ public Task<IResponseCacheEntry> GetAsync(string key)
373373
return Task.FromResult(Get(key));
374374
}
375375

376-
public void Set(string key, IResponseCacheEntry entry, TimeSpan validFor)
376+
public void Set(string key, IResponseCacheEntry entry, TimeSpan validFor, long size)
377377
{
378378
SetCount++;
379379
_storage[key] = entry;
380380
}
381381

382-
public Task SetAsync(string key, IResponseCacheEntry entry, TimeSpan validFor)
382+
public Task SetAsync(string key, IResponseCacheEntry entry, TimeSpan validFor, long size)
383383
{
384-
Set(key, entry, validFor);
384+
Set(key, entry, validFor, size);
385385
return Task.CompletedTask;
386386
}
387387
}

0 commit comments

Comments
 (0)