Skip to content

Commit a898845

Browse files
committed
Move dynamic compression to shared code
1 parent 7395ad1 commit a898845

File tree

11 files changed

+402
-363
lines changed

11 files changed

+402
-363
lines changed

src/Servers/Kestrel/Core/src/Internal/Http2/HPack/StatusCodes.cs

Lines changed: 0 additions & 151 deletions
This file was deleted.
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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.Net.Http;
6+
using System.Net.Http.HPack;
7+
8+
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
9+
{
10+
internal static class HPackHeaderWriter
11+
{
12+
/// <summary>
13+
/// Begin encoding headers in the first HEADERS frame.
14+
/// </summary>
15+
public static bool BeginEncodeHeaders(int statusCode, HPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span<byte> buffer, out int length)
16+
{
17+
length = 0;
18+
19+
if (!hpackEncoder.EnsureDynamicTableSizeUpdate(buffer, out var sizeUpdateLength))
20+
{
21+
throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
22+
}
23+
length += sizeUpdateLength;
24+
25+
if (!EncodeStatusHeader(statusCode, hpackEncoder, buffer.Slice(length), out var statusCodeLength))
26+
{
27+
throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
28+
}
29+
length += statusCodeLength;
30+
31+
if (!headersEnumerator.MoveNext())
32+
{
33+
return true;
34+
}
35+
36+
// We're ok with not throwing if no headers were encoded because we've already encoded the status.
37+
// There is a small chance that the header will encode if there is no other content in the next HEADERS frame.
38+
var done = EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer.Slice(length), throwIfNoneEncoded: false, out var headersLength);
39+
length += headersLength;
40+
return done;
41+
}
42+
43+
/// <summary>
44+
/// Begin encoding headers in the first HEADERS frame.
45+
/// </summary>
46+
public static bool BeginEncodeHeaders(HPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span<byte> buffer, out int length)
47+
{
48+
length = 0;
49+
50+
if (!hpackEncoder.EnsureDynamicTableSizeUpdate(buffer, out var sizeUpdateLength))
51+
{
52+
throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
53+
}
54+
length += sizeUpdateLength;
55+
56+
if (!headersEnumerator.MoveNext())
57+
{
58+
return true;
59+
}
60+
61+
var done = EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer.Slice(length), throwIfNoneEncoded: true, out var headersLength);
62+
length += headersLength;
63+
return done;
64+
}
65+
66+
/// <summary>
67+
/// Continue encoding headers in the next HEADERS frame. The enumerator should already have a current value.
68+
/// </summary>
69+
public static bool ContinueEncodeHeaders(HPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span<byte> buffer, out int length)
70+
{
71+
return EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer, throwIfNoneEncoded: true, out length);
72+
}
73+
74+
private static bool EncodeStatusHeader(int statusCode, HPackEncoder hpackEncoder, Span<byte> buffer, out int length)
75+
{
76+
switch (statusCode)
77+
{
78+
case 200:
79+
case 204:
80+
case 206:
81+
case 304:
82+
case 400:
83+
case 404:
84+
case 500:
85+
// Status codes which exist in the HTTP/2 StaticTable.
86+
return HPackEncoder.EncodeIndexedHeaderField(H2StaticTable.StatusIndex[statusCode], buffer, out length);
87+
default:
88+
const string name = ":status";
89+
var value = StatusCodes.ToStatusString(statusCode);
90+
return hpackEncoder.EncodeHeader(buffer, H2StaticTable.Status200, HeaderEncodingHint.Index, name, value, out length);
91+
}
92+
}
93+
94+
private static bool EncodeHeadersCore(HPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span<byte> buffer, bool throwIfNoneEncoded, out int length)
95+
{
96+
var currentLength = 0;
97+
do
98+
{
99+
var staticTableId = headersEnumerator.HPackStaticTableId;
100+
var name = headersEnumerator.Current.Key;
101+
var value = headersEnumerator.Current.Value;
102+
103+
var hint = ResolveHeaderEncodingHint(staticTableId, name);
104+
105+
if (!hpackEncoder.EncodeHeader(
106+
buffer.Slice(currentLength),
107+
staticTableId,
108+
hint,
109+
name,
110+
value,
111+
out var headerLength))
112+
{
113+
// If the header wasn't written, and no headers have been written, then the header is too large.
114+
// Throw an error to avoid an infinite loop of attempting to write large header.
115+
if (currentLength == 0 && throwIfNoneEncoded)
116+
{
117+
throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
118+
}
119+
120+
length = currentLength;
121+
return false;
122+
}
123+
124+
currentLength += headerLength;
125+
}
126+
while (headersEnumerator.MoveNext());
127+
128+
length = currentLength;
129+
return true;
130+
}
131+
132+
private static HeaderEncodingHint ResolveHeaderEncodingHint(int staticTableId, string name)
133+
{
134+
HeaderEncodingHint hint;
135+
if (IsSensitive(staticTableId, name))
136+
{
137+
hint = HeaderEncodingHint.NeverIndex;
138+
}
139+
else if (IsNotDynamicallyIndexed(staticTableId))
140+
{
141+
hint = HeaderEncodingHint.IgnoreIndex;
142+
}
143+
else
144+
{
145+
hint = HeaderEncodingHint.Index;
146+
}
147+
148+
return hint;
149+
}
150+
151+
private static bool IsSensitive(int staticTableIndex, string name)
152+
{
153+
// Set-Cookie could contain sensitive data.
154+
if (staticTableIndex == H2StaticTable.SetCookie)
155+
{
156+
return true;
157+
}
158+
if (string.Equals(name, "Content-Disposition", StringComparison.OrdinalIgnoreCase))
159+
{
160+
return true;
161+
}
162+
163+
return false;
164+
}
165+
166+
private static bool IsNotDynamicallyIndexed(int staticTableIndex)
167+
{
168+
// Content-Length is added to static content. Content length is different for each
169+
// file, and is unlikely to be reused because of browser caching.
170+
return staticTableIndex == H2StaticTable.ContentLength;
171+
}
172+
}
173+
}

