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 IResponseCachingCacheabilityValidator _cacheabilityValidator ;
28
+ private readonly IResponseCachingCacheKeySuffixProvider _cacheKeySuffixProvider ;
29
+
21
30
private string _cacheKey ;
22
31
private ResponseType ? _responseType ;
23
32
private RequestHeaders _requestHeaders ;
@@ -32,9 +41,10 @@ public class ResponseCachingContext
32
41
public ResponseCachingContext (
33
42
HttpContext httpContext ,
34
43
IResponseCache cache ,
44
+ ObjectPool < StringBuilder > builderPool ,
35
45
IResponseCachingCacheabilityValidator cacheabilityValidator ,
36
46
IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider )
37
- : this ( httpContext , cache , new SystemClock ( ) , cacheabilityValidator , cacheKeySuffixProvider )
47
+ : this ( httpContext , cache , new SystemClock ( ) , builderPool , cacheabilityValidator , cacheKeySuffixProvider )
38
48
{
39
49
}
40
50
@@ -43,14 +53,16 @@ internal ResponseCachingContext(
43
53
HttpContext httpContext ,
44
54
IResponseCache cache ,
45
55
ISystemClock clock ,
56
+ ObjectPool < StringBuilder > builderPool ,
46
57
IResponseCachingCacheabilityValidator cacheabilityValidator ,
47
58
IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider )
48
59
{
49
- HttpContext = httpContext ;
50
- Cache = cache ;
51
- Clock = clock ;
52
- CacheabilityValidator = cacheabilityValidator ;
53
- CacheKeySuffixProvider = cacheKeySuffixProvider ;
60
+ _httpContext = httpContext ;
61
+ _cache = cache ;
62
+ _clock = clock ;
63
+ _builderPool = builderPool ;
64
+ _cacheabilityValidator = cacheabilityValidator ;
65
+ _cacheKeySuffixProvider = cacheKeySuffixProvider ;
54
66
}
55
67
56
68
internal bool CacheResponse
@@ -70,16 +82,6 @@ internal bool CacheResponse
70
82
71
83
internal bool ResponseStarted { get ; set ; }
72
84
73
- private HttpContext HttpContext { get ; }
74
-
75
- private IResponseCache Cache { get ; }
76
-
77
- private ISystemClock Clock { get ; }
78
-
79
- private IResponseCachingCacheabilityValidator CacheabilityValidator { get ; }
80
-
81
- private IResponseCachingCacheKeySuffixProvider CacheKeySuffixProvider { get ; }
82
-
83
85
private Stream OriginalResponseStream { get ; set ; }
84
86
85
87
private ResponseCacheStream ResponseCacheStream { get ; set ; }
@@ -92,7 +94,7 @@ private RequestHeaders RequestHeaders
92
94
{
93
95
if ( _requestHeaders == null )
94
96
{
95
- _requestHeaders = HttpContext . Request . GetTypedHeaders ( ) ;
97
+ _requestHeaders = _httpContext . Request . GetTypedHeaders ( ) ;
96
98
}
97
99
return _requestHeaders ;
98
100
}
@@ -104,7 +106,7 @@ private ResponseHeaders ResponseHeaders
104
106
{
105
107
if ( _responseHeaders == null )
106
108
{
107
- _responseHeaders = HttpContext . Response . GetTypedHeaders ( ) ;
109
+ _responseHeaders = _httpContext . Response . GetTypedHeaders ( ) ;
108
110
}
109
111
return _responseHeaders ;
110
112
}
@@ -143,49 +145,61 @@ internal string CreateCacheKey()
143
145
144
146
internal string CreateCacheKey ( CachedVaryBy varyBy )
145
147
{
146
- var request = HttpContext . Request ;
147
- var builder = new StringBuilder ( )
148
- . Append ( request . Method . ToUpperInvariant ( ) )
149
- . Append ( ";" )
150
- . Append ( request . Path . Value . ToUpperInvariant ( ) ) ;
148
+ var request = _httpContext . Request ;
149
+ var builder = _builderPool ? . Get ( ) ?? new StringBuilder ( ) ;
151
150
152
- if ( varyBy ? . Headers . Count > 0 )
151
+ try
153
152
{
154
- // TODO: resolve key format and delimiters
155
- foreach ( var header in varyBy . Headers )
156
- {
157
- // TODO: Normalization of order, case?
158
- var value = HttpContext . Request . Headers [ header ] ;
153
+ builder
154
+ . Append ( request . Method . ToUpperInvariant ( ) )
155
+ . Append ( ";" )
156
+ . Append ( request . Path . Value . ToUpperInvariant ( ) ) ;
159
157
160
- // TODO: How to handle null/empty string?
161
- if ( StringValues . IsNullOrEmpty ( value ) )
158
+ if ( varyBy ? . Headers . Count > 0 )
159
+ {
160
+ // TODO: resolve key format and delimiters
161
+ foreach ( var header in varyBy . Headers )
162
162
{
163
- value = "null" ;
163
+ // TODO: Normalization of order, case?
164
+ var value = _httpContext . Request . Headers [ header ] ;
165
+
166
+ // TODO: How to handle null/empty string?
167
+ if ( StringValues . IsNullOrEmpty ( value ) )
168
+ {
169
+ value = "null" ;
170
+ }
171
+
172
+ builder . Append ( ";" )
173
+ . Append ( header )
174
+ . Append ( "=" )
175
+ . Append ( value ) ;
164
176
}
177
+ }
178
+ // TODO: Parse querystring params
165
179
180
+ // Append custom cache key segment
181
+ var customKey = _cacheKeySuffixProvider . CreateCustomKeySuffix ( _httpContext ) ;
182
+ if ( ! string . IsNullOrEmpty ( customKey ) )
183
+ {
166
184
builder . Append ( ";" )
167
- . Append ( header )
168
- . Append ( "=" )
169
- . Append ( value ) ;
185
+ . Append ( customKey ) ;
170
186
}
171
- }
172
- // TODO: Parse querystring params
173
187
174
- // Append custom cache key segment
175
- var customKey = CacheKeySuffixProvider . CreateCustomKeySuffix ( HttpContext ) ;
176
- if ( ! string . IsNullOrEmpty ( customKey ) )
188
+ return builder . ToString ( ) ;
189
+ }
190
+ finally
177
191
{
178
- builder . Append ( ";" )
179
- . Append ( customKey ) ;
192
+ if ( _builderPool != null )
193
+ {
194
+ _builderPool . Return ( builder ) ;
195
+ }
180
196
}
181
-
182
- return builder . ToString ( ) ;
183
197
}
184
198
185
199
internal bool RequestIsCacheable ( )
186
200
{
187
201
// Use optional override if specified by user
188
- switch ( CacheabilityValidator . RequestIsCacheableOverride ( HttpContext ) )
202
+ switch ( _cacheabilityValidator . RequestIsCacheableOverride ( _httpContext ) )
189
203
{
190
204
case OverrideResult . UseDefaultLogic :
191
205
break ;
@@ -194,12 +208,12 @@ internal bool RequestIsCacheable()
194
208
case OverrideResult . Cache :
195
209
return true ;
196
210
default :
197
- throw new NotSupportedException ( $ "Unrecognized result from { nameof ( CacheabilityValidator . RequestIsCacheableOverride ) } .") ;
211
+ throw new NotSupportedException ( $ "Unrecognized result from { nameof ( _cacheabilityValidator . RequestIsCacheableOverride ) } .") ;
198
212
}
199
213
200
214
// Verify the method
201
215
// TODO: RFC lists POST as a cacheable method when explicit freshness information is provided, but this is not widely implemented. Will revisit.
202
- var request = HttpContext . Request ;
216
+ var request = _httpContext . Request ;
203
217
if ( string . Equals ( "GET" , request . Method , StringComparison . OrdinalIgnoreCase ) )
204
218
{
205
219
_responseType = ResponseType . FullReponse ;
@@ -249,7 +263,7 @@ internal bool RequestIsCacheable()
249
263
internal bool ResponseIsCacheable ( )
250
264
{
251
265
// Use optional override if specified by user
252
- switch ( CacheabilityValidator . ResponseIsCacheableOverride ( HttpContext ) )
266
+ switch ( _cacheabilityValidator . ResponseIsCacheableOverride ( _httpContext ) )
253
267
{
254
268
case OverrideResult . UseDefaultLogic :
255
269
break ;
@@ -258,7 +272,7 @@ internal bool ResponseIsCacheable()
258
272
case OverrideResult . Cache :
259
273
return true ;
260
274
default :
261
- throw new NotSupportedException ( $ "Unrecognized result from { nameof ( CacheabilityValidator . ResponseIsCacheableOverride ) } .") ;
275
+ throw new NotSupportedException ( $ "Unrecognized result from { nameof ( _cacheabilityValidator . ResponseIsCacheableOverride ) } .") ;
262
276
}
263
277
264
278
// Only cache pages explicitly marked with public
@@ -281,7 +295,7 @@ internal bool ResponseIsCacheable()
281
295
return false ;
282
296
}
283
297
284
- var response = HttpContext . Response ;
298
+ var response = _httpContext . Response ;
285
299
286
300
// Do not cache responses varying by *
287
301
if ( string . Equals ( response . Headers [ HeaderNames . Vary ] , "*" , StringComparison . OrdinalIgnoreCase ) )
@@ -359,28 +373,28 @@ internal bool EntryIsFresh(ResponseHeaders responseHeaders, TimeSpan age, bool v
359
373
internal async Task < bool > TryServeFromCacheAsync ( )
360
374
{
361
375
_cacheKey = CreateCacheKey ( ) ;
362
- var cacheEntry = Cache . Get ( _cacheKey ) ;
376
+ var cacheEntry = _cache . Get ( _cacheKey ) ;
363
377
var responseServed = false ;
364
378
365
379
if ( cacheEntry is CachedVaryBy )
366
380
{
367
381
// Request contains VaryBy rules, recompute key and try again
368
382
_cacheKey = CreateCacheKey ( cacheEntry as CachedVaryBy ) ;
369
- cacheEntry = Cache . Get ( _cacheKey ) ;
383
+ cacheEntry = _cache . Get ( _cacheKey ) ;
370
384
}
371
385
372
386
if ( cacheEntry is CachedResponse )
373
387
{
374
388
var cachedResponse = cacheEntry as CachedResponse ;
375
389
var cachedResponseHeaders = new ResponseHeaders ( cachedResponse . Headers ) ;
376
390
377
- _responseTime = Clock . UtcNow ;
391
+ _responseTime = _clock . UtcNow ;
378
392
var age = _responseTime - cachedResponse . Created ;
379
393
age = age > TimeSpan . Zero ? age : TimeSpan . Zero ;
380
394
381
395
if ( EntryIsFresh ( cachedResponseHeaders , age , verifyAgainstRequest : true ) )
382
396
{
383
- var response = HttpContext . Response ;
397
+ var response = _httpContext . Response ;
384
398
// Copy the cached status code and response headers
385
399
response . StatusCode = cachedResponse . StatusCode ;
386
400
foreach ( var header in cachedResponse . Headers )
@@ -425,7 +439,7 @@ internal async Task<bool> TryServeFromCacheAsync()
425
439
426
440
if ( ! responseServed && RequestCacheControl . OnlyIfCached )
427
441
{
428
- HttpContext . Response . StatusCode = StatusCodes . Status504GatewayTimeout ;
442
+ _httpContext . Response . StatusCode = StatusCodes . Status504GatewayTimeout ;
429
443
430
444
responseServed = true ;
431
445
}
@@ -438,7 +452,7 @@ internal void FinalizeCachingHeaders()
438
452
if ( CacheResponse )
439
453
{
440
454
// Create the cache entry now
441
- var response = HttpContext . Response ;
455
+ var response = _httpContext . Response ;
442
456
var varyHeaderValue = response . Headers [ HeaderNames . Vary ] ;
443
457
_cachedResponseValidFor = ResponseCacheControl . SharedMaxAge
444
458
?? ResponseCacheControl . MaxAge
@@ -457,7 +471,7 @@ internal void FinalizeCachingHeaders()
457
471
} ;
458
472
459
473
// TODO: Overwrite?
460
- Cache . Set ( _cacheKey , cachedVaryBy , _cachedResponseValidFor ) ;
474
+ _cache . Set ( _cacheKey , cachedVaryBy , _cachedResponseValidFor ) ;
461
475
_cacheKey = CreateCacheKey ( cachedVaryBy ) ;
462
476
}
463
477
@@ -471,7 +485,7 @@ internal void FinalizeCachingHeaders()
471
485
_cachedResponse = new CachedResponse
472
486
{
473
487
Created = ResponseHeaders . Date . Value ,
474
- StatusCode = HttpContext . Response . StatusCode
488
+ StatusCode = _httpContext . Response . StatusCode
475
489
} ;
476
490
477
491
foreach ( var header in ResponseHeaders . Headers )
@@ -495,7 +509,7 @@ internal void FinalizeCachingBody()
495
509
{
496
510
_cachedResponse . Body = ResponseCacheStream . BufferedStream . ToArray ( ) ;
497
511
498
- Cache . Set ( _cacheKey , _cachedResponse , _cachedResponseValidFor ) ;
512
+ _cache . Set ( _cacheKey , _cachedResponse , _cachedResponseValidFor ) ;
499
513
}
500
514
}
501
515
@@ -504,7 +518,7 @@ internal void OnResponseStarting()
504
518
if ( ! ResponseStarted )
505
519
{
506
520
ResponseStarted = true ;
507
- _responseTime = Clock . UtcNow ;
521
+ _responseTime = _clock . UtcNow ;
508
522
509
523
FinalizeCachingHeaders ( ) ;
510
524
}
@@ -515,25 +529,25 @@ internal void ShimResponseStream()
515
529
// TODO: Consider caching large responses on disk and serving them from there.
516
530
517
531
// Shim response stream
518
- OriginalResponseStream = HttpContext . Response . Body ;
532
+ OriginalResponseStream = _httpContext . Response . Body ;
519
533
ResponseCacheStream = new ResponseCacheStream ( OriginalResponseStream ) ;
520
- HttpContext . Response . Body = ResponseCacheStream ;
534
+ _httpContext . Response . Body = ResponseCacheStream ;
521
535
522
536
// Shim IHttpSendFileFeature
523
- OriginalSendFileFeature = HttpContext . Features . Get < IHttpSendFileFeature > ( ) ;
537
+ OriginalSendFileFeature = _httpContext . Features . Get < IHttpSendFileFeature > ( ) ;
524
538
if ( OriginalSendFileFeature != null )
525
539
{
526
- HttpContext . Features . Set < IHttpSendFileFeature > ( new SendFileFeatureWrapper ( OriginalSendFileFeature , ResponseCacheStream ) ) ;
540
+ _httpContext . Features . Set < IHttpSendFileFeature > ( new SendFileFeatureWrapper ( OriginalSendFileFeature , ResponseCacheStream ) ) ;
527
541
}
528
542
}
529
543
530
544
internal void UnshimResponseStream ( )
531
545
{
532
546
// Unshim response stream
533
- HttpContext . Response . Body = OriginalResponseStream ;
547
+ _httpContext . Response . Body = OriginalResponseStream ;
534
548
535
549
// Unshim IHttpSendFileFeature
536
- HttpContext . Features . Set ( OriginalSendFileFeature ) ;
550
+ _httpContext . Features . Set ( OriginalSendFileFeature ) ;
537
551
}
538
552
539
553
private enum ResponseType
0 commit comments