Skip to content

Commit 4656f15

Browse files
authored
HTTP/3: Write static header names (#38565)
1 parent 15b9f5d commit 4656f15

18 files changed

+224
-43
lines changed

src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ public ValueTask<FlushResult> WriteResponseTrailersAsync(long streamId, HttpResp
311311

312312
_outgoingFrame.PrepareHeaders();
313313
var buffer = _headerEncodingBuffer.GetSpan(HeaderBufferSize);
314-
var done = QPackHeaderWriter.BeginEncode(_headersEnumerator, buffer, ref _headersTotalSize, out var payloadLength);
314+
var done = QPackHeaderWriter.BeginEncodeHeaders(_headersEnumerator, buffer, ref _headersTotalSize, out var payloadLength);
315315
FinishWritingHeaders(payloadLength, done);
316316
}
317317
// Any exception from the QPack encoder can leave the dynamic table in a corrupt state.
@@ -366,7 +366,7 @@ internal void WriteResponseHeaders(int statusCode, HttpResponseHeaders headers)
366366

367367
_outgoingFrame.PrepareHeaders();
368368
var buffer = _headerEncodingBuffer.GetSpan(HeaderBufferSize);
369-
var done = QPackHeaderWriter.BeginEncode(statusCode, _headersEnumerator, buffer, ref _headersTotalSize, out var payloadLength);
369+
var done = QPackHeaderWriter.BeginEncodeHeaders(statusCode, _headersEnumerator, buffer, ref _headersTotalSize, out var payloadLength);
370370
FinishWritingHeaders(payloadLength, done);
371371
}
372372
// Any exception from the QPack encoder can leave the dynamic table in a corrupt state.

src/Servers/Kestrel/Core/src/Internal/Http3/Http3HeadersEnumerator.cs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections;
66
using System.Collections.Generic;
7+
using System.Net.Http.QPack;
78
using System.Text;
89
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
910
using Microsoft.Extensions.Primitives;
@@ -147,7 +148,89 @@ public void Dispose()
147148

148149
internal static int GetResponseHeaderStaticTableId(KnownHeaderType responseHeaderType)
149150
{
150-
// Not Implemented
151-
return -1;
151+
// Not every header in the QPACK static table is known.
152+
// These are missing from this test and the full header name is written.
153+
// Missing:
154+
// - link
155+
// - location
156+
// - strict-transport-security
157+
// - x-content-type-options
158+
// - x-xss-protection
159+
// - content-security-policy
160+
// - early-data
161+
// - expect-ct
162+
// - purpose
163+
// - timing-allow-origin
164+
// - x-forwarded-for
165+
// - x-frame-options
166+
switch (responseHeaderType)
167+
{
168+
case KnownHeaderType.Age:
169+
return H3StaticTable.Age0;
170+
case KnownHeaderType.ContentLength:
171+
return H3StaticTable.ContentLength0;
172+
case KnownHeaderType.Date:
173+
return H3StaticTable.Date;
174+
case KnownHeaderType.Cookie:
175+
return H3StaticTable.Cookie;
176+
case KnownHeaderType.ETag:
177+
return H3StaticTable.ETag;
178+
case KnownHeaderType.IfModifiedSince:
179+
return H3StaticTable.IfModifiedSince;
180+
case KnownHeaderType.IfNoneMatch:
181+
return H3StaticTable.IfNoneMatch;
182+
case KnownHeaderType.LastModified:
183+
return H3StaticTable.LastModified;
184+
case KnownHeaderType.Location:
185+
return H3StaticTable.Location;
186+
case KnownHeaderType.Referer:
187+
return H3StaticTable.Referer;
188+
case KnownHeaderType.SetCookie:
189+
return H3StaticTable.SetCookie;
190+
case KnownHeaderType.Method:
191+
return H3StaticTable.MethodConnect;
192+
case KnownHeaderType.Accept:
193+
return H3StaticTable.AcceptAny;
194+
case KnownHeaderType.AcceptEncoding:
195+
return H3StaticTable.AcceptEncodingGzipDeflateBr;
196+
case KnownHeaderType.AcceptRanges:
197+
return H3StaticTable.AcceptRangesBytes;
198+
case KnownHeaderType.AccessControlAllowHeaders:
199+
return H3StaticTable.AccessControlAllowHeadersCacheControl;
200+
case KnownHeaderType.AccessControlAllowOrigin:
201+
return H3StaticTable.AccessControlAllowOriginAny;
202+
case KnownHeaderType.CacheControl:
203+
return H3StaticTable.CacheControlMaxAge0;
204+
case KnownHeaderType.ContentEncoding:
205+
return H3StaticTable.ContentEncodingBr;
206+
case KnownHeaderType.ContentType:
207+
return H3StaticTable.ContentTypeApplicationDnsMessage;
208+
case KnownHeaderType.Range:
209+
return H3StaticTable.RangeBytes0ToAll;
210+
case KnownHeaderType.Vary:
211+
return H3StaticTable.VaryAcceptEncoding;
212+
case KnownHeaderType.AcceptLanguage:
213+
return H3StaticTable.AcceptLanguage;
214+
case KnownHeaderType.AccessControlAllowCredentials:
215+
return H3StaticTable.AccessControlAllowCredentials;
216+
case KnownHeaderType.AccessControlAllowMethods:
217+
return H3StaticTable.AccessControlAllowMethodsGet;
218+
case KnownHeaderType.AltSvc:
219+
return H3StaticTable.AltSvcClear;
220+
case KnownHeaderType.Authorization:
221+
return H3StaticTable.Authorization;
222+
case KnownHeaderType.IfRange:
223+
return H3StaticTable.IfRange;
224+
case KnownHeaderType.Origin:
225+
return H3StaticTable.Origin;
226+
case KnownHeaderType.Server:
227+
return H3StaticTable.Server;
228+
case KnownHeaderType.UpgradeInsecureRequests:
229+
return H3StaticTable.UpgradeInsecureRequests1;
230+
case KnownHeaderType.UserAgent:
231+
return H3StaticTable.UserAgent;
232+
default:
233+
return -1;
234+
}
152235
}
153236
}

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext>
640640

641641
try
642642
{
643-
QPackDecoder.Decode(payload, handler: this);
643+
QPackDecoder.Decode(payload, endHeaders: true, handler: this);
644644
QPackDecoder.Reset();
645645
}
646646
catch (QPackDecodingException ex)

src/Servers/Kestrel/Core/src/Internal/Http3/QPackHeaderWriter.cs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
using System;
55
using System.Diagnostics;
66
using System.Net.Http.QPack;
7+
using System.Text;
78

89
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
910

1011
internal static class QPackHeaderWriter
1112
{
12-
public static bool BeginEncode(Http3HeadersEnumerator enumerator, Span<byte> buffer, ref int totalHeaderSize, out int length)
13+
public static bool BeginEncodeHeaders(Http3HeadersEnumerator enumerator, Span<byte> buffer, ref int totalHeaderSize, out int length)
1314
{
1415
bool hasValue = enumerator.MoveNext();
1516
Debug.Assert(hasValue == true);
@@ -24,40 +25,47 @@ public static bool BeginEncode(Http3HeadersEnumerator enumerator, Span<byte> buf
2425
return doneEncode;
2526
}
2627

27-
public static bool BeginEncode(int statusCode, Http3HeadersEnumerator enumerator, Span<byte> buffer, ref int totalHeaderSize, out int length)
28+
public static bool BeginEncodeHeaders(int statusCode, Http3HeadersEnumerator headersEnumerator, Span<byte> buffer, ref int totalHeaderSize, out int length)
2829
{
29-
bool hasValue = enumerator.MoveNext();
30-
Debug.Assert(hasValue == true);
30+
length = 0;
3131

3232
// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#header-prefix
3333
buffer[0] = 0;
3434
buffer[1] = 0;
3535

3636
int statusCodeLength = EncodeStatusCode(statusCode, buffer.Slice(2));
3737
totalHeaderSize += 42; // name (:status) + value (xxx) + overhead (32)
38+
length += statusCodeLength + 2;
3839

39-
bool done = Encode(enumerator, buffer.Slice(statusCodeLength + 2), throwIfNoneEncoded: false, ref totalHeaderSize, out int headersLength);
40-
length = statusCodeLength + headersLength + 2;
40+
if (!headersEnumerator.MoveNext())
41+
{
42+
return true;
43+
}
44+
45+
bool done = Encode(headersEnumerator, buffer.Slice(statusCodeLength + 2), throwIfNoneEncoded: false, ref totalHeaderSize, out int headersLength);
46+
length += headersLength;
4147

4248
return done;
4349
}
4450

45-
public static bool Encode(Http3HeadersEnumerator enumerator, Span<byte> buffer, ref int totalHeaderSize, out int length)
51+
public static bool Encode(Http3HeadersEnumerator headersEnumerator, Span<byte> buffer, ref int totalHeaderSize, out int length)
4652
{
47-
return Encode(enumerator, buffer, throwIfNoneEncoded: true, ref totalHeaderSize, out length);
53+
return Encode(headersEnumerator, buffer, throwIfNoneEncoded: true, ref totalHeaderSize, out length);
4854
}
4955

50-
private static bool Encode(Http3HeadersEnumerator enumerator, Span<byte> buffer, bool throwIfNoneEncoded, ref int totalHeaderSize, out int length)
56+
private static bool Encode(Http3HeadersEnumerator headersEnumerator, Span<byte> buffer, bool throwIfNoneEncoded, ref int totalHeaderSize, out int length)
5157
{
5258
length = 0;
5359

5460
do
5561
{
56-
var current = enumerator.Current;
57-
var valueEncoding = ReferenceEquals(enumerator.EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector)
58-
? null : enumerator.EncodingSelector(current.Key);
62+
var staticTableId = headersEnumerator.QPackStaticTableId;
63+
var name = headersEnumerator.Current.Key;
64+
var value = headersEnumerator.Current.Value;
65+
var valueEncoding = ReferenceEquals(headersEnumerator.EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector)
66+
? null : headersEnumerator.EncodingSelector(name);
5967

60-
if (!QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReference(current.Key, current.Value, valueEncoding, buffer.Slice(length), out int headerLength))
68+
if (!EncodeHeader(buffer.Slice(length), staticTableId, name, value, valueEncoding, out var headerLength))
6169
{
6270
if (length == 0 && throwIfNoneEncoded)
6371
{
@@ -67,13 +75,20 @@ private static bool Encode(Http3HeadersEnumerator enumerator, Span<byte> buffer,
6775
}
6876

6977
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.3
70-
totalHeaderSize += HeaderField.GetLength(current.Key.Length, current.Value.Length);
78+
totalHeaderSize += HeaderField.GetLength(name.Length, value.Length);
7179
length += headerLength;
72-
} while (enumerator.MoveNext());
80+
} while (headersEnumerator.MoveNext());
7381

7482
return true;
7583
}
7684

85+
private static bool EncodeHeader(Span<byte> buffer, int staticTableId, string name, string value, Encoding? valueEncoding, out int headerLength)
86+
{
87+
return staticTableId == -1
88+
? QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReference(name, value, valueEncoding, buffer, out headerLength)
89+
: QPackEncoder.EncodeLiteralHeaderFieldWithStaticNameReference(staticTableId, value, valueEncoding, buffer, out headerLength);
90+
}
91+
7792
private static int EncodeStatusCode(int statusCode, Span<byte> buffer)
7893
{
7994
switch (statusCode)

0 commit comments

Comments
 (0)