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

Commit 9c94a77

Browse files
BrennanConroyJunTaoLuo
authored andcommitted
Improve header parsing performance
1 parent e823118 commit 9c94a77

File tree

8 files changed

+466
-299
lines changed

8 files changed

+466
-299
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNetCore.ResponseCaching.Internal
5+
{
6+
internal class CacheControlValues
7+
{
8+
public const string MaxAgeString = "max-age";
9+
public const string MaxStaleString = "max-stale";
10+
public const string MinFreshString = "min-fresh";
11+
public const string MustRevalidateString = "must-revalidate";
12+
public const string NoCacheString = "no-cache";
13+
public const string NoStoreString = "no-store";
14+
public const string NoTransformString = "no-transform";
15+
public const string OnlyIfCachedString = "only-if-cached";
16+
public const string PrivateString = "private";
17+
public const string ProxyRevalidateString = "proxy-revalidate";
18+
public const string PublicString = "public";
19+
public const string SharedMaxAgeString = "s-maxage";
20+
}
21+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Globalization;
6+
using Microsoft.Extensions.Primitives;
7+
8+
namespace Microsoft.AspNetCore.ResponseCaching.Internal
9+
{
10+
internal static class HttpHeaderParsingHelpers
11+
{
12+
private static readonly string[] DateFormats = new string[] {
13+
// "r", // RFC 1123, required output format but too strict for input
14+
"ddd, d MMM yyyy H:m:s 'GMT'", // RFC 1123 (r, except it allows both 1 and 01 for date and time)
15+
"ddd, d MMM yyyy H:m:s", // RFC 1123, no zone - assume GMT
16+
"d MMM yyyy H:m:s 'GMT'", // RFC 1123, no day-of-week
17+
"d MMM yyyy H:m:s", // RFC 1123, no day-of-week, no zone
18+
"ddd, d MMM yy H:m:s 'GMT'", // RFC 1123, short year
19+
"ddd, d MMM yy H:m:s", // RFC 1123, short year, no zone
20+
"d MMM yy H:m:s 'GMT'", // RFC 1123, no day-of-week, short year
21+
"d MMM yy H:m:s", // RFC 1123, no day-of-week, short year, no zone
22+
23+
"dddd, d'-'MMM'-'yy H:m:s 'GMT'", // RFC 850
24+
"dddd, d'-'MMM'-'yy H:m:s", // RFC 850 no zone
25+
"ddd MMM d H:m:s yyyy", // ANSI C's asctime() format
26+
27+
"ddd, d MMM yyyy H:m:s zzz", // RFC 5322
28+
"ddd, d MMM yyyy H:m:s", // RFC 5322 no zone
29+
"d MMM yyyy H:m:s zzz", // RFC 5322 no day-of-week
30+
"d MMM yyyy H:m:s", // RFC 5322 no day-of-week, no zone
31+
};
32+
33+
// Try the various date formats in the order listed above.
34+
// We should accept a wide verity of common formats, but only output RFC 1123 style dates.
35+
internal static bool TryParseHeaderDate(string input, out DateTimeOffset result) => DateTimeOffset.TryParseExact(input, DateFormats, DateTimeFormatInfo.InvariantInfo,
36+
DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal, out result);
37+
38+
// Try to get the value of a specific header from a list of headers
39+
// e.g. "header1=10, header2=30"
40+
internal static bool TryParseHeaderTimeSpan(StringValues headers, string headerName, out TimeSpan? value)
41+
{
42+
foreach (var header in headers)
43+
{
44+
var index = header.IndexOf(headerName, StringComparison.OrdinalIgnoreCase);
45+
if (index != -1)
46+
{
47+
index += headerName.Length;
48+
int seconds;
49+
if (!TryParseHeaderInt(index, header, out seconds))
50+
{
51+
break;
52+
}
53+
value = TimeSpan.FromSeconds(seconds);
54+
return true;
55+
}
56+
}
57+
value = null;
58+
return false;
59+
}
60+
61+
internal static bool HeaderContains(StringValues headers, string headerName)
62+
{
63+
foreach (var header in headers)
64+
{
65+
var index = header.IndexOf(headerName, StringComparison.OrdinalIgnoreCase);
66+
if (index != -1)
67+
{
68+
return true;
69+
}
70+
}
71+
72+
return false;
73+
}
74+
75+
private static bool TryParseHeaderInt(int startIndex, string header, out int value)
76+
{
77+
var found = false;
78+
while (startIndex != header.Length)
79+
{
80+
var c = header[startIndex];
81+
if (c == '=')
82+
{
83+
found = true;
84+
}
85+
else if (c != ' ')
86+
{
87+
--startIndex;
88+
break;
89+
}
90+
++startIndex;
91+
}
92+
if (found && startIndex != header.Length)
93+
{
94+
var endIndex = startIndex + 1;
95+
while (endIndex < header.Length)
96+
{
97+
var c = header[endIndex];
98+
if ((c >= '0') && (c <= '9'))
99+
{
100+
endIndex++;
101+
}
102+
else
103+
{
104+
break;
105+
}
106+
}
107+
var length = endIndex - (startIndex + 1);
108+
if (length > 0)
109+
{
110+
value = int.Parse(header.Substring(startIndex + 1, length), NumberStyles.None, NumberFormatInfo.InvariantInfo);
111+
return true;
112+
}
113+
}
114+
value = 0;
115+
return false;
116+
}
117+
}
118+
}

src/Microsoft.AspNetCore.ResponseCaching/Internal/ResponseCachingContext.cs

Lines changed: 45 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,21 @@
55
using System.IO;
66
using Microsoft.AspNetCore.Http;
77
using Microsoft.AspNetCore.Http.Features;
8-
using Microsoft.AspNetCore.Http.Headers;
98
using Microsoft.Extensions.Logging;
109
using Microsoft.Net.Http.Headers;
1110

1211
namespace Microsoft.AspNetCore.ResponseCaching.Internal
1312
{
1413
public class ResponseCachingContext
1514
{
16-
private static readonly CacheControlHeaderValue EmptyCacheControl = new CacheControlHeaderValue();
17-
18-
private RequestHeaders _requestHeaders;
19-
private ResponseHeaders _responseHeaders;
20-
private CacheControlHeaderValue _requestCacheControl;
21-
private CacheControlHeaderValue _responseCacheControl;
2215
private DateTimeOffset? _responseDate;
2316
private bool _parsedResponseDate;
2417
private DateTimeOffset? _responseExpires;
2518
private bool _parsedResponseExpires;
19+
private TimeSpan? _responseSharedMaxAge;
20+
private bool _parsedResponseSharedMaxAge;
21+
private TimeSpan? _responseMaxAge;
22+
private bool _parsedResponseMaxAge;
2623

2724
internal ResponseCachingContext(HttpContext httpContext, ILogger logger)
2825
{
@@ -58,85 +55,79 @@ internal ResponseCachingContext(HttpContext httpContext, ILogger logger)
5855

5956
internal IHttpSendFileFeature OriginalSendFileFeature { get; set; }
6057

61-
internal ResponseHeaders CachedResponseHeaders { get; set; }
58+
internal IHeaderDictionary CachedResponseHeaders { get; set; }
6259

63-
internal RequestHeaders TypedRequestHeaders
64-
{
65-
get
66-
{
67-
if (_requestHeaders == null)
68-
{
69-
_requestHeaders = HttpContext.Request.GetTypedHeaders();
70-
}
71-
return _requestHeaders;
72-
}
73-
}
74-
75-
internal ResponseHeaders TypedResponseHeaders
60+
internal DateTimeOffset? ResponseDate
7661
{
7762
get
7863
{
79-
if (_responseHeaders == null)
64+
if (!_parsedResponseDate)
8065
{
81-
_responseHeaders = HttpContext.Response.GetTypedHeaders();
66+
_parsedResponseDate = true;
67+
DateTimeOffset date;
68+
if (HttpHeaderParsingHelpers.TryParseHeaderDate(HttpContext.Response.Headers[HeaderNames.Date], out date))
69+
{
70+
_responseDate = date;
71+
}
72+
else
73+
{
74+
_responseDate = null;
75+
}
8276
}
83-
return _responseHeaders;
77+
return _responseDate;
8478
}
85-
}
86-
87-
internal CacheControlHeaderValue RequestCacheControlHeaderValue
88-
{
89-
get
79+
set
9080
{
91-
if (_requestCacheControl == null)
92-
{
93-
_requestCacheControl = TypedRequestHeaders.CacheControl ?? EmptyCacheControl;
94-
}
95-
return _requestCacheControl;
81+
// Don't reparse the response date again if it's explicitly set
82+
_parsedResponseDate = true;
83+
_responseDate = value;
9684
}
9785
}
9886

99-
internal CacheControlHeaderValue ResponseCacheControlHeaderValue
87+
internal DateTimeOffset? ResponseExpires
10088
{
10189
get
10290
{
103-
if (_responseCacheControl == null)
91+
if (!_parsedResponseExpires)
10492
{
105-
_responseCacheControl = TypedResponseHeaders.CacheControl ?? EmptyCacheControl;
93+
_parsedResponseExpires = true;
94+
DateTimeOffset expires;
95+
if (HttpHeaderParsingHelpers.TryParseHeaderDate(HttpContext.Response.Headers[HeaderNames.Expires], out expires))
96+
{
97+
_responseExpires = expires;
98+
}
99+
else
100+
{
101+
_responseExpires = null;
102+
}
106103
}
107-
return _responseCacheControl;
104+
return _responseExpires;
108105
}
109106
}
110107

111-
internal DateTimeOffset? ResponseDate
108+
internal TimeSpan? ResponseSharedMaxAge
112109
{
113110
get
114111
{
115-
if (!_parsedResponseDate)
112+
if (!_parsedResponseSharedMaxAge)
116113
{
117-
_parsedResponseDate = true;
118-
_responseDate = TypedResponseHeaders.Date;
114+
_parsedResponseSharedMaxAge = true;
115+
HttpHeaderParsingHelpers.TryParseHeaderTimeSpan(HttpContext.Response.Headers[HeaderNames.CacheControl], CacheControlValues.SharedMaxAgeString, out _responseSharedMaxAge);
119116
}
120-
return _responseDate;
121-
}
122-
set
123-
{
124-
// Don't reparse the response date again if it's explicitly set
125-
_parsedResponseDate = true;
126-
_responseDate = value;
117+
return _responseSharedMaxAge;
127118
}
128119
}
129120

130-
internal DateTimeOffset? ResponseExpires
121+
internal TimeSpan? ResponseMaxAge
131122
{
132123
get
133124
{
134-
if (!_parsedResponseExpires)
125+
if (!_parsedResponseMaxAge)
135126
{
136-
_parsedResponseExpires = true;
137-
_responseExpires = TypedResponseHeaders.Expires;
127+
_parsedResponseMaxAge = true;
128+
HttpHeaderParsingHelpers.TryParseHeaderTimeSpan(HttpContext.Response.Headers[HeaderNames.CacheControl], CacheControlValues.MaxAgeString, out _responseMaxAge);
138129
}
139-
return _responseExpires;
130+
return _responseMaxAge;
140131
}
141132
}
142133
}

0 commit comments

Comments
 (0)