Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP/3: Improve static table compression to include values #38681

Merged
merged 3 commits into from
Nov 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,179 @@ internal enum KnownHeaderType
WWWAuthenticate,
}

internal static class HttpHeadersCompression
{
internal static (int index, bool matchedValue) MatchKnownHeaderQPack(KnownHeaderType knownHeader, string value)
{
switch (knownHeader)
{
case KnownHeaderType.Age:
switch (value)
{
case "0":
return (2, true);
default:
return (2, false);
}
case KnownHeaderType.ContentLength:
switch (value)
{
case "0":
return (4, true);
default:
return (4, false);
}
case KnownHeaderType.Date:
return (6, false);
case KnownHeaderType.ETag:
return (7, false);
case KnownHeaderType.LastModified:
return (10, false);
case KnownHeaderType.Location:
return (12, false);
case KnownHeaderType.SetCookie:
return (14, false);
case KnownHeaderType.AcceptRanges:
switch (value)
{
case "bytes":
return (32, true);
default:
return (32, false);
}
case KnownHeaderType.AccessControlAllowHeaders:
switch (value)
{
case "cache-control":
return (33, true);
case "content-type":
return (34, true);
case "*":
return (75, true);
default:
return (33, false);
}
case KnownHeaderType.AccessControlAllowOrigin:
switch (value)
{
case "*":
return (35, true);
default:
return (35, false);
}
case KnownHeaderType.CacheControl:
switch (value)
{
case "max-age=0":
return (36, true);
case "max-age=2592000":
return (37, true);
case "max-age=604800":
return (38, true);
case "no-cache":
return (39, true);
case "no-store":
return (40, true);
case "public, max-age=31536000":
return (41, true);
default:
return (36, false);
}
case KnownHeaderType.ContentEncoding:
switch (value)
{
case "br":
return (42, true);
case "gzip":
return (43, true);
default:
return (42, false);
}
case KnownHeaderType.ContentType:
switch (value)
{
case "application/dns-message":
return (44, true);
case "application/javascript":
return (45, true);
case "application/json":
return (46, true);
case "application/x-www-form-urlencoded":
return (47, true);
case "image/gif":
return (48, true);
case "image/jpeg":
return (49, true);
case "image/png":
return (50, true);
case "text/css":
return (51, true);
case "text/html; charset=utf-8":
return (52, true);
case "text/plain":
return (53, true);
case "text/plain;charset=utf-8":
return (54, true);
default:
return (44, false);
}
case KnownHeaderType.Vary:
switch (value)
{
case "accept-encoding":
return (59, true);
case "origin":
return (60, true);
default:
return (59, false);
}
case KnownHeaderType.AccessControlAllowCredentials:
switch (value)
{
case "FALSE":
return (73, true);
case "TRUE":
return (74, true);
default:
return (73, false);
}
case KnownHeaderType.AccessControlAllowMethods:
switch (value)
{
case "get":
return (76, true);
case "get, post, options":
return (77, true);
case "options":
return (78, true);
default:
return (76, false);
}
case KnownHeaderType.AccessControlExposeHeaders:
switch (value)
{
case "content-length":
return (79, true);
default:
return (79, false);
}
case KnownHeaderType.AltSvc:
switch (value)
{
case "clear":
return (83, true);
default:
return (83, false);
}
case KnownHeaderType.Server:
return (92, false);

default:
return (-1, false);
}
}
}

