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

Commit c85d633

Browse files
committed
Use ObjectPool for StringBuilder
1 parent 544a044 commit c85d633

File tree

3 files changed

+101
-76
lines changed

3 files changed

+101
-76
lines changed

src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingContext.cs

Lines changed: 83 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,22 @@
1212
using Microsoft.AspNetCore.ResponseCaching.Internal;
1313
using Microsoft.Extensions.Primitives;
1414
using Microsoft.Net.Http.Headers;
15+
using Microsoft.Extensions.ObjectPool;
1516

1617
namespace 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

Comments
 (0)