1212using Microsoft . AspNetCore . ResponseCaching . Internal ;
1313using Microsoft . Extensions . Primitives ;
1414using Microsoft . Net . Http . Headers ;
15+ using Microsoft . Extensions . ObjectPool ;
1516
1617namespace Microsoft . AspNetCore . ResponseCaching
1718{
1819 public class ResponseCachingContext
1920 {
2021 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+
2130 private string _cacheKey ;
2231 private ResponseType ? _responseType ;
2332 private RequestHeaders _requestHeaders ;
@@ -32,9 +41,10 @@ public class ResponseCachingContext
3241 public ResponseCachingContext (
3342 HttpContext httpContext ,
3443 IResponseCache cache ,
44+ ObjectPool < StringBuilder > builderPool ,
3545 IResponseCachingCacheabilityValidator cacheabilityValidator ,
3646 IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider )
37- : this ( httpContext , cache , new SystemClock ( ) , cacheabilityValidator , cacheKeySuffixProvider )
47+ : this ( httpContext , cache , new SystemClock ( ) , builderPool , cacheabilityValidator , cacheKeySuffixProvider )
3848 {
3949 }
4050
@@ -43,14 +53,16 @@ internal ResponseCachingContext(
4353 HttpContext httpContext ,
4454 IResponseCache cache ,
4555 ISystemClock clock ,
56+ ObjectPool < StringBuilder > builderPool ,
4657 IResponseCachingCacheabilityValidator cacheabilityValidator ,
4758 IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider )
4859 {
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 ;
5466 }
5567
5668 internal bool CacheResponse
@@ -70,16 +82,6 @@ internal bool CacheResponse
7082
7183 internal bool ResponseStarted { get ; set ; }
7284
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-
8385 private Stream OriginalResponseStream { get ; set ; }
8486
8587 private ResponseCacheStream ResponseCacheStream { get ; set ; }
@@ -92,7 +94,7 @@ private RequestHeaders RequestHeaders
9294 {
9395 if ( _requestHeaders == null )
9496 {
95- _requestHeaders = HttpContext . Request . GetTypedHeaders ( ) ;
97+ _requestHeaders = _httpContext . Request . GetTypedHeaders ( ) ;
9698 }
9799 return _requestHeaders ;
98100 }
@@ -104,7 +106,7 @@ private ResponseHeaders ResponseHeaders
104106 {
105107 if ( _responseHeaders == null )
106108 {
107- _responseHeaders = HttpContext . Response . GetTypedHeaders ( ) ;
109+ _responseHeaders = _httpContext . Response . GetTypedHeaders ( ) ;
108110 }
109111 return _responseHeaders ;
110112 }
@@ -143,49 +145,61 @@ internal string CreateCacheKey()
143145
144146 internal string CreateCacheKey ( CachedVaryBy varyBy )
145147 {
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 ( ) ;
151150
152- if ( varyBy ? . Headers . Count > 0 )
151+ try
153152 {
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 ( ) ) ;
159157
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 )
162162 {
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 ) ;
164176 }
177+ }
178+ // TODO: Parse querystring params
165179
180+ // Append custom cache key segment
181+ var customKey = _cacheKeySuffixProvider . CreateCustomKeySuffix ( _httpContext ) ;
182+ if ( ! string . IsNullOrEmpty ( customKey ) )
183+ {
166184 builder . Append ( ";" )
167- . Append ( header )
168- . Append ( "=" )
169- . Append ( value ) ;
185+ . Append ( customKey ) ;
170186 }
171- }
172- // TODO: Parse querystring params
173187
174- // Append custom cache key segment
175- var customKey = CacheKeySuffixProvider . CreateCustomKeySuffix ( HttpContext ) ;
176- if ( ! string . IsNullOrEmpty ( customKey ) )
188+ return builder . ToString ( ) ;
189+ }
190+ finally
177191 {
178- builder . Append ( ";" )
179- . Append ( customKey ) ;
192+ if ( _builderPool != null )
193+ {
194+ _builderPool . Return ( builder ) ;
195+ }
180196 }
181-
182- return builder . ToString ( ) ;
183197 }
184198
185199 internal bool RequestIsCacheable ( )
186200 {
187201 // Use optional override if specified by user
188- switch ( CacheabilityValidator . RequestIsCacheableOverride ( HttpContext ) )
202+ switch ( _cacheabilityValidator . RequestIsCacheableOverride ( _httpContext ) )
189203 {
190204 case OverrideResult . UseDefaultLogic :
191205 break ;
@@ -194,12 +208,12 @@ internal bool RequestIsCacheable()
194208 case OverrideResult . Cache :
195209 return true ;
196210 default :
197- throw new NotSupportedException ( $ "Unrecognized result from { nameof ( CacheabilityValidator . RequestIsCacheableOverride ) } .") ;
211+ throw new NotSupportedException ( $ "Unrecognized result from { nameof ( _cacheabilityValidator . RequestIsCacheableOverride ) } .") ;
198212 }
199213
200214 // Verify the method
201215 // 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 ;
203217 if ( string . Equals ( "GET" , request . Method , StringComparison . OrdinalIgnoreCase ) )
204218 {
205219 _responseType = ResponseType . FullReponse ;
@@ -249,7 +263,7 @@ internal bool RequestIsCacheable()
249263 internal bool ResponseIsCacheable ( )
250264 {
251265 // Use optional override if specified by user
252- switch ( CacheabilityValidator . ResponseIsCacheableOverride ( HttpContext ) )
266+ switch ( _cacheabilityValidator . ResponseIsCacheableOverride ( _httpContext ) )
253267 {
254268 case OverrideResult . UseDefaultLogic :
255269 break ;
@@ -258,7 +272,7 @@ internal bool ResponseIsCacheable()
258272 case OverrideResult . Cache :
259273 return true ;
260274 default :
261- throw new NotSupportedException ( $ "Unrecognized result from { nameof ( CacheabilityValidator . ResponseIsCacheableOverride ) } .") ;
275+ throw new NotSupportedException ( $ "Unrecognized result from { nameof ( _cacheabilityValidator . ResponseIsCacheableOverride ) } .") ;
262276 }
263277
264278 // Only cache pages explicitly marked with public
@@ -281,7 +295,7 @@ internal bool ResponseIsCacheable()
281295 return false ;
282296 }
283297
284- var response = HttpContext . Response ;
298+ var response = _httpContext . Response ;
285299
286300 // Do not cache responses varying by *
287301 if ( string . Equals ( response . Headers [ HeaderNames . Vary ] , "*" , StringComparison . OrdinalIgnoreCase ) )
@@ -359,28 +373,28 @@ internal bool EntryIsFresh(ResponseHeaders responseHeaders, TimeSpan age, bool v
359373 internal async Task < bool > TryServeFromCacheAsync ( )
360374 {
361375 _cacheKey = CreateCacheKey ( ) ;
362- var cacheEntry = Cache . Get ( _cacheKey ) ;
376+ var cacheEntry = _cache . Get ( _cacheKey ) ;
363377 var responseServed = false ;
364378
365379 if ( cacheEntry is CachedVaryBy )
366380 {
367381 // Request contains VaryBy rules, recompute key and try again
368382 _cacheKey = CreateCacheKey ( cacheEntry as CachedVaryBy ) ;
369- cacheEntry = Cache . Get ( _cacheKey ) ;
383+ cacheEntry = _cache . Get ( _cacheKey ) ;
370384 }
371385
372386 if ( cacheEntry is CachedResponse )
373387 {
374388 var cachedResponse = cacheEntry as CachedResponse ;
375389 var cachedResponseHeaders = new ResponseHeaders ( cachedResponse . Headers ) ;
376390
377- _responseTime = Clock . UtcNow ;
391+ _responseTime = _clock . UtcNow ;
378392 var age = _responseTime - cachedResponse . Created ;
379393 age = age > TimeSpan . Zero ? age : TimeSpan . Zero ;
380394
381395 if ( EntryIsFresh ( cachedResponseHeaders , age , verifyAgainstRequest : true ) )
382396 {
383- var response = HttpContext . Response ;
397+ var response = _httpContext . Response ;
384398 // Copy the cached status code and response headers
385399 response . StatusCode = cachedResponse . StatusCode ;
386400 foreach ( var header in cachedResponse . Headers )
@@ -425,7 +439,7 @@ internal async Task<bool> TryServeFromCacheAsync()
425439
426440 if ( ! responseServed && RequestCacheControl . OnlyIfCached )
427441 {
428- HttpContext . Response . StatusCode = StatusCodes . Status504GatewayTimeout ;
442+ _httpContext . Response . StatusCode = StatusCodes . Status504GatewayTimeout ;
429443
430444 responseServed = true ;
431445 }
@@ -438,7 +452,7 @@ internal void FinalizeCachingHeaders()
438452 if ( CacheResponse )
439453 {
440454 // Create the cache entry now
441- var response = HttpContext . Response ;
455+ var response = _httpContext . Response ;
442456 var varyHeaderValue = response . Headers [ HeaderNames . Vary ] ;
443457 _cachedResponseValidFor = ResponseCacheControl . SharedMaxAge
444458 ?? ResponseCacheControl . MaxAge
@@ -457,7 +471,7 @@ internal void FinalizeCachingHeaders()
457471 } ;
458472
459473 // TODO: Overwrite?
460- Cache . Set ( _cacheKey , cachedVaryBy , _cachedResponseValidFor ) ;
474+ _cache . Set ( _cacheKey , cachedVaryBy , _cachedResponseValidFor ) ;
461475 _cacheKey = CreateCacheKey ( cachedVaryBy ) ;
462476 }
463477
@@ -471,7 +485,7 @@ internal void FinalizeCachingHeaders()
471485 _cachedResponse = new CachedResponse
472486 {
473487 Created = ResponseHeaders . Date . Value ,
474- StatusCode = HttpContext . Response . StatusCode
488+ StatusCode = _httpContext . Response . StatusCode
475489 } ;
476490
477491 foreach ( var header in ResponseHeaders . Headers )
@@ -495,7 +509,7 @@ internal void FinalizeCachingBody()
495509 {
496510 _cachedResponse . Body = ResponseCacheStream . BufferedStream . ToArray ( ) ;
497511
498- Cache . Set ( _cacheKey , _cachedResponse , _cachedResponseValidFor ) ;
512+ _cache . Set ( _cacheKey , _cachedResponse , _cachedResponseValidFor ) ;
499513 }
500514 }
501515
@@ -504,7 +518,7 @@ internal void OnResponseStarting()
504518 if ( ! ResponseStarted )
505519 {
506520 ResponseStarted = true ;
507- _responseTime = Clock . UtcNow ;
521+ _responseTime = _clock . UtcNow ;
508522
509523 FinalizeCachingHeaders ( ) ;
510524 }
@@ -515,25 +529,25 @@ internal void ShimResponseStream()
515529 // TODO: Consider caching large responses on disk and serving them from there.
516530
517531 // Shim response stream
518- OriginalResponseStream = HttpContext . Response . Body ;
532+ OriginalResponseStream = _httpContext . Response . Body ;
519533 ResponseCacheStream = new ResponseCacheStream ( OriginalResponseStream ) ;
520- HttpContext . Response . Body = ResponseCacheStream ;
534+ _httpContext . Response . Body = ResponseCacheStream ;
521535
522536 // Shim IHttpSendFileFeature
523- OriginalSendFileFeature = HttpContext . Features . Get < IHttpSendFileFeature > ( ) ;
537+ OriginalSendFileFeature = _httpContext . Features . Get < IHttpSendFileFeature > ( ) ;
524538 if ( OriginalSendFileFeature != null )
525539 {
526- HttpContext . Features . Set < IHttpSendFileFeature > ( new SendFileFeatureWrapper ( OriginalSendFileFeature , ResponseCacheStream ) ) ;
540+ _httpContext . Features . Set < IHttpSendFileFeature > ( new SendFileFeatureWrapper ( OriginalSendFileFeature , ResponseCacheStream ) ) ;
527541 }
528542 }
529543
530544 internal void UnshimResponseStream ( )
531545 {
532546 // Unshim response stream
533- HttpContext . Response . Body = OriginalResponseStream ;
547+ _httpContext . Response . Body = OriginalResponseStream ;
534548
535549 // Unshim IHttpSendFileFeature
536- HttpContext . Features . Set ( OriginalSendFileFeature ) ;
550+ _httpContext . Features . Set ( OriginalSendFileFeature ) ;
537551 }
538552
539553 private enum ResponseType
0 commit comments