src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ internal class Http2FrameWriter
3939
private readonly ITimeoutControl _timeoutControl;
4040
private readonly MinDataRate _minResponseDataRate;
4141
private readonly TimingPipeFlusher _flusher;
42-
private readonly Http2HPackEncoder _hpackEncoder;
42+
private readonly HPackEncoder _hpackEncoder;
4343

4444
private uint _maxFrameSize = Http2PeerSettings.MinAllowedMaxFrameSize;
4545
private byte[] _headerEncodingBuffer;
@@ -72,7 +72,7 @@ public Http2FrameWriter(
7272
_outgoingFrame = new Http2Frame();
7373
_headerEncodingBuffer = new byte[_maxFrameSize];
7474

75-
_hpackEncoder = new Http2HPackEncoder(serviceContext.ServerOptions.DisableResponseDynamicHeaderCompression);
75+
_hpackEncoder = new HPackEncoder(serviceContext.ServerOptions.DisableResponseDynamicHeaderCompression);
7676
}
7777

7878
public void UpdateMaxHeaderTableSize(uint maxHeaderTableSize)
@@ -187,7 +187,7 @@ public void WriteResponseHeaders(int streamId, int statusCode, Http2HeadersFrame
187187
_headersEnumerator.Initialize(headers);
188188
_outgoingFrame.PrepareHeaders(headerFrameFlags, streamId);
189189
var buffer = _headerEncodingBuffer.AsSpan();
190-
var done = _hpackEncoder.BeginEncodeHeaders(statusCode, _headersEnumerator, buffer, out var payloadLength);
190+
var done = HPackHeaderWriter.BeginEncodeHeaders(statusCode, _hpackEncoder, _headersEnumerator, buffer, out var payloadLength);
191191
FinishWritingHeaders(streamId, payloadLength, done);
192192
}
193193
catch (HPackEncodingException hex)
@@ -213,7 +213,7 @@ public ValueTask<FlushResult> WriteResponseTrailers(int streamId, HttpResponseTr
213213
_headersEnumerator.Initialize(headers);
214214
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.END_STREAM, streamId);
215215
var buffer = _headerEncodingBuffer.AsSpan();
216-
var done = _hpackEncoder.BeginEncodeHeaders(_headersEnumerator, buffer, out var payloadLength);
216+
var done = HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, _headersEnumerator, buffer, out var payloadLength);
217217
FinishWritingHeaders(streamId, payloadLength, done);
218218
}
219219
catch (HPackEncodingException hex)
@@ -242,7 +242,7 @@ private void FinishWritingHeaders(int streamId, int payloadLength, bool done)
242242
{
243243
_outgoingFrame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
244244

245-
done = _hpackEncoder.ContinueEncodeHeaders(_headersEnumerator, buffer, out payloadLength);
245+
done = HPackHeaderWriter.ContinueEncodeHeaders(_hpackEncoder, _headersEnumerator, buffer, out payloadLength);
246246
_outgoingFrame.PayloadLength = payloadLength;
247247

248248
if (done)

0 commit comments

Comments
 (0)