Performance optimizations
- Calculate age using operations on long - Compute content length of resposne on store - Format age using the new HeaderUtility - Lazily create HeaderDictionary - Use for instead of foreach to reduce allocations from enumerators
This commit is contained in:
parent
184c0f3c44
commit
8e8525512d
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
||||||
|
|
||||||
public int StatusCode { get; set; }
|
public int StatusCode { get; set; }
|
||||||
|
|
||||||
public IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
|
public IHeaderDictionary Headers { get; set; }
|
||||||
|
|
||||||
public Stream Body { get; set; }
|
public Stream Body { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -107,13 +107,18 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
||||||
builder.Append(KeyDelimiter)
|
builder.Append(KeyDelimiter)
|
||||||
.Append('H');
|
.Append('H');
|
||||||
|
|
||||||
foreach (var header in varyByRules.Headers)
|
for (var i = 0; i < varyByRules.Headers.Count; i++)
|
||||||
{
|
{
|
||||||
|
var header = varyByRules.Headers[i];
|
||||||
|
var headerValues = context.HttpContext.Request.Headers[header];
|
||||||
builder.Append(KeyDelimiter)
|
builder.Append(KeyDelimiter)
|
||||||
.Append(header)
|
.Append(header)
|
||||||
.Append("=")
|
.Append("=");
|
||||||
// TODO: Perf - iterate the string values instead?
|
|
||||||
.Append(context.HttpContext.Request.Headers[header]);
|
for (var j = 0; j < headerValues.Count; j++)
|
||||||
|
{
|
||||||
|
builder.Append(headerValues[j]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,19 +136,28 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
||||||
{
|
{
|
||||||
builder.Append(KeyDelimiter)
|
builder.Append(KeyDelimiter)
|
||||||
.AppendUpperInvariant(query.Key)
|
.AppendUpperInvariant(query.Key)
|
||||||
.Append("=")
|
.Append("=");
|
||||||
.Append(query.Value);
|
|
||||||
|
for (var i = 0; i < query.Value.Count; i++)
|
||||||
|
{
|
||||||
|
builder.Append(query.Value[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var queryKey in varyByRules.QueryKeys)
|
for (var i = 0; i < varyByRules.QueryKeys.Count; i++)
|
||||||
{
|
{
|
||||||
|
var queryKey = varyByRules.QueryKeys[i];
|
||||||
|
var queryKeyValues = context.HttpContext.Request.Query[queryKey];
|
||||||
builder.Append(KeyDelimiter)
|
builder.Append(KeyDelimiter)
|
||||||
.Append(queryKey)
|
.Append(queryKey)
|
||||||
.Append("=")
|
.Append("=");
|
||||||
// TODO: Perf - iterate the string values instead?
|
|
||||||
.Append(context.HttpContext.Request.Query[queryKey]);
|
for (var j = 0; j < queryKeyValues.Count; j++)
|
||||||
|
{
|
||||||
|
builder.Append(queryKeyValues[j]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
@ -142,18 +141,15 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
||||||
response.Headers.Add(header);
|
response.Headers.Add(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Headers[HeaderNames.Age] = context.CachedEntryAge.Value.TotalSeconds.ToString("F0", CultureInfo.InvariantCulture);
|
// Note: int64 division truncates result and errors may be up to 1 second. This reduction in
|
||||||
|
// accuracy of age calculation is considered appropriate since it is small compared to clock
|
||||||
|
// skews and the "Age" header is an estimate of the real age of cached content.
|
||||||
|
response.Headers[HeaderNames.Age] = HeaderUtilities.FormatInt64(context.CachedEntryAge.Value.Ticks / TimeSpan.TicksPerSecond);
|
||||||
|
|
||||||
// Copy the cached response body
|
// Copy the cached response body
|
||||||
var body = context.CachedResponse.Body;
|
var body = context.CachedResponse.Body;
|
||||||
if (body.Length > 0)
|
if (body.Length > 0)
|
||||||
{
|
{
|
||||||
// Add a content-length if required
|
|
||||||
if (!response.ContentLength.HasValue && StringValues.IsNullOrEmpty(response.Headers[HeaderNames.TransferEncoding]))
|
|
||||||
{
|
|
||||||
response.ContentLength = body.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await body.CopyToAsync(response.Body, StreamUtilities.BodySegmentSize, context.HttpContext.RequestAborted);
|
await body.CopyToAsync(response.Body, StreamUtilities.BodySegmentSize, context.HttpContext.RequestAborted);
|
||||||
|
|
@ -263,7 +259,8 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
||||||
context.CachedResponse = new CachedResponse
|
context.CachedResponse = new CachedResponse
|
||||||
{
|
{
|
||||||
Created = context.ResponseDate.Value,
|
Created = context.ResponseDate.Value,
|
||||||
StatusCode = context.HttpContext.Response.StatusCode
|
StatusCode = context.HttpContext.Response.StatusCode,
|
||||||
|
Headers = new HeaderDictionary()
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var header in context.HttpContext.Response.Headers)
|
foreach (var header in context.HttpContext.Response.Headers)
|
||||||
|
|
@ -288,6 +285,13 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
||||||
var bufferStream = context.ResponseCachingStream.GetBufferStream();
|
var bufferStream = context.ResponseCachingStream.GetBufferStream();
|
||||||
if (!contentLength.HasValue || contentLength == bufferStream.Length)
|
if (!contentLength.HasValue || contentLength == bufferStream.Length)
|
||||||
{
|
{
|
||||||
|
var response = context.HttpContext.Response;
|
||||||
|
// Add a content-length if required
|
||||||
|
if (!response.ContentLength.HasValue && StringValues.IsNullOrEmpty(response.Headers[HeaderNames.TransferEncoding]))
|
||||||
|
{
|
||||||
|
context.CachedResponse.Headers[HeaderNames.ContentLength] = HeaderUtilities.FormatInt64(bufferStream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
context.CachedResponse.Body = bufferStream;
|
context.CachedResponse.Body = bufferStream;
|
||||||
_logger.LogResponseCached();
|
_logger.LogResponseCached();
|
||||||
await _cache.SetAsync(context.StorageVaryKey ?? context.BaseKey, context.CachedResponse, context.CachedResponseValidFor);
|
await _cache.SetAsync(context.StorageVaryKey ?? context.BaseKey, context.CachedResponse, context.CachedResponseValidFor);
|
||||||
|
|
@ -371,8 +375,9 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
||||||
&& EntityTagHeaderValue.TryParse(cachedResponseHeaders[HeaderNames.ETag], out eTag)
|
&& EntityTagHeaderValue.TryParse(cachedResponseHeaders[HeaderNames.ETag], out eTag)
|
||||||
&& EntityTagHeaderValue.TryParseList(ifNoneMatchHeader, out ifNoneMatchEtags))
|
&& EntityTagHeaderValue.TryParseList(ifNoneMatchHeader, out ifNoneMatchEtags))
|
||||||
{
|
{
|
||||||
foreach (var requestETag in ifNoneMatchEtags)
|
for (var i = 0; i < ifNoneMatchEtags.Count; i++)
|
||||||
{
|
{
|
||||||
|
var requestETag = ifNoneMatchEtags[i];
|
||||||
if (eTag.Compare(requestETag, useStrongComparison: false))
|
if (eTag.Compare(requestETag, useStrongComparison: false))
|
||||||
{
|
{
|
||||||
context.Logger.LogNotModifiedIfNoneMatchMatched(requestETag);
|
context.Logger.LogNotModifiedIfNoneMatchMatched(requestETag);
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
"BaseKey",
|
"BaseKey",
|
||||||
new CachedResponse()
|
new CachedResponse()
|
||||||
{
|
{
|
||||||
|
Headers = new HeaderDictionary(),
|
||||||
Body = new SegmentReadStream(new List<byte[]>(0), 0)
|
Body = new SegmentReadStream(new List<byte[]>(0), 0)
|
||||||
},
|
},
|
||||||
TimeSpan.Zero);
|
TimeSpan.Zero);
|
||||||
|
|
@ -108,6 +109,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
"BaseKeyVaryKey2",
|
"BaseKeyVaryKey2",
|
||||||
new CachedResponse()
|
new CachedResponse()
|
||||||
{
|
{
|
||||||
|
Headers = new HeaderDictionary(),
|
||||||
Body = new SegmentReadStream(new List<byte[]>(0), 0)
|
Body = new SegmentReadStream(new List<byte[]>(0), 0)
|
||||||
},
|
},
|
||||||
TimeSpan.Zero);
|
TimeSpan.Zero);
|
||||||
|
|
@ -666,7 +668,10 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
await context.HttpContext.Response.WriteAsync(new string('0', 10));
|
await context.HttpContext.Response.WriteAsync(new string('0', 10));
|
||||||
|
|
||||||
context.ShouldCacheResponse = true;
|
context.ShouldCacheResponse = true;
|
||||||
context.CachedResponse = new CachedResponse();
|
context.CachedResponse = new CachedResponse()
|
||||||
|
{
|
||||||
|
Headers = new HeaderDictionary()
|
||||||
|
};
|
||||||
context.BaseKey = "BaseKey";
|
context.BaseKey = "BaseKey";
|
||||||
context.CachedResponseValidFor = TimeSpan.FromSeconds(10);
|
context.CachedResponseValidFor = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue