12
12
using Microsoft . AspNetCore . ResponseCaching . Internal ;
13
13
using Microsoft . Extensions . Primitives ;
14
14
using Microsoft . Net . Http . Headers ;
15
+ using Microsoft . Extensions . ObjectPool ;
15
16
16
17
namespace Microsoft . AspNetCore . ResponseCaching
17
18
{
18
19
public class ResponseCachingContext
19
20
{
20
21
private static readonly CacheControlHeaderValue EmptyCacheControl = new CacheControlHeaderValue ( ) ;
22
+
23
+ private readonly HttpContext _httpContext ;
24
+ private readonly IResponseCache _cache ;
25
+ private readonly ISystemClock _clock ;
26
+ private readonly ObjectPool < StringBuilder > _builderPool ;
27
+ private readonly IResponseCachingRequestCacheabilityValidator _requestCacheabilityValidator ;
28
+ private readonly IResponseCachingResponseCacheabilityValidator _responseCacheabilityValidator ;
29
+ private readonly IResponseCachingCacheKeySuffixProvider _cacheKeySuffixProvider ;
30
+
21
31
private string _cacheKey ;
22
32
private ResponseType ? _responseType ;
23
33
private RequestHeaders _requestHeaders ;
@@ -32,28 +42,31 @@ public class ResponseCachingContext
32
42
public ResponseCachingContext (
33
43
HttpContext httpContext ,
34
44
IResponseCache cache ,
45
+ ObjectPool < StringBuilder > builderPool ,
35
46
IResponseCachingRequestCacheabilityValidator requestCacheabilityValidator ,
36
47
IResponseCachingResponseCacheabilityValidator responseCacheabilityValidator ,
37
48
IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider )
38
- : this ( httpContext , cache , new SystemClock ( ) , requestCacheabilityValidator , responseCacheabilityValidator , cacheKeySuffixProvider )
49
+ : this ( httpContext , cache , new SystemClock ( ) , builderPool , requestCacheabilityValidator , responseCacheabilityValidator , cacheKeySuffixProvider )
39
50
{
40
51
}
41
52
42
53
// Internal for testing
43
54
internal ResponseCachingContext (
44
55
HttpContext httpContext ,
45
- IResponseCache cache ,
56
+ IResponseCache cache ,
46
57
ISystemClock clock ,
58
+ ObjectPool < StringBuilder > builderPool ,
47
59
IResponseCachingRequestCacheabilityValidator requestCacheabilityValidator ,
48
60
IResponseCachingResponseCacheabilityValidator responseCacheabilityValidator ,
49
61
IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider )
50
62
{
51
- HttpContext = httpContext ;
52
- Cache = cache ;
53
- Clock = clock ;
54
- RequestCacheabilityValidator = requestCacheabilityValidator ;
55
- ResponseCacheabilityValidator = responseCacheabilityValidator ;
56
- CacheKeySuffixProvider = cacheKeySuffixProvider ;
63
+ _httpContext = httpContext ;
64
+ _cache = cache ;
65
+ _clock = clock ;
66
+ _builderPool = builderPool ;
67
+ _requestCacheabilityValidator = requestCacheabilityValidator ;
68
+ _responseCacheabilityValidator = responseCacheabilityValidator ;
69
+ _cacheKeySuffixProvider = cacheKeySuffixProvider ;
57
70
}
58
71
59
72
internal bool CacheResponse
@@ -73,18 +86,6 @@ internal bool CacheResponse
73
86
74
87
internal bool ResponseStarted { get ; set ; }
75
88
76
- private HttpContext HttpContext { get ; }
77
-
78
- private IResponseCache Cache { get ; }
79
-
80
- private ISystemClock Clock { get ; }
81
-
82
- private IResponseCachingRequestCacheabilityValidator RequestCacheabilityValidator { get ; }
83
-
84
- private IResponseCachingResponseCacheabilityValidator ResponseCacheabilityValidator { get ; }
85
-
86
- private IResponseCachingCacheKeySuffixProvider CacheKeySuffixProvider { get ; }
87
-
88
89
private Stream OriginalResponseStream { get ; set ; }
89
90
90
91
private ResponseCacheStream ResponseCacheStream { get ; set ; }
@@ -97,7 +98,7 @@ private RequestHeaders RequestHeaders
97
98
{
98
99
if ( _requestHeaders == null )
99
100
{
100
- _requestHeaders = HttpContext . Request . GetTypedHeaders ( ) ;
101
+ _requestHeaders = _httpContext . Request . GetTypedHeaders ( ) ;
101
102
}
102
103
return _requestHeaders ;
103
104
}
@@ -109,7 +110,7 @@ private ResponseHeaders ResponseHeaders
109
110
{
110
111
if ( _responseHeaders == null )
111
112
{
112
- _responseHeaders = HttpContext . Response . GetTypedHeaders ( ) ;
113
+ _responseHeaders = _httpContext . Response . GetTypedHeaders ( ) ;
113
114
}
114
115
return _responseHeaders ;
115
116
}
@@ -148,49 +149,61 @@ internal string CreateCacheKey()
148
149
149
150
internal string CreateCacheKey ( CachedVaryBy varyBy )
150
151
{
151
- var request = HttpContext . Request ;
152
- var builder = new StringBuilder ( )
153
- . Append ( request . Method . ToUpperInvariant ( ) )
154
- . Append ( ";" )
155
- . Append ( request . Path . Value . ToUpperInvariant ( ) ) ;
152
+ var request = _httpContext . Request ;
153
+ var builder = _builderPool ? . Get ( ) ?? new StringBuilder ( ) ;
156
154
157
- if ( varyBy ? . Headers . Count > 0 )
155
+ try
158
156
{
159
- // TODO: resolve key format and delimiters
160
- foreach ( var header in varyBy . Headers )
161
- {
162
- // TODO: Normalization of order, case?
163
- var value = HttpContext . Request . Headers [ header ] ;
157
+ builder
158
+ . Append ( request . Method . ToUpperInvariant ( ) )
159
+ . Append ( ";" )
160
+ . Append ( request . Path . Value . ToUpperInvariant ( ) ) ;
164
161
165
- // TODO: How to handle null/empty string?
166
- if ( StringValues . IsNullOrEmpty ( value ) )
162
+ if ( varyBy ? . Headers . Count > 0 )
163
+ {
164
+ // TODO: resolve key format and delimiters
165
+ foreach ( var header in varyBy . Headers )
167
166
{
168
- value = "null" ;
167
+ // TODO: Normalization of order, case?
168
+ var value = _httpContext . Request . Headers [ header ] ;
169
+
170
+ // TODO: How to handle null/empty string?
171
+ if ( StringValues . IsNullOrEmpty ( value ) )
172
+ {
173
+ value = "null" ;
174
+ }
175
+
176
+ builder . Append ( ";" )
177
+ . Append ( header )
178
+ . Append ( "=" )
179
+ . Append ( value ) ;
169
180
}
181
+ }
182
+ // TODO: Parse querystring params
170
183
184
+ // Append custom cache key segment
185
+ var customKey = _cacheKeySuffixProvider . CreateCustomKeySuffix ( _httpContext ) ;
186
+ if ( ! string . IsNullOrEmpty ( customKey ) )
187
+ {
171
188
builder . Append ( ";" )
172
- . Append ( header )
173
- . Append ( "=" )
174
- . Append ( value ) ;
189
+ . Append ( customKey ) ;
175
190
}
176
- }
177
- // TODO: Parse querystring params
178
191
179
- // Append custom cache key segment
180
- var customKey = CacheKeySuffixProvider . CreateCustomKeySuffix ( HttpContext ) ;
181
- if ( ! string . IsNullOrEmpty ( customKey ) )
192
+ return builder . ToString ( ) ;
193
+ }
194
+ finally
182
195
{
183
- builder . Append ( ";" )
184
- . Append ( customKey ) ;
196
+ if ( _builderPool != null )
197
+ {
198
+ _builderPool . Return ( builder ) ;
199
+ }
185
200
}
186
-
187
- return builder . ToString ( ) ;
188
201
}
189
202
190
203
internal bool RequestIsCacheable ( )
191
204
{
192
205
// Use optional override if specified by user
193
- switch ( RequestCacheabilityValidator . RequestIsCacheableOverride ( HttpContext ) )
206
+ switch ( _requestCacheabilityValidator . RequestIsCacheableOverride ( _httpContext ) )
194
207
{
195
208
case OverrideResult . UseDefaultLogic :
196
209
break ;
@@ -199,12 +212,12 @@ internal bool RequestIsCacheable()
199
212
case OverrideResult . Cache :
200
213
return true ;
201
214
default :
202
- throw new NotSupportedException ( $ "Unrecognized result from { nameof ( RequestCacheabilityValidator . RequestIsCacheableOverride ) } .") ;
215
+ throw new NotSupportedException ( $ "Unrecognized result from { nameof ( _requestCacheabilityValidator . RequestIsCacheableOverride ) } .") ;
203
216
}
204
217
205
218
// Verify the method
206
219
// TODO: RFC lists POST as a cacheable method when explicit freshness information is provided, but this is not widely implemented. Will revisit.
207
- var request = HttpContext . Request ;
220
+ var request = _httpContext . Request ;
208
221
if ( string . Equals ( "GET" , request . Method , StringComparison . OrdinalIgnoreCase ) )
209
222
{
210
223
_responseType = ResponseType . FullReponse ;
@@ -254,7 +267,7 @@ internal bool RequestIsCacheable()
254
267
internal bool ResponseIsCacheable ( )
255
268
{
256
269
// Use optional override if specified by user
257
- switch ( ResponseCacheabilityValidator . ResponseIsCacheableOverride ( HttpContext ) )
270
+ switch ( _responseCacheabilityValidator . ResponseIsCacheableOverride ( _httpContext ) )
258
271
{
259
272
case OverrideResult . UseDefaultLogic :
260
273
break ;
@@ -263,7 +276,7 @@ internal bool ResponseIsCacheable()
263
276
case OverrideResult . Cache :
264
277
return true ;
265
278
default :
266
- throw new NotSupportedException ( $ "Unrecognized result from { nameof ( ResponseCacheabilityValidator . ResponseIsCacheableOverride ) } .") ;
279
+ throw new NotSupportedException ( $ "Unrecognized result from { nameof ( _responseCacheabilityValidator . ResponseIsCacheableOverride ) } .") ;
267
280
}
268
281
269
282
// Only cache pages explicitly marked with public
@@ -286,7 +299,7 @@ internal bool ResponseIsCacheable()
286
299
return false ;
287
300
}
288
301
289
- var response = HttpContext . Response ;
302
+ var response = _httpContext . Response ;
290
303
291
304
// Do not cache responses varying by *
292
305
if ( string . Equals ( response . Headers [ HeaderNames . Vary ] , "*" , StringComparison . OrdinalIgnoreCase ) )
@@ -364,28 +377,28 @@ internal bool EntryIsFresh(ResponseHeaders responseHeaders, TimeSpan age, bool v
364
377
internal async Task < bool > TryServeFromCacheAsync ( )
365
378
{
366
379
_cacheKey = CreateCacheKey ( ) ;
367
- var cacheEntry = Cache . Get ( _cacheKey ) ;
380
+ var cacheEntry = _cache . Get ( _cacheKey ) ;
368
381
var responseServed = false ;
369
382
370
383
if ( cacheEntry is CachedVaryBy )
371
384
{
372
385
// Request contains VaryBy rules, recompute key and try again
373
386
_cacheKey = CreateCacheKey ( cacheEntry as CachedVaryBy ) ;
374
- cacheEntry = Cache . Get ( _cacheKey ) ;
387
+ cacheEntry = _cache . Get ( _cacheKey ) ;
375
388
}
376
389
377
390
if ( cacheEntry is CachedResponse )
378
391
{
379
392
var cachedResponse = cacheEntry as CachedResponse ;
380
393
var cachedResponseHeaders = new ResponseHeaders ( cachedResponse . Headers ) ;
381
394
382
- _responseTime = Clock . UtcNow ;
395
+ _responseTime = _clock . UtcNow ;
383
396
var age = _responseTime - cachedResponse . Created ;
384
397
age = age > TimeSpan . Zero ? age : TimeSpan . Zero ;
385
398
386
399
if ( EntryIsFresh ( cachedResponseHeaders , age , verifyAgainstRequest : true ) )
387
400
{
388
- var response = HttpContext . Response ;
401
+ var response = _httpContext . Response ;
389
402
// Copy the cached status code and response headers
390
403
response . StatusCode = cachedResponse . StatusCode ;
391
404
foreach ( var header in cachedResponse . Headers )
@@ -430,7 +443,7 @@ internal async Task<bool> TryServeFromCacheAsync()
430
443
431
444
if ( ! responseServed && RequestCacheControl . OnlyIfCached )
432
445
{
433
- HttpContext . Response . StatusCode = StatusCodes . Status504GatewayTimeout ;
446
+ _httpContext . Response . StatusCode = StatusCodes . Status504GatewayTimeout ;
434
447
435
448
responseServed = true ;
436
449
}
@@ -443,7 +456,7 @@ internal void FinalizeCachingHeaders()
443
456
if ( CacheResponse )
444
457
{
445
458
// Create the cache entry now
446
- var response = HttpContext . Response ;
459
+ var response = _httpContext . Response ;
447
460
var varyHeaderValue = response . Headers [ HeaderNames . Vary ] ;
448
461
_cachedResponseValidFor = ResponseCacheControl . SharedMaxAge
449
462
?? ResponseCacheControl . MaxAge
@@ -462,7 +475,7 @@ internal void FinalizeCachingHeaders()
462
475
} ;
463
476
464
477
// TODO: Overwrite?
465
- Cache . Set ( _cacheKey , cachedVaryBy , _cachedResponseValidFor ) ;
478
+ _cache . Set ( _cacheKey , cachedVaryBy , _cachedResponseValidFor ) ;
466
479
_cacheKey = CreateCacheKey ( cachedVaryBy ) ;
467
480
}
468
481
@@ -476,7 +489,7 @@ internal void FinalizeCachingHeaders()
476
489
_cachedResponse = new CachedResponse
477
490
{
478
491
Created = ResponseHeaders . Date . Value ,
479
- StatusCode = HttpContext . Response . StatusCode
492
+ StatusCode = _httpContext . Response . StatusCode
480
493
} ;
481
494
482
495
foreach ( var header in ResponseHeaders . Headers )
@@ -500,7 +513,7 @@ internal void FinalizeCachingBody()
500
513
{
501
514
_cachedResponse . Body = ResponseCacheStream . BufferedStream . ToArray ( ) ;
502
515
503
- Cache . Set ( _cacheKey , _cachedResponse , _cachedResponseValidFor ) ;
516
+ _cache . Set ( _cacheKey , _cachedResponse , _cachedResponseValidFor ) ;
504
517
}
505
518
}
506
519
@@ -509,7 +522,7 @@ internal void OnResponseStarting()
509
522
if ( ! ResponseStarted )
510
523
{
511
524
ResponseStarted = true ;
512
- _responseTime = Clock . UtcNow ;
525
+ _responseTime = _clock . UtcNow ;
513
526
514
527
FinalizeCachingHeaders ( ) ;
515
528
}
@@ -520,25 +533,25 @@ internal void ShimResponseStream()
520
533
// TODO: Consider caching large responses on disk and serving them from there.
521
534
522
535
// Shim response stream
523
- OriginalResponseStream = HttpContext . Response . Body ;
536
+ OriginalResponseStream = _httpContext . Response . Body ;
524
537
ResponseCacheStream = new ResponseCacheStream ( OriginalResponseStream ) ;
525
- HttpContext . Response . Body = ResponseCacheStream ;
538
+ _httpContext . Response . Body = ResponseCacheStream ;
526
539
527
540
// Shim IHttpSendFileFeature
528
- OriginalSendFileFeature = HttpContext . Features . Get < IHttpSendFileFeature > ( ) ;
541
+ OriginalSendFileFeature = _httpContext . Features . Get < IHttpSendFileFeature > ( ) ;
529
542
if ( OriginalSendFileFeature != null )
530
543
{
531
- HttpContext . Features . Set < IHttpSendFileFeature > ( new SendFileFeatureWrapper ( OriginalSendFileFeature , ResponseCacheStream ) ) ;
544
+ _httpContext . Features . Set < IHttpSendFileFeature > ( new SendFileFeatureWrapper ( OriginalSendFileFeature , ResponseCacheStream ) ) ;
532
545
}
533
546
}
534
547
535
548
internal void UnshimResponseStream ( )
536
549
{
537
550
// Unshim response stream
538
- HttpContext . Response . Body = OriginalResponseStream ;
551
+ _httpContext . Response . Body = OriginalResponseStream ;
539
552
540
553
// Unshim IHttpSendFileFeature
541
- HttpContext . Features . Set ( OriginalSendFileFeature ) ;
554
+ _httpContext . Features . Set ( OriginalSendFileFeature ) ;
542
555
}
543
556
544
557
private enum ResponseType
0 commit comments