Skip to content

Commit ffc0bf9

Browse files
authored
Fast path for validating static table HTTP/2 headers (#24730)
1 parent 7f7528f commit ffc0bf9

File tree

14 files changed

+614
-144
lines changed

14 files changed

+614
-144
lines changed

src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs

Lines changed: 247 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6617,7 +6617,7 @@ public unsafe void Append(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
66176617
ref StringValues values = ref Unsafe.AsRef<StringValues>(null);
66186618
var flag = 0L;
66196619

6620-
// Does the name matched any "known" headers
6620+
// Does the name match any "known" headers
66216621
switch (name.Length)
66226622
{
66236623
case 2:
@@ -7070,6 +7070,251 @@ public unsafe void Append(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
70707070
}
70717071
}
70727072

7073+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
7074+
public unsafe bool TryHPackAppend(int index, ReadOnlySpan<byte> value)
7075+
{
7076+
ref StringValues values = ref Unsafe.AsRef<StringValues>(null);
7077+
var nameStr = string.Empty;
7078+
var flag = 0L;
7079+
7080+
// Does the HPack static index match any "known" headers
7081+
switch (index)
7082+
{
7083+
case 1:
7084+
flag = 0x100000L;
7085+
values = ref _headers._Authority;
7086+
nameStr = HeaderNames.Authority;
7087+
break;
7088+
case 2:
7089+
case 3:
7090+
flag = 0x200000L;
7091+
values = ref _headers._Method;
7092+
nameStr = HeaderNames.Method;
7093+
break;
7094+
case 4:
7095+
case 5:
7096+
flag = 0x400000L;
7097+
values = ref _headers._Path;
7098+
nameStr = HeaderNames.Path;
7099+
break;
7100+
case 6:
7101+
case 7:
7102+
flag = 0x800000L;
7103+
values = ref _headers._Scheme;
7104+
nameStr = HeaderNames.Scheme;
7105+
break;
7106+
case 15:
7107+
flag = 0x2000000L;
7108+
values = ref _headers._AcceptCharset;
7109+
nameStr = HeaderNames.AcceptCharset;
7110+
break;
7111+
case 16:
7112+
flag = 0x4000000L;
7113+
values = ref _headers._AcceptEncoding;
7114+
nameStr = HeaderNames.AcceptEncoding;
7115+
break;
7116+
case 17:
7117+
flag = 0x8000000L;
7118+
values = ref _headers._AcceptLanguage;
7119+
nameStr = HeaderNames.AcceptLanguage;
7120+
break;
7121+
case 19:
7122+
flag = 0x1000000L;
7123+
values = ref _headers._Accept;
7124+
nameStr = HeaderNames.Accept;
7125+
break;
7126+
case 22:
7127+
flag = 0x800L;
7128+
values = ref _headers._Allow;
7129+
nameStr = HeaderNames.Allow;
7130+
break;
7131+
case 23:
7132+
flag = 0x10000000L;
7133+
values = ref _headers._Authorization;
7134+
nameStr = HeaderNames.Authorization;
7135+
break;
7136+
case 24:
7137+
flag = 0x1L;
7138+
values = ref _headers._CacheControl;
7139+
nameStr = HeaderNames.CacheControl;
7140+
break;
7141+
case 26:
7142+
flag = 0x2000L;
7143+
values = ref _headers._ContentEncoding;
7144+
nameStr = HeaderNames.ContentEncoding;
7145+
break;
7146+
case 27:
7147+
flag = 0x4000L;
7148+
values = ref _headers._ContentLanguage;
7149+
nameStr = HeaderNames.ContentLanguage;
7150+
break;
7151+
case 28:
7152+
if (ReferenceEquals(EncodingSelector, KestrelServerOptions.DefaultRequestHeaderEncodingSelector))
7153+
{
7154+
AppendContentLength(value);
7155+
}
7156+
else
7157+
{
7158+
AppendContentLengthCustomEncoding(value, EncodingSelector(HeaderNames.ContentLength));
7159+
}
7160+
return true;
7161+
case 29:
7162+
flag = 0x8000L;
7163+
values = ref _headers._ContentLocation;
7164+
nameStr = HeaderNames.ContentLocation;
7165+
break;
7166+
case 30:
7167+
flag = 0x20000L;
7168+
values = ref _headers._ContentRange;
7169+
nameStr = HeaderNames.ContentRange;
7170+
break;
7171+
case 31:
7172+
flag = 0x1000L;
7173+
values = ref _headers._ContentType;
7174+
nameStr = HeaderNames.ContentType;
7175+
break;
7176+
case 32:
7177+
flag = 0x20000000L;
7178+
values = ref _headers._Cookie;
7179+
nameStr = HeaderNames.Cookie;
7180+
break;
7181+
case 33:
7182+
flag = 0x4L;
7183+
values = ref _headers._Date;
7184+
nameStr = HeaderNames.Date;
7185+
break;
7186+
case 35:
7187+
flag = 0x40000000L;
7188+
values = ref _headers._Expect;
7189+
nameStr = HeaderNames.Expect;
7190+
break;
7191+
case 36:
7192+
flag = 0x40000L;
7193+
values = ref _headers._Expires;
7194+
nameStr = HeaderNames.Expires;
7195+
break;
7196+
case 37:
7197+
flag = 0x80000000L;
7198+
values = ref _headers._From;
7199+
nameStr = HeaderNames.From;
7200+
break;
7201+
case 38:
7202+
flag = 0x400000000L;
7203+
values = ref _headers._Host;
7204+
nameStr = HeaderNames.Host;
7205+
break;
7206+
case 39:
7207+
flag = 0x800000000L;
7208+
values = ref _headers._IfMatch;
7209+
nameStr = HeaderNames.IfMatch;
7210+
break;
7211+
case 40:
7212+
flag = 0x1000000000L;
7213+
values = ref _headers._IfModifiedSince;
7214+
nameStr = HeaderNames.IfModifiedSince;
7215+
break;
7216+
case 41:
7217+
flag = 0x2000000000L;
7218+
values = ref _headers._IfNoneMatch;
7219+
nameStr = HeaderNames.IfNoneMatch;
7220+
break;
7221+
case 42:
7222+
flag = 0x4000000000L;
7223+
values = ref _headers._IfRange;
7224+
nameStr = HeaderNames.IfRange;
7225+
break;
7226+
case 43:
7227+
flag = 0x8000000000L;
7228+
values = ref _headers._IfUnmodifiedSince;
7229+
nameStr = HeaderNames.IfUnmodifiedSince;
7230+
break;
7231+
case 44:
7232+
flag = 0x80000L;
7233+
values = ref _headers._LastModified;
7234+
nameStr = HeaderNames.LastModified;
7235+
break;
7236+
case 47:
7237+
flag = 0x10000000000L;
7238+
values = ref _headers._MaxForwards;
7239+
nameStr = HeaderNames.MaxForwards;
7240+
break;
7241+
case 49:
7242+
flag = 0x20000000000L;
7243+
values = ref _headers._ProxyAuthorization;
7244+
nameStr = HeaderNames.ProxyAuthorization;
7245+
break;
7246+
case 50:
7247+
flag = 0x80000000000L;
7248+
values = ref _headers._Range;
7249+
nameStr = HeaderNames.Range;
7250+
break;
7251+
case 51:
7252+
flag = 0x40000000000L;
7253+
values = ref _headers._Referer;
7254+
nameStr = HeaderNames.Referer;
7255+
break;
7256+
case 57:
7257+
flag = 0x80L;
7258+
values = ref _headers._TransferEncoding;
7259+
nameStr = HeaderNames.TransferEncoding;
7260+
break;
7261+
case 58:
7262+
flag = 0x400000000000L;
7263+
values = ref _headers._UserAgent;
7264+
nameStr = HeaderNames.UserAgent;
7265+
break;
7266+
case 60:
7267+
flag = 0x200L;
7268+
values = ref _headers._Via;
7269+
nameStr = HeaderNames.Via;
7270+
break;
7271+
}
7272+
7273+
if (flag != 0)
7274+
{
7275+
// Matched a known header
7276+
if ((_previousBits & flag) != 0)
7277+
{
7278+
// Had a previous string for this header, mark it as used so we don't clear it OnHeadersComplete or consider it if we get a second header
7279+
_previousBits ^= flag;
7280+
7281+
// We will only reuse this header if there was only one previous header
7282+
if (values.Count == 1)
7283+
{
7284+
var previousValue = values.ToString();
7285+
// Check lengths are the same, then if the bytes were converted to an ascii string if they would be the same.
7286+
// We do not consider Utf8 headers for reuse.
7287+
if (previousValue.Length == value.Length &&
7288+
StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, value))
7289+
{
7290+
// The previous string matches what the bytes would convert to, so we will just use that one.
7291+
_bits |= flag;
7292+
return true;
7293+
}
7294+
}
7295+
}
7296+
7297+
// We didn't have a previous matching header value, or have already added a header, so get the string for this value.
7298+
var valueStr = value.GetRequestHeaderString(nameStr, EncodingSelector);
7299+
if ((_bits & flag) == 0)
7300+
{
7301+
// We didn't already have a header set, so add a new one.
7302+
_bits |= flag;
7303+
values = new StringValues(valueStr);
7304+
}
7305+
else
7306+
{
7307+
// We already had a header set, so concatenate the new one.
7308+
values = AppendValue(values, valueStr);
7309+
}
7310+
return true;
7311+
}
7312+
else
7313+
{
7314+
return false;
7315+
}
7316+
}
7317+
70737318
private struct HeaderReferences
70747319
{
70757320
public StringValues _CacheControl;
@@ -13716,4 +13961,4 @@ public bool MoveNext()
1371613961
}
1371713962
}
1371813963
}
13719-
}
13964+
}

