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 IResponseCachingRequestCacheabilityValidator _requestCacheabilityValidator ;
28+ private readonly IResponseCachingResponseCacheabilityValidator _responseCacheabilityValidator ;
29+ private readonly IResponseCachingCacheKeySuffixProvider _cacheKeySuffixProvider ;
30+
2131 private string _cacheKey ;
2232 private ResponseType ? _responseType ;
2333 private RequestHeaders _requestHeaders ;
@@ -32,28 +42,31 @@ public class ResponseCachingContext
3242 public ResponseCachingContext (
3343 HttpContext httpContext ,
3444 IResponseCache cache ,
45+ ObjectPool < StringBuilder > builderPool ,
3546 IResponseCachingRequestCacheabilityValidator requestCacheabilityValidator ,
3647 IResponseCachingResponseCacheabilityValidator responseCacheabilityValidator ,
3748 IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider )
38- : this ( httpContext , cache , new SystemClock ( ) , requestCacheabilityValidator , responseCacheabilityValidator , cacheKeySuffixProvider )
49+ : this ( httpContext , cache , new SystemClock ( ) , builderPool , requestCacheabilityValidator , responseCacheabilityValidator , cacheKeySuffixProvider )
3950 {
4051 }
4152
4253 // Internal for testing
4354 internal ResponseCachingContext (
4455 HttpContext httpContext ,
45- IResponseCache cache ,
56+ IResponseCache cache ,
4657 ISystemClock clock ,
58+ ObjectPool < StringBuilder > builderPool ,
4759 IResponseCachingRequestCacheabilityValidator requestCacheabilityValidator ,
4860 IResponseCachingResponseCacheabilityValidator responseCacheabilityValidator ,
4961 IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider )
5062 {
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 ;
5770 }
5871
5972 internal bool CacheResponse
@@ -73,18 +86,6 @@ internal bool CacheResponse
7386
7487 internal bool ResponseStarted { get ; set ; }
7588
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-
8889 private Stream OriginalResponseStream { get ; set ; }
8990
9091 private ResponseCacheStream ResponseCacheStream { get ; set ; }
@@ -97,7 +98,7 @@ private RequestHeaders RequestHeaders
9798 {
9899 if ( _requestHeaders == null )
99100 {
100- _requestHeaders = HttpContext . Request . GetTypedHeaders ( ) ;
101+ _requestHeaders = _httpContext . Request . GetTypedHeaders ( ) ;
101102 }
102103 return _requestHeaders ;
103104 }
@@ -109,7 +110,7 @@ private ResponseHeaders ResponseHeaders
109110 {
110111 if ( _responseHeaders == null )
111112 {
112- _responseHeaders = HttpContext . Response . GetTypedHeaders ( ) ;
113+ _responseHeaders = _httpContext . Response . GetTypedHeaders ( ) ;
113114 }
114115 return _responseHeaders ;
115116 }
@@ -148,49 +149,61 @@ internal string CreateCacheKey()
148149
149150 internal string CreateCacheKey ( CachedVaryBy varyBy )
150151 {
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 ( ) ;
156154
157- if ( varyBy ? . Headers . Count > 0 )
155+ try
158156 {
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 ( ) ) ;
164161
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 )
167166 {
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 ) ;
169180 }
181+ }
182+ // TODO: Parse querystring params
170183
184+ // Append custom cache key segment
185+ var customKey = _cacheKeySuffixProvider . CreateCustomKeySuffix ( _httpContext ) ;
186+ if ( ! string . IsNullOrEmpty ( customKey ) )
187+ {
171188 builder . Append ( ";" )
172- . Append ( header )
173- . Append ( "=" )
174- . Append ( value ) ;
189+ . Append ( customKey ) ;
175190 }
176- }
177- // TODO: Parse querystring params
178191
179- // Append custom cache key segment
180- var customKey = CacheKeySuffixProvider . CreateCustomKeySuffix ( HttpContext ) ;
181- if ( ! string . IsNullOrEmpty ( customKey ) )
192+ return builder . ToString ( ) ;
193+ }
194+ finally
182195 {
183- builder . Append ( ";" )
184- . Append ( customKey ) ;
196+ if ( _builderPool != null )
197+ {
198+ _builderPool . Return ( builder ) ;
199+ }
185200 }
186-
187- return builder . ToString ( ) ;
188201 }
189202
190203 internal bool RequestIsCacheable ( )
191204 {
192205 // Use optional override if specified by user
193- switch ( RequestCacheabilityValidator . RequestIsCacheableOverride ( HttpContext ) )
206+ switch ( _requestCacheabilityValidator . RequestIsCacheableOverride ( _httpContext ) )
194207 {
195208 case OverrideResult . UseDefaultLogic :
196209 break ;
@@ -199,12 +212,12 @@ internal bool RequestIsCacheable()
199212 case OverrideResult . Cache :
200213 return true ;
201214 default :
202- throw new NotSupportedException ( $ "Unrecognized result from { nameof ( RequestCacheabilityValidator . RequestIsCacheableOverride ) } .") ;
215+ throw new NotSupportedException ( $ "Unrecognized result from { nameof ( _requestCacheabilityValidator . RequestIsCacheableOverride ) } .") ;
203216 }
204217
205218 // Verify the method
206219 // 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 ;
208221 if ( string . Equals ( "GET" , request . Method , StringComparison . OrdinalIgnoreCase ) )
209222 {
210223 _responseType = ResponseType . FullReponse ;
@@ -254,7 +267,7 @@ internal bool RequestIsCacheable()
254267 internal bool ResponseIsCacheable ( )
255268 {
256269 // Use optional override if specified by user
257- switch ( ResponseCacheabilityValidator . ResponseIsCacheableOverride ( HttpContext ) )
270+ switch ( _responseCacheabilityValidator . ResponseIsCacheableOverride ( _httpContext ) )
258271 {
259272 case OverrideResult . UseDefaultLogic :
260273 break ;
@@ -263,7 +276,7 @@ internal bool ResponseIsCacheable()
263276 case OverrideResult . Cache :
264277 return true ;
265278 default :
266- throw new NotSupportedException ( $ "Unrecognized result from { nameof ( ResponseCacheabilityValidator . ResponseIsCacheableOverride ) } .") ;
279+ throw new NotSupportedException ( $ "Unrecognized result from { nameof ( _responseCacheabilityValidator . ResponseIsCacheableOverride ) } .") ;
267280 }
268281
269282 // Only cache pages explicitly marked with public
@@ -286,7 +299,7 @@ internal bool ResponseIsCacheable()
286299 return false ;
287300 }
288301
289- var response = HttpContext . Response ;
302+ var response = _httpContext . Response ;
290303
291304 // Do not cache responses varying by *
292305 if ( string . Equals ( response . Headers [ HeaderNames . Vary ] , "*" , StringComparison . OrdinalIgnoreCase ) )
@@ -364,28 +377,28 @@ internal bool EntryIsFresh(ResponseHeaders responseHeaders, TimeSpan age, bool v
364377 internal async Task < bool > TryServeFromCacheAsync ( )
365378 {
366379 _cacheKey = CreateCacheKey ( ) ;
367- var cacheEntry = Cache . Get ( _cacheKey ) ;
380+ var cacheEntry = _cache . Get ( _cacheKey ) ;
368381 var responseServed = false ;
369382
370383 if ( cacheEntry is CachedVaryBy )
371384 {
372385 // Request contains VaryBy rules, recompute key and try again
373386 _cacheKey = CreateCacheKey ( cacheEntry as CachedVaryBy ) ;
374- cacheEntry = Cache . Get ( _cacheKey ) ;
387+ cacheEntry = _cache . Get ( _cacheKey ) ;
375388 }
376389
377390 if ( cacheEntry is CachedResponse )
378391 {
379392 var cachedResponse = cacheEntry as CachedResponse ;
380393 var cachedResponseHeaders = new ResponseHeaders ( cachedResponse . Headers ) ;
381394
382- _responseTime = Clock . UtcNow ;
395+ _responseTime = _clock . UtcNow ;
383396 var age = _responseTime - cachedResponse . Created ;
384397 age = age > TimeSpan . Zero ? age : TimeSpan . Zero ;
385398
386399 if ( EntryIsFresh ( cachedResponseHeaders , age , verifyAgainstRequest : true ) )
387400 {
388- var response = HttpContext . Response ;
401+ var response = _httpContext . Response ;
389402 // Copy the cached status code and response headers
390403 response . StatusCode = cachedResponse . StatusCode ;
391404 foreach ( var header in cachedResponse . Headers )
@@ -430,7 +443,7 @@ internal async Task<bool> TryServeFromCacheAsync()
430443
431444 if ( ! responseServed && RequestCacheControl . OnlyIfCached )
432445 {
433- HttpContext . Response . StatusCode = StatusCodes . Status504GatewayTimeout ;
446+ _httpContext . Response . StatusCode = StatusCodes . Status504GatewayTimeout ;
434447
435448 responseServed = true ;
436449 }
@@ -443,7 +456,7 @@ internal void FinalizeCachingHeaders()
443456 if ( CacheResponse )
444457 {
445458 // Create the cache entry now
446- var response = HttpContext . Response ;
459+ var response = _httpContext . Response ;
447460 var varyHeaderValue = response . Headers [ HeaderNames . Vary ] ;
448461 _cachedResponseValidFor = ResponseCacheControl . SharedMaxAge
449462 ?? ResponseCacheControl . MaxAge
@@ -462,7 +475,7 @@ internal void FinalizeCachingHeaders()
462475 } ;
463476
464477 // TODO: Overwrite?
465- Cache . Set ( _cacheKey , cachedVaryBy , _cachedResponseValidFor ) ;
478+ _cache . Set ( _cacheKey , cachedVaryBy , _cachedResponseValidFor ) ;
466479 _cacheKey = CreateCacheKey ( cachedVaryBy ) ;
467480 }
468481
@@ -476,7 +489,7 @@ internal void FinalizeCachingHeaders()
476489 _cachedResponse = new CachedResponse
477490 {
478491 Created = ResponseHeaders . Date . Value ,
479- StatusCode = HttpContext . Response . StatusCode
492+ StatusCode = _httpContext . Response . StatusCode
480493 } ;
481494
482495 foreach ( var header in ResponseHeaders . Headers )
@@ -500,7 +513,7 @@ internal void FinalizeCachingBody()
500513 {
501514 _cachedResponse . Body = ResponseCacheStream . BufferedStream . ToArray ( ) ;
502515
503- Cache . Set ( _cacheKey , _cachedResponse , _cachedResponseValidFor ) ;
516+ _cache . Set ( _cacheKey , _cachedResponse , _cachedResponseValidFor ) ;
504517 }
505518 }
506519
@@ -509,7 +522,7 @@ internal void OnResponseStarting()
509522 if ( ! ResponseStarted )
510523 {
511524 ResponseStarted = true ;
512- _responseTime = Clock . UtcNow ;
525+ _responseTime = _clock . UtcNow ;
513526
514527 FinalizeCachingHeaders ( ) ;
515528 }
@@ -520,25 +533,25 @@ internal void ShimResponseStream()
520533 // TODO: Consider caching large responses on disk and serving them from there.
521534
522535 // Shim response stream
523- OriginalResponseStream = HttpContext . Response . Body ;
536+ OriginalResponseStream = _httpContext . Response . Body ;
524537 ResponseCacheStream = new ResponseCacheStream ( OriginalResponseStream ) ;
525- HttpContext . Response . Body = ResponseCacheStream ;
538+ _httpContext . Response . Body = ResponseCacheStream ;
526539
527540 // Shim IHttpSendFileFeature
528- OriginalSendFileFeature = HttpContext . Features . Get < IHttpSendFileFeature > ( ) ;
541+ OriginalSendFileFeature = _httpContext . Features . Get < IHttpSendFileFeature > ( ) ;
529542 if ( OriginalSendFileFeature != null )
530543 {
531- HttpContext . Features . Set < IHttpSendFileFeature > ( new SendFileFeatureWrapper ( OriginalSendFileFeature , ResponseCacheStream ) ) ;
544+ _httpContext . Features . Set < IHttpSendFileFeature > ( new SendFileFeatureWrapper ( OriginalSendFileFeature , ResponseCacheStream ) ) ;
532545 }
533546 }
534547
535548 internal void UnshimResponseStream ( )
536549 {
537550 // Unshim response stream
538- HttpContext . Response . Body = OriginalResponseStream ;
551+ _httpContext . Response . Body = OriginalResponseStream ;
539552
540553 // Unshim IHttpSendFileFeature
541- HttpContext . Features . Set ( OriginalSendFileFeature ) ;
554+ _httpContext . Features . Set ( OriginalSendFileFeature ) ;
542555 }
543556
544557 private enum ResponseType
0 commit comments