diff --git a/src/Http/Headers/src/HeaderNames.cs b/src/Http/Headers/src/HeaderNames.cs
index 1355909d6d85..ca48a8ab0e82 100644
--- a/src/Http/Headers/src/HeaderNames.cs
+++ b/src/Http/Headers/src/HeaderNames.cs
@@ -166,6 +166,9 @@ public static class HeaderNames
/// Gets the Last-Modified HTTP header name.
public static readonly string LastModified = "Last-Modified";
+ /// Gets the Link HTTP header name.
+ public static readonly string Link = "Link";
+
/// Gets the Location HTTP header name.
public static readonly string Location = "Location";
@@ -274,10 +277,16 @@ public static class HeaderNames
/// Gets the WWW-Authenticate HTTP header name.
public static readonly string WWWAuthenticate = "WWW-Authenticate";
+ /// Gets the X-Content-Type-Options HTTP header name.
+ public static readonly string XContentTypeOptions = "X-Content-Type-Options";
+
/// Gets the X-Frame-Options HTTP header name.
public static readonly string XFrameOptions = "X-Frame-Options";
/// Gets the X-Requested-With HTTP header name.
public static readonly string XRequestedWith = "X-Requested-With";
+
+ /// Gets the X-XSS-Protection HTTP header name.
+ public static readonly string XXssProtection = "X-XSS-Protection";
}
}
diff --git a/src/Http/Headers/src/PublicAPI.Unshipped.txt b/src/Http/Headers/src/PublicAPI.Unshipped.txt
index be7e9574bc0a..d7014272fd7c 100644
--- a/src/Http/Headers/src/PublicAPI.Unshipped.txt
+++ b/src/Http/Headers/src/PublicAPI.Unshipped.txt
@@ -3,4 +3,7 @@
Microsoft.Net.Http.Headers.MediaTypeHeaderValue.MatchesMediaType(Microsoft.Extensions.Primitives.StringSegment otherMediaType) -> bool
Microsoft.Net.Http.Headers.RangeConditionHeaderValue.RangeConditionHeaderValue(Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> void
static readonly Microsoft.Net.Http.Headers.HeaderNames.Baggage -> string!
+static readonly Microsoft.Net.Http.Headers.HeaderNames.Link -> string!
static readonly Microsoft.Net.Http.Headers.HeaderNames.ProxyConnection -> string!
+static readonly Microsoft.Net.Http.Headers.HeaderNames.XContentTypeOptions -> string!
+static readonly Microsoft.Net.Http.Headers.HeaderNames.XXssProtection -> string!
diff --git a/src/Middleware/WebSockets/src/Constants.cs b/src/Middleware/WebSockets/src/Constants.cs
index 6208d4914a92..3757d3a344ab 100644
--- a/src/Middleware/WebSockets/src/Constants.cs
+++ b/src/Middleware/WebSockets/src/Constants.cs
@@ -6,10 +6,9 @@ namespace Microsoft.AspNetCore.WebSockets
internal static class Constants
{
public static class Headers
- {
- public const string UpgradeWebSocket = "websocket";
- public const string ConnectionUpgrade = "Upgrade";
- public const string SupportedVersion = "13";
+ {
+ public readonly static string UpgradeWebSocket = "websocket";
+ public readonly static string SupportedVersion = "13";
}
}
}
diff --git a/src/Middleware/WebSockets/src/HandshakeHelpers.cs b/src/Middleware/WebSockets/src/HandshakeHelpers.cs
index f18de60e0e30..4ecc685090c6 100644
--- a/src/Middleware/WebSockets/src/HandshakeHelpers.cs
+++ b/src/Middleware/WebSockets/src/HandshakeHelpers.cs
@@ -34,7 +34,7 @@ internal static class HandshakeHelpers
};
// Verify Method, Upgrade, Connection, version, key, etc..
- public static bool CheckSupportedWebSocketRequest(string method, List> headers)
+ public static bool CheckSupportedWebSocketRequest(string method, List> interestingHeaders, IHeaderDictionary requestHeaders)
{
bool validUpgrade = false, validConnection = false, validKey = false, validVersion = false;
@@ -43,11 +43,11 @@ public static bool CheckSupportedWebSocketRequest(string method, List>();
- foreach (string headerName in HandshakeHelpers.NeededHeaders)
+ var requestHeaders = _context.Request.Headers;
+ var interestingHeaders = new List>();
+ foreach (var headerName in HandshakeHelpers.NeededHeaders)
{
- foreach (var value in _context.Request.Headers.GetCommaSeparatedValues(headerName))
+ foreach (var value in requestHeaders.GetCommaSeparatedValues(headerName))
{
- headers.Add(new KeyValuePair(headerName, value));
+ interestingHeaders.Add(new KeyValuePair(headerName, value));
}
}
- _isWebSocketRequest = HandshakeHelpers.CheckSupportedWebSocketRequest(_context.Request.Method, headers);
+ _isWebSocketRequest = HandshakeHelpers.CheckSupportedWebSocketRequest(_context.Request.Method, interestingHeaders, requestHeaders);
}
}
return _isWebSocketRequest.Value;
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs
index 4e2a87ec10c0..5bee9a25b90a 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs
@@ -128,7 +128,7 @@ public static MessageBody For(
if (headers.HasConnection)
{
- var connectionOptions = HttpHeaders.ParseConnection(headers.HeaderConnection);
+ var connectionOptions = HttpHeaders.ParseConnection(headers);
upgrade = (connectionOptions & ConnectionOptions.Upgrade) != 0;
keepAlive = keepAlive || (connectionOptions & ConnectionOptions.KeepAlive) != 0;
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs
index d8ad519873de..6bba7c9af45a 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs
@@ -99,6 +99,106 @@ internal enum KnownHeaderType
WWWAuthenticate,
}
+ internal partial class HttpHeaders
+ {
+ private readonly static HashSet _internedHeaderNames = new HashSet(93, StringComparer.OrdinalIgnoreCase)
+ {
+ HeaderNames.Accept,
+ HeaderNames.AcceptCharset,
+ HeaderNames.AcceptEncoding,
+ HeaderNames.AcceptLanguage,
+ HeaderNames.AcceptRanges,
+ HeaderNames.AccessControlAllowCredentials,
+ HeaderNames.AccessControlAllowHeaders,
+ HeaderNames.AccessControlAllowMethods,
+ HeaderNames.AccessControlAllowOrigin,
+ HeaderNames.AccessControlExposeHeaders,
+ HeaderNames.AccessControlMaxAge,
+ HeaderNames.AccessControlRequestHeaders,
+ HeaderNames.AccessControlRequestMethod,
+ HeaderNames.Age,
+ HeaderNames.Allow,
+ HeaderNames.AltSvc,
+ HeaderNames.Authority,
+ HeaderNames.Authorization,
+ HeaderNames.Baggage,
+ HeaderNames.CacheControl,
+ HeaderNames.Connection,
+ HeaderNames.ContentDisposition,
+ HeaderNames.ContentEncoding,
+ HeaderNames.ContentLanguage,
+ HeaderNames.ContentLength,
+ HeaderNames.ContentLocation,
+ HeaderNames.ContentMD5,
+ HeaderNames.ContentRange,
+ HeaderNames.ContentSecurityPolicy,
+ HeaderNames.ContentSecurityPolicyReportOnly,
+ HeaderNames.ContentType,
+ HeaderNames.CorrelationContext,
+ HeaderNames.Cookie,
+ HeaderNames.Date,
+ HeaderNames.DNT,
+ HeaderNames.ETag,
+ HeaderNames.Expires,
+ HeaderNames.Expect,
+ HeaderNames.From,
+ HeaderNames.GrpcAcceptEncoding,
+ HeaderNames.GrpcEncoding,
+ HeaderNames.GrpcMessage,
+ HeaderNames.GrpcStatus,
+ HeaderNames.GrpcTimeout,
+ HeaderNames.Host,
+ HeaderNames.KeepAlive,
+ HeaderNames.IfMatch,
+ HeaderNames.IfModifiedSince,
+ HeaderNames.IfNoneMatch,
+ HeaderNames.IfRange,
+ HeaderNames.IfUnmodifiedSince,
+ HeaderNames.LastModified,
+ HeaderNames.Link,
+ HeaderNames.Location,
+ HeaderNames.MaxForwards,
+ HeaderNames.Method,
+ HeaderNames.Origin,
+ HeaderNames.Path,
+ HeaderNames.Pragma,
+ HeaderNames.ProxyAuthenticate,
+ HeaderNames.ProxyAuthorization,
+ HeaderNames.ProxyConnection,
+ HeaderNames.Range,
+ HeaderNames.Referer,
+ HeaderNames.RetryAfter,
+ HeaderNames.RequestId,
+ HeaderNames.Scheme,
+ HeaderNames.SecWebSocketAccept,
+ HeaderNames.SecWebSocketKey,
+ HeaderNames.SecWebSocketProtocol,
+ HeaderNames.SecWebSocketVersion,
+ HeaderNames.Server,
+ HeaderNames.SetCookie,
+ HeaderNames.Status,
+ HeaderNames.StrictTransportSecurity,
+ HeaderNames.TE,
+ HeaderNames.Trailer,
+ HeaderNames.TransferEncoding,
+ HeaderNames.Translate,
+ HeaderNames.TraceParent,
+ HeaderNames.TraceState,
+ HeaderNames.Upgrade,
+ HeaderNames.UpgradeInsecureRequests,
+ HeaderNames.UserAgent,
+ HeaderNames.Vary,
+ HeaderNames.Via,
+ HeaderNames.Warning,
+ HeaderNames.WebSocketSubProtocols,
+ HeaderNames.WWWAuthenticate,
+ HeaderNames.XContentTypeOptions,
+ HeaderNames.XFrameOptions,
+ HeaderNames.XRequestedWith,
+ HeaderNames.XXssProtection,
+ };
+ }
+
internal partial class HttpRequestHeaders
{
private HeaderReferences _headers;
@@ -125,7 +225,7 @@ public StringValues HeaderCacheControl
_headers._CacheControl = value;
}
}
- public StringValues HeaderConnection
+ public override StringValues HeaderConnection
{
get
{
@@ -8129,7 +8229,7 @@ public StringValues HeaderCacheControl
_headers._CacheControl = value;
}
}
- public StringValues HeaderConnection
+ public override StringValues HeaderConnection
{
get
{
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs
index e7912791bd00..c823ff2ef829 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.cs
@@ -12,16 +12,17 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
- internal abstract class HttpHeaders : IHeaderDictionary
+ internal abstract partial class HttpHeaders : IHeaderDictionary
{
protected long _bits = 0;
protected long? _contentLength;
protected bool _isReadOnly;
protected Dictionary? MaybeUnknown;
- protected Dictionary Unknown => MaybeUnknown ?? (MaybeUnknown = new Dictionary(StringComparer.OrdinalIgnoreCase));
+ protected Dictionary Unknown => MaybeUnknown ??= new Dictionary(StringComparer.OrdinalIgnoreCase);
public long? ContentLength
{
@@ -40,6 +41,8 @@ public long? ContentLength
}
}
+ public abstract StringValues HeaderConnection { get; set; }
+
StringValues IHeaderDictionary.this[string key]
{
get
@@ -126,6 +129,18 @@ public void Reset()
ClearFast();
}
+ protected static string GetInternedHeaderName(string name)
+ {
+ // Some headers can be very long lived; for example those on a WebSocket connection
+ // so we exchange these for the preallocated strings predefined in HeaderNames
+ if (_internedHeaderNames.TryGetValue(name, out var internedName))
+ {
+ return internedName;
+ }
+
+ return name;
+ }
+
[MethodImpl(MethodImplOptions.NoInlining)]
protected static StringValues AppendValue(StringValues existing, string append)
{
@@ -276,7 +291,12 @@ public static void ValidateHeaderNameCharacters(string headerCharacters)
}
}
- public static ConnectionOptions ParseConnection(StringValues connection)
+ private readonly static string KeepAlive = "keep-alive";
+ private readonly static StringValues ConnectionValueKeepAlive = KeepAlive;
+ private readonly static StringValues ConnectionValueClose = "close";
+ private readonly static StringValues ConnectionValueUpgrade = HeaderNames.Upgrade;
+
+ public static ConnectionOptions ParseConnection(HttpHeaders headers)
{
// Keep-alive
const ulong lowerCaseKeep = 0x0000_0020_0020_0020; // Don't lowercase hyphen
@@ -289,9 +309,27 @@ public static ConnectionOptions ParseConnection(StringValues connection)
// Close
const ulong closeEnd = 0x0065_0073_006f_006c; // 4 chars "lose"
+ var connection = headers.HeaderConnection;
+ var connectionCount = connection.Count;
+ if (connectionCount == 0)
+ {
+ return ConnectionOptions.None;
+ }
+
var connectionOptions = ConnectionOptions.None;
- var connectionCount = connection.Count;
+ if (connectionCount == 1)
+ {
+ // "keep-alive" is the only value that will be repeated over
+ // many requests on the same connection; on the first request
+ // we will have switched it for the readonly static value;
+ // so we can ptentially short-circuit parsing and use ReferenceEquals.
+ if (ReferenceEquals(connection.ToString(), KeepAlive))
+ {
+ return ConnectionOptions.KeepAlive;
+ }
+ }
+
for (var i = 0; i < connectionCount; i++)
{
var value = connection[i].AsSpan();
@@ -420,6 +458,21 @@ public static ConnectionOptions ParseConnection(StringValues connection)
}
}
+ // If Connection is a single value, switch it for the interned value
+ // in case the connection is long lived
+ if (connectionOptions == ConnectionOptions.Upgrade)
+ {
+ headers.HeaderConnection = ConnectionValueUpgrade;
+ }
+ else if (connectionOptions == ConnectionOptions.KeepAlive)
+ {
+ headers.HeaderConnection = ConnectionValueKeepAlive;
+ }
+ else if (connectionOptions == ConnectionOptions.Close)
+ {
+ headers.HeaderConnection = ConnectionValueClose;
+ }
+
return connectionOptions;
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs
index 71e9ed2047a6..9c10f3f4afb5 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs
@@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Collections.Generic;
-using System.Diagnostics;
using System.IO;
using System.IO.Pipelines;
using System.Net;
@@ -309,7 +307,7 @@ async Task IHttpUpgradeFeature.UpgradeAsync()
StatusCode = StatusCodes.Status101SwitchingProtocols;
ReasonPhrase = "Switching Protocols";
- ResponseHeaders[HeaderNames.Connection] = "Upgrade";
+ ResponseHeaders[HeaderNames.Connection] = HeaderNames.Upgrade;
await FlushAsync();
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs
index bf856a53c4c8..2f575865930b 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs
@@ -1122,7 +1122,7 @@ private HttpResponseHeaders CreateResponseHeaders(bool appCompleted)
if (_keepAlive &&
hasConnection &&
- (HttpHeaders.ParseConnection(responseHeaders.HeaderConnection) & ConnectionOptions.KeepAlive) == 0)
+ (HttpHeaders.ParseConnection(responseHeaders) & ConnectionOptions.KeepAlive) == 0)
{
_keepAlive = false;
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs
index 05e017cbfea1..b8833b3051bc 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs
@@ -121,13 +121,13 @@ private void AppendContentLengthCustomEncoding(ReadOnlySpan value, Encodin
[MethodImpl(MethodImplOptions.NoInlining)]
private void SetValueUnknown(string key, StringValues value)
{
- Unknown[key] = value;
+ Unknown[GetInternedHeaderName(key)] = value;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private bool AddValueUnknown(string key, StringValues value)
{
- Unknown.Add(key, value);
+ Unknown.Add(GetInternedHeaderName(key), value);
// Return true, above will throw and exit for false
return true;
}
@@ -135,6 +135,7 @@ private bool AddValueUnknown(string key, StringValues value)
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe void AppendUnknownHeaders(string name, string valueString)
{
+ name = GetInternedHeaderName(name);
Unknown.TryGetValue(name, out var existing);
Unknown[name] = AppendValue(existing, valueString);
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs
index efc4dc6bc8bc..bdefaf8e9861 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs
@@ -76,14 +76,14 @@ private static void ThrowInvalidContentLengthException(string value)
private void SetValueUnknown(string key, StringValues value)
{
ValidateHeaderNameCharacters(key);
- Unknown[key] = value;
+ Unknown[GetInternedHeaderName(key)] = value;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private bool AddValueUnknown(string key, StringValues value)
{
ValidateHeaderNameCharacters(key);
- Unknown.Add(key, value);
+ Unknown.Add(GetInternedHeaderName(key), value);
// Return true, above will throw and exit for false
return true;
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseTrailers.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseTrailers.cs
index 49948877d6cb..3fb3cef5e385 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseTrailers.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseTrailers.cs
@@ -25,18 +25,20 @@ protected override IEnumerator> GetEnumerator
private void SetValueUnknown(string key, StringValues value)
{
ValidateHeaderNameCharacters(key);
- Unknown[key] = value;
+ Unknown[GetInternedHeaderName(key)] = value;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private bool AddValueUnknown(string key, StringValues value)
{
ValidateHeaderNameCharacters(key);
- Unknown.Add(key, value);
+ Unknown.Add(GetInternedHeaderName(key), value);
// Return true, above will throw and exit for false
return true;
}
+ public override StringValues HeaderConnection { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
+
public partial struct Enumerator : IEnumerator>
{
private readonly HttpResponseTrailers _collection;
diff --git a/src/Servers/Kestrel/Core/test/HttpHeadersTests.cs b/src/Servers/Kestrel/Core/test/HttpHeadersTests.cs
index 092e2d47b75a..c13062318439 100644
--- a/src/Servers/Kestrel/Core/test/HttpHeadersTests.cs
+++ b/src/Servers/Kestrel/Core/test/HttpHeadersTests.cs
@@ -135,7 +135,9 @@ public class HttpHeadersTests
public void TestParseConnection(string connection, int intExpectedConnectionOptions)
{
var expectedConnectionOptions = (ConnectionOptions)intExpectedConnectionOptions;
- var connectionOptions = HttpHeaders.ParseConnection(connection);
+ var requestHeaders = new HttpRequestHeaders();
+ requestHeaders.HeaderConnection = connection;
+ var connectionOptions = HttpHeaders.ParseConnection(requestHeaders);
Assert.Equal(expectedConnectionOptions, connectionOptions);
}
@@ -159,7 +161,9 @@ public void TestParseConnectionMultipleValues(string value1, string value2, int
{
var expectedConnectionOptions = (ConnectionOptions)intExpectedConnectionOptions;
var connection = new StringValues(new[] { value1, value2 });
- var connectionOptions = HttpHeaders.ParseConnection(connection);
+ var requestHeaders = new HttpRequestHeaders();
+ requestHeaders.HeaderConnection = connection;
+ var connectionOptions = HttpHeaders.ParseConnection(requestHeaders);
Assert.Equal(expectedConnectionOptions, connectionOptions);
}
diff --git a/src/Servers/Kestrel/shared/KnownHeaders.cs b/src/Servers/Kestrel/shared/KnownHeaders.cs
index e6541998aa29..e8b1d010df78 100644
--- a/src/Servers/Kestrel/shared/KnownHeaders.cs
+++ b/src/Servers/Kestrel/shared/KnownHeaders.cs
@@ -7,7 +7,9 @@
using System.Globalization;
using System.Linq;
using System.Net.Http.HPack;
+using System.Reflection;
using System.Text;
+using Microsoft.Net.Http.Headers;
namespace CodeGenerator
{
@@ -22,94 +24,94 @@ static KnownHeaders()
{
var requestPrimaryHeaders = new[]
{
- "Accept",
- "Connection",
- "Host",
- "User-Agent"
+ HeaderNames.Accept,
+ HeaderNames.Connection,
+ HeaderNames.Host,
+ HeaderNames.UserAgent
};
var responsePrimaryHeaders = new[]
{
- "Connection",
- "Date",
- "Content-Type",
- "Server",
- "Content-Length",
+ HeaderNames.Connection,
+ HeaderNames.Date,
+ HeaderNames.ContentType,
+ HeaderNames.Server,
+ HeaderNames.ContentLength,
};
var commonHeaders = new[]
{
- "Cache-Control",
- "Connection",
- "Date",
- "Grpc-Encoding",
- "Keep-Alive",
- "Pragma",
- "Trailer",
- "Transfer-Encoding",
- "Upgrade",
- "Via",
- "Warning",
- "Allow",
- "Content-Type",
- "Content-Encoding",
- "Content-Language",
- "Content-Location",
- "Content-MD5",
- "Content-Range",
- "Expires",
- "Last-Modified"
+ HeaderNames.CacheControl,
+ HeaderNames.Connection,
+ HeaderNames.Date,
+ HeaderNames.GrpcEncoding,
+ HeaderNames.KeepAlive,
+ HeaderNames.Pragma,
+ HeaderNames.Trailer,
+ HeaderNames.TransferEncoding,
+ HeaderNames.Upgrade,
+ HeaderNames.Via,
+ HeaderNames.Warning,
+ HeaderNames.Allow,
+ HeaderNames.ContentType,
+ HeaderNames.ContentEncoding,
+ HeaderNames.ContentLanguage,
+ HeaderNames.ContentLocation,
+ HeaderNames.ContentMD5,
+ HeaderNames.ContentRange,
+ HeaderNames.Expires,
+ HeaderNames.LastModified
};
// http://www.w3.org/TR/cors/#syntax
var corsRequestHeaders = new[]
{
- "Origin",
- "Access-Control-Request-Method",
- "Access-Control-Request-Headers",
+ HeaderNames.Origin,
+ HeaderNames.AccessControlRequestMethod,
+ HeaderNames.AccessControlRequestHeaders,
};
var requestHeadersExistence = new[]
{
- "Connection",
- "Transfer-Encoding",
+ HeaderNames.Connection,
+ HeaderNames.TransferEncoding,
};
var requestHeadersCount = new[]
{
- "Host"
+ HeaderNames.Host
};
RequestHeaders = commonHeaders.Concat(new[]
{
- ":authority",
- ":method",
- ":path",
- ":scheme",
- "Accept",
- "Accept-Charset",
- "Accept-Encoding",
- "Accept-Language",
- "Authorization",
- "Cookie",
- "Expect",
- "From",
- "Grpc-Accept-Encoding",
- "Grpc-Timeout",
- "Host",
- "If-Match",
- "If-Modified-Since",
- "If-None-Match",
- "If-Range",
- "If-Unmodified-Since",
- "Max-Forwards",
- "Proxy-Authorization",
- "Referer",
- "Range",
- "TE",
- "Translate",
- "User-Agent",
- "DNT",
- "Upgrade-Insecure-Requests",
- "Request-Id",
- "Correlation-Context",
- "TraceParent",
- "TraceState",
- "Baggage"
+ HeaderNames.Authority,
+ HeaderNames.Method,
+ HeaderNames.Path,
+ HeaderNames.Scheme,
+ HeaderNames.Accept,
+ HeaderNames.AcceptCharset,
+ HeaderNames.AcceptEncoding,
+ HeaderNames.AcceptLanguage,
+ HeaderNames.Authorization,
+ HeaderNames.Cookie,
+ HeaderNames.Expect,
+ HeaderNames.From,
+ HeaderNames.GrpcAcceptEncoding,
+ HeaderNames.GrpcTimeout,
+ HeaderNames.Host,
+ HeaderNames.IfMatch,
+ HeaderNames.IfModifiedSince,
+ HeaderNames.IfNoneMatch,
+ HeaderNames.IfRange,
+ HeaderNames.IfUnmodifiedSince,
+ HeaderNames.MaxForwards,
+ HeaderNames.ProxyAuthorization,
+ HeaderNames.Referer,
+ HeaderNames.Range,
+ HeaderNames.TE,
+ HeaderNames.Translate,
+ HeaderNames.UserAgent,
+ HeaderNames.DNT,
+ HeaderNames.UpgradeInsecureRequests,
+ HeaderNames.RequestId,
+ HeaderNames.CorrelationContext,
+ HeaderNames.TraceParent,
+ HeaderNames.TraceState,
+ HeaderNames.Baggage
})
.Concat(corsRequestHeaders)
.Select((header, index) => new KnownHeader
@@ -122,50 +124,50 @@ static KnownHeaders()
})
.Concat(new[] { new KnownHeader
{
- Name = "Content-Length",
+ Name = HeaderNames.ContentLength,
Index = -1,
- PrimaryHeader = requestPrimaryHeaders.Contains("Content-Length")
+ PrimaryHeader = requestPrimaryHeaders.Contains(HeaderNames.ContentLength)
}})
.ToArray();
var responseHeadersExistence = new[]
{
- "Connection",
- "Server",
- "Date",
- "Transfer-Encoding"
+ HeaderNames.Connection,
+ HeaderNames.Server,
+ HeaderNames.Date,
+ HeaderNames.TransferEncoding
};
var enhancedHeaders = new[]
{
- "Connection",
- "Server",
- "Date",
- "Transfer-Encoding"
+ HeaderNames.Connection,
+ HeaderNames.Server,
+ HeaderNames.Date,
+ HeaderNames.TransferEncoding
};
// http://www.w3.org/TR/cors/#syntax
var corsResponseHeaders = new[]
{
- "Access-Control-Allow-Credentials",
- "Access-Control-Allow-Headers",
- "Access-Control-Allow-Methods",
- "Access-Control-Allow-Origin",
- "Access-Control-Expose-Headers",
- "Access-Control-Max-Age",
+ HeaderNames.AccessControlAllowCredentials,
+ HeaderNames.AccessControlAllowHeaders,
+ HeaderNames.AccessControlAllowMethods,
+ HeaderNames.AccessControlAllowOrigin,
+ HeaderNames.AccessControlExposeHeaders,
+ HeaderNames.AccessControlMaxAge,
};
ResponseHeaders = commonHeaders.Concat(new[]
{
- "Accept-Ranges",
- "Age",
- "Alt-Svc",
- "ETag",
- "Location",
- "Proxy-Authenticate",
- "Proxy-Connection",
- "Retry-After",
- "Server",
- "Set-Cookie",
- "Vary",
- "WWW-Authenticate",
+ HeaderNames.AcceptRanges,
+ HeaderNames.Age,
+ HeaderNames.AltSvc,
+ HeaderNames.ETag,
+ HeaderNames.Location,
+ HeaderNames.ProxyAuthenticate,
+ HeaderNames.ProxyConnection,
+ HeaderNames.RetryAfter,
+ HeaderNames.Server,
+ HeaderNames.SetCookie,
+ HeaderNames.Vary,
+ HeaderNames.WWWAuthenticate,
})
.Concat(corsResponseHeaders)
.Select((header, index) => new KnownHeader
@@ -178,18 +180,18 @@ static KnownHeaders()
})
.Concat(new[] { new KnownHeader
{
- Name = "Content-Length",
+ Name = HeaderNames.ContentLength,
Index = 63,
- EnhancedSetter = enhancedHeaders.Contains("Content-Length"),
- PrimaryHeader = responsePrimaryHeaders.Contains("Content-Length")
+ EnhancedSetter = enhancedHeaders.Contains(HeaderNames.ContentLength),
+ PrimaryHeader = responsePrimaryHeaders.Contains(HeaderNames.ContentLength)
}})
.ToArray();
ResponseTrailers = new[]
{
- "ETag",
- "Grpc-Message",
- "Grpc-Status"
+ HeaderNames.ETag,
+ HeaderNames.GrpcMessage,
+ HeaderNames.GrpcStatus
}
.Select((header, index) => new KnownHeader
{
@@ -203,11 +205,11 @@ static KnownHeaders()
var invalidH2H3ResponseHeaders = new[]
{
- "Connection",
- "Transfer-Encoding",
- "Keep-Alive",
- "Upgrade",
- "Proxy-Connection"
+ HeaderNames.Connection,
+ HeaderNames.TransferEncoding,
+ HeaderNames.KeepAlive,
+ HeaderNames.Upgrade,
+ HeaderNames.ProxyConnection
};
InvalidH2H3ResponseHeadersBits = ResponseHeaders
@@ -280,7 +282,7 @@ static string AppendValue(bool returnTrue = false) =>
static string AppendHPackSwitchSection(HPackGroup group)
{
var header = group.Header;
- if (header.Identifier == "ContentLength")
+ if (header.Name == HeaderNames.ContentLength)
{
return $@"if (ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultRequestHeaderEncodingSelector))
{{
@@ -320,7 +322,7 @@ static string AppendSwitchSection(int length, IList values)
string GenerateIfBody(KnownHeader header, string extraIndent = "")
{
- if (header.Identifier == "ContentLength")
+ if (header.Name == HeaderNames.ContentLength)
{
return $@"
{extraIndent}if (ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultRequestHeaderEncodingSelector))
@@ -384,6 +386,14 @@ public class KnownHeader
private string ResolveIdentifier(string name)
{
+ // Check the 3 lowercase headers
+ switch (name)
+ {
+ case "baggage": return "Baggage";
+ case "traceparent": return "TraceParent";
+ case "tracestate": return "TraceState";
+ }
+
var identifier = name.Replace("-", "");
// Pseudo headers start with a colon. A colon isn't valid in C# names so
@@ -732,6 +742,11 @@ internal enum KnownHeaderType
Unknown,{Each(allHeaderNames, n => @"
" + n + ",")}
}}
+
+ internal partial class HttpHeaders
+ {{
+ {GetHeaderLookup()}
+ }}
{Each(loops, loop => $@"
internal partial class {loop.ClassName}
{{{(loop.Bytes != null ?
@@ -747,8 +762,8 @@ internal partial class {loop.ClassName}
{Each(loop.Headers.Where(header => header.FastCount), header => $@"
public int {header.Identifier}Count => _headers._{header.Identifier}.Count;")}
{Each(loop.Headers, header => $@"
- public StringValues Header{header.Identifier}
- {{{(header.Identifier == "ContentLength" ? $@"
+ public {(header.Name == HeaderNames.Connection ? "override " : "")}StringValues Header{header.Identifier}
+ {{{(header.Name == HeaderNames.ContentLength ? $@"
get
{{
StringValues value = default;
@@ -798,7 +813,7 @@ protected override bool TryGetValueFast(string key, out StringValues value)
case {byLength.Key}:
{{{Each(byLength.OrderBy(h => !h.PrimaryHeader), header => $@"
if (ReferenceEquals(HeaderNames.{header.Identifier}, key))
- {{{(header.Identifier == "ContentLength" ? @"
+ {{{(header.Name == HeaderNames.ContentLength ? @"
if (_contentLength.HasValue)
{
value = HeaderUtilities.FormatNonNegativeInt64(_contentLength.Value);
@@ -814,7 +829,7 @@ protected override bool TryGetValueFast(string key, out StringValues value)
}}")}
{Each(byLength.OrderBy(h => !h.PrimaryHeader), header => $@"
if (HeaderNames.{header.Identifier}.Equals(key, StringComparison.OrdinalIgnoreCase))
- {{{(header.Identifier == "ContentLength" ? @"
+ {{{(header.Name == HeaderNames.ContentLength ? @"
if (_contentLength.HasValue)
{
value = HeaderUtilities.FormatNonNegativeInt64(_contentLength.Value);
@@ -843,7 +858,7 @@ protected override void SetValueFast(string key, StringValues value)
case {byLength.Key}:
{{{Each(byLength.OrderBy(h => !h.PrimaryHeader), header => $@"
if (ReferenceEquals(HeaderNames.{header.Identifier}, key))
- {{{(header.Identifier == "ContentLength" ? $@"
+ {{{(header.Name == HeaderNames.ContentLength ? $@"
_contentLength = ParseContentLength(value.ToString());" : $@"
{header.SetBit()};
_headers._{header.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@"
@@ -852,7 +867,7 @@ protected override void SetValueFast(string key, StringValues value)
}}")}
{Each(byLength.OrderBy(h => !h.PrimaryHeader), header => $@"
if (HeaderNames.{header.Identifier}.Equals(key, StringComparison.OrdinalIgnoreCase))
- {{{(header.Identifier == "ContentLength" ? $@"
+ {{{(header.Name == HeaderNames.ContentLength ? $@"
_contentLength = ParseContentLength(value.ToString());" : $@"
{header.SetBit()};
_headers._{header.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@"
@@ -874,7 +889,7 @@ protected override bool AddValueFast(string key, StringValues value)
case {byLength.Key}:
{{{Each(byLength.OrderBy(h => !h.PrimaryHeader), header => $@"
if (ReferenceEquals(HeaderNames.{header.Identifier}, key))
- {{{(header.Identifier == "ContentLength" ? $@"
+ {{{(header.Name == HeaderNames.ContentLength ? $@"
if (!_contentLength.HasValue)
{{
_contentLength = ParseContentLength(value);
@@ -892,7 +907,7 @@ protected override bool AddValueFast(string key, StringValues value)
}}")}
{Each(byLength.OrderBy(h => !h.PrimaryHeader), header => $@"
if (HeaderNames.{header.Identifier}.Equals(key, StringComparison.OrdinalIgnoreCase))
- {{{(header.Identifier == "ContentLength" ? $@"
+ {{{(header.Name == HeaderNames.ContentLength ? $@"
if (!_contentLength.HasValue)
{{
_contentLength = ParseContentLength(value);
@@ -922,7 +937,7 @@ protected override bool RemoveFast(string key)
case {byLength.Key}:
{{{Each(byLength.OrderBy(h => !h.PrimaryHeader), header => $@"
if (ReferenceEquals(HeaderNames.{header.Identifier}, key))
- {{{(header.Identifier == "ContentLength" ? @"
+ {{{(header.Name == HeaderNames.ContentLength ? @"
if (_contentLength.HasValue)
{
_contentLength = null;
@@ -940,7 +955,7 @@ protected override bool RemoveFast(string key)
}}")}
{Each(byLength.OrderBy(h => !h.PrimaryHeader), header => $@"
if (HeaderNames.{header.Identifier}.Equals(key, StringComparison.OrdinalIgnoreCase))
- {{{(header.Identifier == "ContentLength" ? @"
+ {{{(header.Name == HeaderNames.ContentLength ? @"
if (_contentLength.HasValue)
{
_contentLength = null;
@@ -1200,6 +1215,15 @@ public bool MoveNext()
")}}}";
}
+ private static string GetHeaderLookup()
+ {
+ var headerNameFields = typeof(HeaderNames).GetFields(BindingFlags.Static | BindingFlags.Public);
+ return @$"private readonly static HashSet _internedHeaderNames = new HashSet({headerNameFields.Length}, StringComparer.OrdinalIgnoreCase)
+ {{{Each(headerNameFields, (f) => @"
+ HeaderNames." + f.Name + ",")}
+ }};";
+ }
+
private static IEnumerable GroupHPack(KnownHeader[] headers)
{
var staticHeaders = new (int Index, HeaderField HeaderField)[H2StaticTable.Count];