src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -513,27 +513,35 @@ private void PreventRequestAbortedCancellation()
513513

514514
public virtual void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
515515
{
516-
_requestHeadersParsed++;
517-
if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount)
518-
{
519-
KestrelBadHttpRequestException.Throw(RequestRejectionReason.TooManyHeaders);
520-
}
516+
IncrementRequestHeadersCount();
521517

522518
HttpRequestHeaders.Append(name, value);
523519
}
524520

521+
public virtual void OnHeader(int index, ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
522+
{
523+
IncrementRequestHeadersCount();
524+
525+
// This method should be overriden in specific implementations and the base should be
526+
// called to validate the header count.
527+
}
528+
525529
public void OnTrailer(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
526530
{
527-
// Trailers still count towards the limit.
531+
IncrementRequestHeadersCount();
532+
533+
string key = name.GetHeaderName();
534+
var valueStr = value.GetRequestHeaderString(key, HttpRequestHeaders.EncodingSelector);
535+
RequestTrailers.Append(key, valueStr);
536+
}
537+
538+
private void IncrementRequestHeadersCount()
539+
{
528540
_requestHeadersParsed++;
529541
if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount)
530542
{
531543
KestrelBadHttpRequestException.Throw(RequestRejectionReason.TooManyHeaders);
532544
}
533-
534-
string key = name.GetHeaderName();
535-
var valueStr = value.GetRequestHeaderString(key, HttpRequestHeaders.EncodingSelector);
536-
RequestTrailers.Append(key, valueStr);
537545
}
538546

539547
public void OnHeadersComplete()

0 commit comments

Comments
 (0)