internal partial class HttpHeaders
{
private readonly static HashSet<string> _internedHeaderNames = new HashSet<string>(96, StringComparer.OrdinalIgnoreCase)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private enum HeadersType : byte

public Func<string, Encoding?> EncodingSelector { get; set; } = KestrelServerOptions.DefaultHeaderEncodingSelector;

public int QPackStaticTableId => GetResponseHeaderStaticTableId(_knownHeaderType);
public (int index, bool matchedValue) GetQPackStaticTableId() => HttpHeadersCompression.MatchKnownHeaderQPack(_knownHeaderType, Current.Value);
public KeyValuePair<string, string> Current { get; private set; }
object IEnumerator.Current => Current;

Expand Down Expand Up @@ -145,66 +145,4 @@ public void Reset()
public void Dispose()
{
}

internal static int GetResponseHeaderStaticTableId(KnownHeaderType responseHeaderType)
{
// Removed from this test are request-only headers, e.g. cookie.
//
// Not every header in the QPACK static table is known.
// These are missing from this test and the full header name is written.
// Missing:
// - link
// - location
// - strict-transport-security
// - x-content-type-options
// - x-xss-protection
// - content-security-policy
// - early-data
// - expect-ct
// - purpose
// - timing-allow-origin
// - x-forwarded-for
// - x-frame-options
switch (responseHeaderType)
{
case KnownHeaderType.Age:
return H3StaticTable.Age0;
case KnownHeaderType.ContentLength:
return H3StaticTable.ContentLength0;
case KnownHeaderType.Date:
return H3StaticTable.Date;
case KnownHeaderType.ETag:
return H3StaticTable.ETag;
case KnownHeaderType.LastModified:
return H3StaticTable.LastModified;
case KnownHeaderType.Location:
return H3StaticTable.Location;
case KnownHeaderType.SetCookie:
return H3StaticTable.SetCookie;
case KnownHeaderType.AcceptRanges:
return H3StaticTable.AcceptRangesBytes;
case KnownHeaderType.AccessControlAllowHeaders:
return H3StaticTable.AccessControlAllowHeadersCacheControl;
case KnownHeaderType.AccessControlAllowOrigin:
return H3StaticTable.AccessControlAllowOriginAny;
case KnownHeaderType.CacheControl:
return H3StaticTable.CacheControlMaxAge0;
case KnownHeaderType.ContentEncoding:
return H3StaticTable.ContentEncodingBr;
case KnownHeaderType.ContentType:
return H3StaticTable.ContentTypeApplicationDnsMessage;
case KnownHeaderType.Vary:
return H3StaticTable.VaryAcceptEncoding;
case KnownHeaderType.AccessControlAllowCredentials:
return H3StaticTable.AccessControlAllowCredentials;
case KnownHeaderType.AccessControlAllowMethods:
return H3StaticTable.AccessControlAllowMethodsGet;
case KnownHeaderType.AltSvc:
return H3StaticTable.AltSvcClear;
case KnownHeaderType.Server:
return H3StaticTable.Server;
default:
return -1;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,39 @@ private static bool Encode(Http3HeadersEnumerator headersEnumerator, Span<byte>

do
{
var staticTableId = headersEnumerator.QPackStaticTableId;
// Match the current header to the QPACK static table. Possible outcomes:
// 1. Known header and value. Write index.
// 2. Known header with custom value. Write name index and full value.
// 3. Unknown header. Write full name and value.
var (staticTableId, matchedValue) = headersEnumerator.GetQPackStaticTableId();
var name = headersEnumerator.Current.Key;
var value = headersEnumerator.Current.Value;
var valueEncoding = ReferenceEquals(headersEnumerator.EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector)
? null : headersEnumerator.EncodingSelector(name);

if (!EncodeHeader(buffer.Slice(length), staticTableId, name, value, valueEncoding, out var headerLength))
int headerLength;
if (matchedValue)
{
if (length == 0 && throwIfNoneEncoded)
if (!QPackEncoder.EncodeStaticIndexedHeaderField(staticTableId, buffer.Slice(length), out headerLength))
{
throw new QPackEncodingException("TODO sync with corefx" /* CoreStrings.HPackErrorNotEnoughBuffer */);
if (length == 0 && throwIfNoneEncoded)
{
throw new QPackEncodingException("TODO sync with corefx" /* CoreStrings.HPackErrorNotEnoughBuffer */);
}
return false;
}
}
else
{
var valueEncoding = ReferenceEquals(headersEnumerator.EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector)
? null : headersEnumerator.EncodingSelector(name);

if (!EncodeHeader(buffer.Slice(length), staticTableId, name, value, valueEncoding, out headerLength))
{
if (length == 0 && throwIfNoneEncoded)
{
throw new QPackEncodingException("TODO sync with corefx" /* CoreStrings.HPackErrorNotEnoughBuffer */);
}
return false;
}
return false;
}

// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,20 @@ public void Initialize_ChangeHeadersSource_EnumeratorUsesNewSource()
Assert.True(e.MoveNext());
Assert.Equal("Name1", e.Current.Key);
Assert.Equal("Value1", e.Current.Value);
Assert.Equal(-1, e.QPackStaticTableId);
var (index, matchedValue) = e.GetQPackStaticTableId();
Assert.Equal(-1, index);

Assert.True(e.MoveNext());
Assert.Equal("Name2", e.Current.Key);
Assert.Equal("Value2-1", e.Current.Value);
Assert.Equal(-1, e.QPackStaticTableId);
(index, matchedValue) = e.GetQPackStaticTableId();
Assert.Equal(-1, index);

Assert.True(e.MoveNext());
Assert.Equal("Name2", e.Current.Key);
Assert.Equal("Value2-2", e.Current.Value);
Assert.Equal(-1, e.QPackStaticTableId);
(index, matchedValue) = e.GetQPackStaticTableId();
Assert.Equal(-1, index);

var responseTrailers = (IHeaderDictionary)new HttpResponseTrailers();

Expand All @@ -118,22 +121,26 @@ public void Initialize_ChangeHeadersSource_EnumeratorUsesNewSource()
Assert.True(e.MoveNext());
Assert.Equal("Grpc-Status", e.Current.Key);
Assert.Equal("1", e.Current.Value);
Assert.Equal(-1, e.QPackStaticTableId);
(index, matchedValue) = e.GetQPackStaticTableId();
Assert.Equal(-1, index);

Assert.True(e.MoveNext());
Assert.Equal("Name1", e.Current.Key);
Assert.Equal("Value1", e.Current.Value);
Assert.Equal(-1, e.QPackStaticTableId);
(index, matchedValue) = e.GetQPackStaticTableId();
Assert.Equal(-1, index);

Assert.True(e.MoveNext());
Assert.Equal("Name2", e.Current.Key);
Assert.Equal("Value2-1", e.Current.Value);
Assert.Equal(-1, e.QPackStaticTableId);
(index, matchedValue) = e.GetQPackStaticTableId();
Assert.Equal(-1, index);

Assert.True(e.MoveNext());
Assert.Equal("Name2", e.Current.Key);
Assert.Equal("Value2-2", e.Current.Value);
Assert.Equal(-1, e.QPackStaticTableId);
(index, matchedValue) = e.GetQPackStaticTableId();
Assert.Equal(-1, index);

Assert.False(e.MoveNext());
}
Expand All @@ -143,7 +150,7 @@ public void Initialize_ChangeHeadersSource_EnumeratorUsesNewSource()
var headers = new List<(int HPackStaticTableId, string Name, string Value)>();
while (enumerator.MoveNext())
{
headers.Add(CreateHeaderResult(enumerator.QPackStaticTableId, enumerator.Current.Key, enumerator.Current.Value));
headers.Add(CreateHeaderResult(enumerator.GetQPackStaticTableId().index, enumerator.Current.Key, enumerator.Current.Value));
}
return headers.ToArray();
}
Expand Down
Loading