diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs
index 6673afaa1cc1..cfbd7ab1e2dc 100644
--- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs
+++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs
@@ -127,6 +127,7 @@ public partial class KestrelServerOptions
{
public KestrelServerOptions() { }
public bool AddServerHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
+ public bool AllowResponseHeaderCompression { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
public Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader ConfigurationLoader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
diff --git a/src/Servers/Kestrel/Core/src/CoreStrings.resx b/src/Servers/Kestrel/Core/src/CoreStrings.resx
index f84ed1d2cef1..1d270be8ee2d 100644
--- a/src/Servers/Kestrel/Core/src/CoreStrings.resx
+++ b/src/Servers/Kestrel/Core/src/CoreStrings.resx
@@ -599,4 +599,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
Unable to resolve service for type 'Microsoft.AspNetCore.Connections.IConnectionListenerFactory' while attempting to activate 'Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer'.
+
+ A value greater than or equal to zero is required.
+
\ No newline at end of file
diff --git a/src/Servers/Kestrel/Core/src/Http2Limits.cs b/src/Servers/Kestrel/Core/src/Http2Limits.cs
index 68d101f07647..713159f66a66 100644
--- a/src/Servers/Kestrel/Core/src/Http2Limits.cs
+++ b/src/Servers/Kestrel/Core/src/Http2Limits.cs
@@ -39,9 +39,9 @@ public int MaxStreamsPerConnection
}
///
- /// Limits the size of the header compression table, in octets, the HPACK decoder on the server can use.
+ /// Limits the size of the header compression tables, in octets, the HPACK encoder and decoder on the server can use.
///
- /// Value must be greater than 0, defaults to 4096
+ /// Value must be greater than or equal to 0, defaults to 4096
///
///
public int HeaderTableSize
@@ -49,9 +49,9 @@ public int HeaderTableSize
get => _headerTableSize;
set
{
- if (value <= 0)
+ if (value < 0)
{
- throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanZeroRequired);
+ throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanOrEqualToZeroRequired);
}
_headerTableSize = value;
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs
index 1598a18c7f67..33c7b920f395 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs
@@ -4,7 +4,6 @@
using System;
using System.Net.Http;
using System.Net.Http.HPack;
-using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
@@ -13,57 +12,105 @@ internal static class HPackHeaderWriter
///
/// Begin encoding headers in the first HEADERS frame.
///
- public static bool BeginEncodeHeaders(int statusCode, Http2HeadersEnumerator headersEnumerator, Span buffer, out int length)
+ public static bool BeginEncodeHeaders(int statusCode, HPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span buffer, out int length)
{
- if (!HPackEncoder.EncodeStatusHeader(statusCode, buffer, out var statusCodeLength))
+ length = 0;
+
+ if (!hpackEncoder.EnsureDynamicTableSizeUpdate(buffer, out var sizeUpdateLength))
+ {
+ throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
+ }
+ length += sizeUpdateLength;
+
+ if (!EncodeStatusHeader(statusCode, hpackEncoder, buffer.Slice(length), out var statusCodeLength))
{
throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
}
+ length += statusCodeLength;
if (!headersEnumerator.MoveNext())
{
- length = statusCodeLength;
return true;
}
// We're ok with not throwing if no headers were encoded because we've already encoded the status.
// There is a small chance that the header will encode if there is no other content in the next HEADERS frame.
- var done = EncodeHeaders(headersEnumerator, buffer.Slice(statusCodeLength), throwIfNoneEncoded: false, out var headersLength);
- length = statusCodeLength + headersLength;
-
+ var done = EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer.Slice(length), throwIfNoneEncoded: false, out var headersLength);
+ length += headersLength;
return done;
}
///
/// Begin encoding headers in the first HEADERS frame.
///
- public static bool BeginEncodeHeaders(Http2HeadersEnumerator headersEnumerator, Span buffer, out int length)
+ public static bool BeginEncodeHeaders(HPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span buffer, out int length)
{
+ length = 0;
+
+ if (!hpackEncoder.EnsureDynamicTableSizeUpdate(buffer, out var sizeUpdateLength))
+ {
+ throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
+ }
+ length += sizeUpdateLength;
+
if (!headersEnumerator.MoveNext())
{
- length = 0;
return true;
}
- return EncodeHeaders(headersEnumerator, buffer, throwIfNoneEncoded: true, out length);
+ var done = EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer.Slice(length), throwIfNoneEncoded: true, out var headersLength);
+ length += headersLength;
+ return done;
}
///
/// Continue encoding headers in the next HEADERS frame. The enumerator should already have a current value.
///
- public static bool ContinueEncodeHeaders(Http2HeadersEnumerator headersEnumerator, Span buffer, out int length)
+ public static bool ContinueEncodeHeaders(HPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span buffer, out int length)
{
- return EncodeHeaders(headersEnumerator, buffer, throwIfNoneEncoded: true, out length);
+ return EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer, throwIfNoneEncoded: true, out length);
+ }
+
+ private static bool EncodeStatusHeader(int statusCode, HPackEncoder hpackEncoder, Span buffer, out int length)
+ {
+ switch (statusCode)
+ {
+ case 200:
+ case 204:
+ case 206:
+ case 304:
+ case 400:
+ case 404:
+ case 500:
+ // Status codes which exist in the HTTP/2 StaticTable.
+ return HPackEncoder.EncodeIndexedHeaderField(H2StaticTable.StatusIndex[statusCode], buffer, out length);
+ default:
+ const string name = ":status";
+ var value = StatusCodes.ToStatusString(statusCode);
+ return hpackEncoder.EncodeHeader(buffer, H2StaticTable.Status200, HeaderEncodingHint.Index, name, value, out length);
+ }
}
- private static bool EncodeHeaders(Http2HeadersEnumerator headersEnumerator, Span buffer, bool throwIfNoneEncoded, out int length)
+ private static bool EncodeHeadersCore(HPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span buffer, bool throwIfNoneEncoded, out int length)
{
var currentLength = 0;
do
{
- if (!EncodeHeader(headersEnumerator.KnownHeaderType, headersEnumerator.Current.Key, headersEnumerator.Current.Value, buffer.Slice(currentLength), out int headerLength))
+ var staticTableId = headersEnumerator.HPackStaticTableId;
+ var name = headersEnumerator.Current.Key;
+ var value = headersEnumerator.Current.Value;
+
+ var hint = ResolveHeaderEncodingHint(staticTableId, name);
+
+ if (!hpackEncoder.EncodeHeader(
+ buffer.Slice(currentLength),
+ staticTableId,
+ hint,
+ name,
+ value,
+ out var headerLength))
{
- // The the header wasn't written and no headers have been written then the header is too large.
+ // If the header wasn't written, and no headers have been written, then the header is too large.
// Throw an error to avoid an infinite loop of attempting to write large header.
if (currentLength == 0 && throwIfNoneEncoded)
{
@@ -79,79 +126,48 @@ private static bool EncodeHeaders(Http2HeadersEnumerator headersEnumerator, Span
while (headersEnumerator.MoveNext());
length = currentLength;
-
return true;
}
- private static bool EncodeHeader(KnownHeaderType knownHeaderType, string name, string value, Span buffer, out int length)
+ private static HeaderEncodingHint ResolveHeaderEncodingHint(int staticTableId, string name)
{
- var hPackStaticTableId = GetResponseHeaderStaticTableId(knownHeaderType);
-
- if (hPackStaticTableId == -1)
+ HeaderEncodingHint hint;
+ if (IsSensitive(staticTableId, name))
{
- return HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingNewName(name, value, buffer, out length);
+ hint = HeaderEncodingHint.NeverIndex;
+ }
+ else if (IsNotDynamicallyIndexed(staticTableId))
+ {
+ hint = HeaderEncodingHint.IgnoreIndex;
}
else
{
- return HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexing(hPackStaticTableId, value, buffer, out length);
+ hint = HeaderEncodingHint.Index;
}
+
+ return hint;
}
- private static int GetResponseHeaderStaticTableId(KnownHeaderType responseHeaderType)
+ private static bool IsSensitive(int staticTableIndex, string name)
{
- switch (responseHeaderType)
+ // Set-Cookie could contain sensitive data.
+ if (staticTableIndex == H2StaticTable.SetCookie)
{
- case KnownHeaderType.CacheControl:
- return H2StaticTable.CacheControl;
- case KnownHeaderType.Date:
- return H2StaticTable.Date;
- case KnownHeaderType.TransferEncoding:
- return H2StaticTable.TransferEncoding;
- case KnownHeaderType.Via:
- return H2StaticTable.Via;
- case KnownHeaderType.Allow:
- return H2StaticTable.Allow;
- case KnownHeaderType.ContentType:
- return H2StaticTable.ContentType;
- case KnownHeaderType.ContentEncoding:
- return H2StaticTable.ContentEncoding;
- case KnownHeaderType.ContentLanguage:
- return H2StaticTable.ContentLanguage;
- case KnownHeaderType.ContentLocation:
- return H2StaticTable.ContentLocation;
- case KnownHeaderType.ContentRange:
- return H2StaticTable.ContentRange;
- case KnownHeaderType.Expires:
- return H2StaticTable.Expires;
- case KnownHeaderType.LastModified:
- return H2StaticTable.LastModified;
- case KnownHeaderType.AcceptRanges:
- return H2StaticTable.AcceptRanges;
- case KnownHeaderType.Age:
- return H2StaticTable.Age;
- case KnownHeaderType.ETag:
- return H2StaticTable.ETag;
- case KnownHeaderType.Location:
- return H2StaticTable.Location;
- case KnownHeaderType.ProxyAuthenticate:
- return H2StaticTable.ProxyAuthenticate;
- case KnownHeaderType.RetryAfter:
- return H2StaticTable.RetryAfter;
- case KnownHeaderType.Server:
- return H2StaticTable.Server;
- case KnownHeaderType.SetCookie:
- return H2StaticTable.SetCookie;
- case KnownHeaderType.Vary:
- return H2StaticTable.Vary;
- case KnownHeaderType.WWWAuthenticate:
- return H2StaticTable.WwwAuthenticate;
- case KnownHeaderType.AccessControlAllowOrigin:
- return H2StaticTable.AccessControlAllowOrigin;
- case KnownHeaderType.ContentLength:
- return H2StaticTable.ContentLength;
- default:
- return -1;
+ return true;
+ }
+ if (string.Equals(name, "Content-Disposition", StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
}
+
+ return false;
+ }
+
+ private static bool IsNotDynamicallyIndexed(int staticTableIndex)
+ {
+ // Content-Length is added to static content. Content length is different for each
+ // file, and is unlikely to be reused because of browser caching.
+ return staticTableIndex == H2StaticTable.ContentLength;
}
}
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs
index 8666bebc64a4..acf81b3e1293 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs
@@ -87,7 +87,7 @@ public Http2Connection(HttpConnectionContext context)
httpLimits.MinResponseDataRate,
context.ConnectionId,
context.MemoryPool,
- context.ServiceContext.Log);
+ context.ServiceContext);
var inputOptions = new PipeOptions(pool: context.MemoryPool,
readerScheduler: context.ServiceContext.Scheduler,
@@ -743,6 +743,15 @@ private Task ProcessSettingsFrameAsync(in ReadOnlySequence payload)
}
}
+ // Maximum HPack encoder size is limited by Http2Limits.HeaderTableSize, configured max the server.
+ //
+ // Note that the client HPack decoder doesn't care about the ACK so we don't need to lock sending the
+ // ACK and updating the table size on the server together.
+ // The client will wait until a size agreed upon by it (sent in SETTINGS_HEADER_TABLE_SIZE) and the
+ // server (sent as a dynamic table size update in the next HEADERS frame) is received before applying
+ // the new size.
+ _frameWriter.UpdateMaxHeaderTableSize(Math.Min(_clientSettings.HeaderTableSize, (uint)Limits.Http2.HeaderTableSize));
+
return ackTask.AsTask();
}
catch (Http2SettingsParameterOutOfRangeException ex)
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs
index fd8d5530678c..ca428eae8a5c 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs
@@ -38,6 +38,7 @@ internal class Http2FrameWriter
private readonly ITimeoutControl _timeoutControl;
private readonly MinDataRate _minResponseDataRate;
private readonly TimingPipeFlusher _flusher;
+ private readonly HPackEncoder _hpackEncoder;
private uint _maxFrameSize = Http2PeerSettings.MinAllowedMaxFrameSize;
private byte[] _headerEncodingBuffer;
@@ -55,7 +56,7 @@ public Http2FrameWriter(
MinDataRate minResponseDataRate,
string connectionId,
MemoryPool memoryPool,
- IKestrelTrace log)
+ ServiceContext serviceContext)
{
// Allow appending more data to the PipeWriter when a flush is pending.
_outputWriter = new ConcurrentPipeWriter(outputPipeWriter, memoryPool, _writeLock);
@@ -63,12 +64,22 @@ public Http2FrameWriter(
_http2Connection = http2Connection;
_connectionOutputFlowControl = connectionOutputFlowControl;
_connectionId = connectionId;
- _log = log;
+ _log = serviceContext.Log;
_timeoutControl = timeoutControl;
_minResponseDataRate = minResponseDataRate;
- _flusher = new TimingPipeFlusher(_outputWriter, timeoutControl, log);
+ _flusher = new TimingPipeFlusher(_outputWriter, timeoutControl, serviceContext.Log);
_outgoingFrame = new Http2Frame();
_headerEncodingBuffer = new byte[_maxFrameSize];
+
+ _hpackEncoder = new HPackEncoder(serviceContext.ServerOptions.AllowResponseHeaderCompression);
+ }
+
+ public void UpdateMaxHeaderTableSize(uint maxHeaderTableSize)
+ {
+ lock (_writeLock)
+ {
+ _hpackEncoder.UpdateMaxHeaderTableSize(maxHeaderTableSize);
+ }
}
public void UpdateMaxFrameSize(uint maxFrameSize)
@@ -175,7 +186,7 @@ public void WriteResponseHeaders(int streamId, int statusCode, Http2HeadersFrame
_headersEnumerator.Initialize(headers);
_outgoingFrame.PrepareHeaders(headerFrameFlags, streamId);
var buffer = _headerEncodingBuffer.AsSpan();
- var done = HPackHeaderWriter.BeginEncodeHeaders(statusCode, _headersEnumerator, buffer, out var payloadLength);
+ var done = HPackHeaderWriter.BeginEncodeHeaders(statusCode, _hpackEncoder, _headersEnumerator, buffer, out var payloadLength);
FinishWritingHeaders(streamId, payloadLength, done);
}
catch (HPackEncodingException hex)
@@ -201,7 +212,7 @@ public ValueTask WriteResponseTrailers(int streamId, HttpResponseTr
_headersEnumerator.Initialize(headers);
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.END_STREAM, streamId);
var buffer = _headerEncodingBuffer.AsSpan();
- var done = HPackHeaderWriter.BeginEncodeHeaders(_headersEnumerator, buffer, out var payloadLength);
+ var done = HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, _headersEnumerator, buffer, out var payloadLength);
FinishWritingHeaders(streamId, payloadLength, done);
}
catch (HPackEncodingException hex)
@@ -230,7 +241,7 @@ private void FinishWritingHeaders(int streamId, int payloadLength, bool done)
{
_outgoingFrame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
- done = HPackHeaderWriter.ContinueEncodeHeaders(_headersEnumerator, buffer, out payloadLength);
+ done = HPackHeaderWriter.ContinueEncodeHeaders(_hpackEncoder, _headersEnumerator, buffer, out payloadLength);
_outgoingFrame.PayloadLength = payloadLength;
if (done)
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs
index 421650b9fde2..db21bfb0fddd 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeaderEnumerator.cs
@@ -3,6 +3,7 @@
using System.Collections;
using System.Collections.Generic;
+using System.Net.Http.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.Extensions.Primitives;
@@ -15,8 +16,9 @@ internal sealed class Http2HeadersEnumerator : IEnumerator> _genericEnumerator;
private StringValues.Enumerator _stringValuesEnumerator;
+ private KnownHeaderType _knownHeaderType;
- public KnownHeaderType KnownHeaderType { get; private set; }
+ public int HPackStaticTableId => GetResponseHeaderStaticTableId(_knownHeaderType);
public KeyValuePair Current { get; private set; }
object IEnumerator.Current => Current;
@@ -33,7 +35,7 @@ public void Initialize(HttpResponseHeaders headers)
_stringValuesEnumerator = default;
Current = default;
- KnownHeaderType = default;
+ _knownHeaderType = default;
}
public void Initialize(HttpResponseTrailers headers)
@@ -45,7 +47,7 @@ public void Initialize(HttpResponseTrailers headers)
_stringValuesEnumerator = default;
Current = default;
- KnownHeaderType = default;
+ _knownHeaderType = default;
}
public void Initialize(IDictionary headers)
@@ -57,7 +59,7 @@ public void Initialize(IDictionary headers)
_stringValuesEnumerator = default;
Current = default;
- KnownHeaderType = default;
+ _knownHeaderType = default;
}
public bool MoveNext()
@@ -110,7 +112,7 @@ private bool TryGetNextStringEnumerator(out StringValues.Enumerator enumerator)
else
{
enumerator = _genericEnumerator.Current.Value.GetEnumerator();
- KnownHeaderType = default;
+ _knownHeaderType = default;
return true;
}
}
@@ -124,7 +126,7 @@ private bool TryGetNextStringEnumerator(out StringValues.Enumerator enumerator)
else
{
enumerator = _trailersEnumerator.Current.Value.GetEnumerator();
- KnownHeaderType = _trailersEnumerator.CurrentKnownType;
+ _knownHeaderType = _trailersEnumerator.CurrentKnownType;
return true;
}
}
@@ -138,7 +140,7 @@ private bool TryGetNextStringEnumerator(out StringValues.Enumerator enumerator)
else
{
enumerator = _headersEnumerator.Current.Value.GetEnumerator();
- KnownHeaderType = _headersEnumerator.CurrentKnownType;
+ _knownHeaderType = _headersEnumerator.CurrentKnownType;
return true;
}
}
@@ -159,11 +161,68 @@ public void Reset()
_headersEnumerator.Reset();
}
_stringValuesEnumerator = default;
- KnownHeaderType = default;
+ _knownHeaderType = default;
}
public void Dispose()
{
}
+
+ internal static int GetResponseHeaderStaticTableId(KnownHeaderType responseHeaderType)
+ {
+ switch (responseHeaderType)
+ {
+ case KnownHeaderType.CacheControl:
+ return H2StaticTable.CacheControl;
+ case KnownHeaderType.Date:
+ return H2StaticTable.Date;
+ case KnownHeaderType.TransferEncoding:
+ return H2StaticTable.TransferEncoding;
+ case KnownHeaderType.Via:
+ return H2StaticTable.Via;
+ case KnownHeaderType.Allow:
+ return H2StaticTable.Allow;
+ case KnownHeaderType.ContentType:
+ return H2StaticTable.ContentType;
+ case KnownHeaderType.ContentEncoding:
+ return H2StaticTable.ContentEncoding;
+ case KnownHeaderType.ContentLanguage:
+ return H2StaticTable.ContentLanguage;
+ case KnownHeaderType.ContentLocation:
+ return H2StaticTable.ContentLocation;
+ case KnownHeaderType.ContentRange:
+ return H2StaticTable.ContentRange;
+ case KnownHeaderType.Expires:
+ return H2StaticTable.Expires;
+ case KnownHeaderType.LastModified:
+ return H2StaticTable.LastModified;
+ case KnownHeaderType.AcceptRanges:
+ return H2StaticTable.AcceptRanges;
+ case KnownHeaderType.Age:
+ return H2StaticTable.Age;
+ case KnownHeaderType.ETag:
+ return H2StaticTable.ETag;
+ case KnownHeaderType.Location:
+ return H2StaticTable.Location;
+ case KnownHeaderType.ProxyAuthenticate:
+ return H2StaticTable.ProxyAuthenticate;
+ case KnownHeaderType.RetryAfter:
+ return H2StaticTable.RetryAfter;
+ case KnownHeaderType.Server:
+ return H2StaticTable.Server;
+ case KnownHeaderType.SetCookie:
+ return H2StaticTable.SetCookie;
+ case KnownHeaderType.Vary:
+ return H2StaticTable.Vary;
+ case KnownHeaderType.WWWAuthenticate:
+ return H2StaticTable.WwwAuthenticate;
+ case KnownHeaderType.AccessControlAllowOrigin:
+ return H2StaticTable.AccessControlAllowOrigin;
+ case KnownHeaderType.ContentLength:
+ return H2StaticTable.ContentLength;
+ default:
+ return -1;
+ }
+ }
}
}
diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs
index 71def30d7399..c849b84caf1c 100644
--- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs
+++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs
@@ -38,6 +38,16 @@ public class KestrelServerOptions
///
public bool AddServerHeader { get; set; } = true;
+ ///
+ /// Gets or sets a value that controls whether dynamic compression of response headers is allowed.
+ /// For more information about the security considerations of HPack dynamic header compression, visit
+ /// https://tools.ietf.org/html/rfc7541#section-7.
+ ///
+ ///
+ /// Defaults to true.
+ ///
+ public bool AllowResponseHeaderCompression { get; set; } = true;
+
///
/// Gets or sets a value that controls whether synchronous IO is allowed for the and
///
diff --git a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
index 72a38463da28..73f77bb7730c 100644
--- a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
+++ b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
@@ -1,4 +1,4 @@
-
+
Core components of ASP.NET Core Kestrel cross-platform web server.
@@ -16,9 +16,10 @@
-
-
-
+
+
+
+
diff --git a/src/Servers/Kestrel/Core/test/HPackHeaderWriterTests.cs b/src/Servers/Kestrel/Core/test/HPackHeaderWriterTests.cs
index 3b290d712be6..bab351521168 100644
--- a/src/Servers/Kestrel/Core/test/HPackHeaderWriterTests.cs
+++ b/src/Servers/Kestrel/Core/test/HPackHeaderWriterTests.cs
@@ -1,199 +1,199 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// 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.Linq;
-using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
-using Microsoft.Extensions.Primitives;
-using Xunit;
-
-namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
-{
- public class HPackHeaderWriterTests
- {
- public static TheoryData[], byte[], int?> SinglePayloadData
- {
- get
- {
- var data = new TheoryData[], byte[], int?>();
-
- // Lowercase header name letters only
- data.Add(
- new[]
- {
- new KeyValuePair("CustomHeader", "CustomValue"),
- },
- new byte[]
- {
- // 0 12 c u s t o m
- 0x00, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
- // h e a d e r 11 C
- 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0b, 0x43,
- // u s t o m V a l
- 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x61, 0x6c,
- // u e
- 0x75, 0x65
- },
- null);
- // Lowercase header name letters only
- data.Add(
- new[]
- {
- new KeyValuePair("CustomHeader!#$%&'*+-.^_`|~", "CustomValue"),
- },
- new byte[]
- {
- // 0 27 c u s t o m
- 0x00, 0x1b, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
- // h e a d e r ! #
- 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x21, 0x23,
- // $ % & ' * + - .
- 0x24, 0x25, 0x26, 0x27, 0x2a, 0x2b, 0x2d, 0x2e,
- // ^ _ ` | ~ 11 C u
- 0x5e, 0x5f, 0x60, 0x7c, 0x7e, 0x0b, 0x43, 0x75,
- // s t o m V a l u
- 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x61, 0x6c, 0x75,
- // e
- 0x65
- },
- null);
- // Single Payload
- data.Add(
- new[]
- {
- new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"),
- new KeyValuePair("content-type", "text/html; charset=utf-8"),
- new KeyValuePair("server", "Kestrel")
- },
- new byte[]
- {
- 0x88, 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d,
- 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20,
- 0x4a, 0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37,
- 0x20, 0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33,
- 0x30, 0x20, 0x47, 0x4d, 0x54, 0x00, 0x0c, 0x63,
- 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74,
- 0x79, 0x70, 0x65, 0x18, 0x74, 0x65, 0x78, 0x74,
- 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x20, 0x63,
- 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x75,
- 0x74, 0x66, 0x2d, 0x38, 0x00, 0x06, 0x73, 0x65,
- 0x72, 0x76, 0x65, 0x72, 0x07, 0x4b, 0x65, 0x73,
- 0x74, 0x72, 0x65, 0x6c
- },
- 200);
-
- return data;
- }
- }
-
- [Theory]
- [MemberData(nameof(SinglePayloadData))]
- public void EncodesHeadersInSinglePayloadWhenSpaceAvailable(KeyValuePair[] headers, byte[] expectedPayload, int? statusCode)
- {
- var payload = new byte[1024];
- var length = 0;
- if (statusCode.HasValue)
- {
- Assert.True(HPackHeaderWriter.BeginEncodeHeaders(statusCode.Value, GetHeadersEnumerator(headers), payload, out length));
- }
- else
- {
- Assert.True(HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), payload, out length));
- }
- Assert.Equal(expectedPayload.Length, length);
-
- for (var i = 0; i < length; i++)
- {
- Assert.True(expectedPayload[i] == payload[i], $"{expectedPayload[i]} != {payload[i]} at {i} (len {length})");
- }
-
- Assert.Equal(expectedPayload, new ArraySegment(payload, 0, length));
- }
-
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void EncodesHeadersInMultiplePayloadsWhenSpaceNotAvailable(bool exactSize)
- {
- var statusCode = 200;
- var headers = new[]
- {
- new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"),
- new KeyValuePair("content-type", "text/html; charset=utf-8"),
- new KeyValuePair("server", "Kestrel")
- };
-
- var expectedStatusCodePayload = new byte[]
- {
- 0x88
- };
-
- var expectedDateHeaderPayload = new byte[]
- {
- 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d, 0x4d,
- 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20, 0x4a,
- 0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37, 0x20,
- 0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33, 0x30,
- 0x20, 0x47, 0x4d, 0x54
- };
-
- var expectedContentTypeHeaderPayload = new byte[]
- {
- 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
- 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x18, 0x74,
- 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c,
- 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
- 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38
- };
-
- var expectedServerHeaderPayload = new byte[]
- {
- 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
- 0x07, 0x4b, 0x65, 0x73, 0x74, 0x72, 0x65, 0x6c
- };
-
- Span payload = new byte[1024];
- var offset = 0;
- var headerEnumerator = GetHeadersEnumerator(headers);
-
- // When !exactSize, slices are one byte short of fitting the next header
- var sliceLength = expectedStatusCodePayload.Length + (exactSize ? 0 : expectedDateHeaderPayload.Length - 1);
- Assert.False(HPackHeaderWriter.BeginEncodeHeaders(statusCode, headerEnumerator, payload.Slice(offset, sliceLength), out var length));
- Assert.Equal(expectedStatusCodePayload.Length, length);
- Assert.Equal(expectedStatusCodePayload, payload.Slice(0, length).ToArray());
-
- offset += length;
-
- sliceLength = expectedDateHeaderPayload.Length + (exactSize ? 0 : expectedContentTypeHeaderPayload.Length - 1);
- Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length));
- Assert.Equal(expectedDateHeaderPayload.Length, length);
- Assert.Equal(expectedDateHeaderPayload, payload.Slice(offset, length).ToArray());
-
- offset += length;
-
- sliceLength = expectedContentTypeHeaderPayload.Length + (exactSize ? 0 : expectedServerHeaderPayload.Length - 1);
- Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length));
- Assert.Equal(expectedContentTypeHeaderPayload.Length, length);
- Assert.Equal(expectedContentTypeHeaderPayload, payload.Slice(offset, length).ToArray());
-
- offset += length;
-
- sliceLength = expectedServerHeaderPayload.Length;
- Assert.True(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length));
- Assert.Equal(expectedServerHeaderPayload.Length, length);
- Assert.Equal(expectedServerHeaderPayload, payload.Slice(offset, length).ToArray());
- }
-
- private static Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable> headers)
- {
- var groupedHeaders = headers
- .GroupBy(k => k.Key)
- .ToDictionary(g => g.Key, g => new StringValues(g.Select(gg => gg.Value).ToArray()));
-
- var enumerator = new Http2HeadersEnumerator();
- enumerator.Initialize(groupedHeaders);
- return enumerator;
- }
- }
-}
+//// Copyright (c) .NET Foundation. All rights reserved.
+//// 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.Linq;
+//using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
+//using Microsoft.Extensions.Primitives;
+//using Xunit;
+
+//namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
+//{
+// public class HPackHeaderWriterTests
+// {
+// public static TheoryData[], byte[], int?> SinglePayloadData
+// {
+// get
+// {
+// var data = new TheoryData[], byte[], int?>();
+
+// // Lowercase header name letters only
+// data.Add(
+// new[]
+// {
+// new KeyValuePair("CustomHeader", "CustomValue"),
+// },
+// new byte[]
+// {
+// // 0 12 c u s t o m
+// 0x00, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
+// // h e a d e r 11 C
+// 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0b, 0x43,
+// // u s t o m V a l
+// 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x61, 0x6c,
+// // u e
+// 0x75, 0x65
+// },
+// null);
+// // Lowercase header name letters only
+// data.Add(
+// new[]
+// {
+// new KeyValuePair("CustomHeader!#$%&'*+-.^_`|~", "CustomValue"),
+// },
+// new byte[]
+// {
+// // 0 27 c u s t o m
+// 0x00, 0x1b, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
+// // h e a d e r ! #
+// 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x21, 0x23,
+// // $ % & ' * + - .
+// 0x24, 0x25, 0x26, 0x27, 0x2a, 0x2b, 0x2d, 0x2e,
+// // ^ _ ` | ~ 11 C u
+// 0x5e, 0x5f, 0x60, 0x7c, 0x7e, 0x0b, 0x43, 0x75,
+// // s t o m V a l u
+// 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x61, 0x6c, 0x75,
+// // e
+// 0x65
+// },
+// null);
+// // Single Payload
+// data.Add(
+// new[]
+// {
+// new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"),
+// new KeyValuePair("content-type", "text/html; charset=utf-8"),
+// new KeyValuePair("server", "Kestrel")
+// },
+// new byte[]
+// {
+// 0x88, 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d,
+// 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20,
+// 0x4a, 0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37,
+// 0x20, 0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33,
+// 0x30, 0x20, 0x47, 0x4d, 0x54, 0x00, 0x0c, 0x63,
+// 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74,
+// 0x79, 0x70, 0x65, 0x18, 0x74, 0x65, 0x78, 0x74,
+// 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x20, 0x63,
+// 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x75,
+// 0x74, 0x66, 0x2d, 0x38, 0x00, 0x06, 0x73, 0x65,
+// 0x72, 0x76, 0x65, 0x72, 0x07, 0x4b, 0x65, 0x73,
+// 0x74, 0x72, 0x65, 0x6c
+// },
+// 200);
+
+// return data;
+// }
+// }
+
+// [Theory]
+// [MemberData(nameof(SinglePayloadData))]
+// public void EncodesHeadersInSinglePayloadWhenSpaceAvailable(KeyValuePair[] headers, byte[] expectedPayload, int? statusCode)
+// {
+// var payload = new byte[1024];
+// var length = 0;
+// if (statusCode.HasValue)
+// {
+// Assert.True(HPackHeaderWriter.BeginEncodeHeaders(statusCode.Value, GetHeadersEnumerator(headers), payload, out length));
+// }
+// else
+// {
+// Assert.True(HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), payload, out length));
+// }
+// Assert.Equal(expectedPayload.Length, length);
+
+// for (var i = 0; i < length; i++)
+// {
+// Assert.True(expectedPayload[i] == payload[i], $"{expectedPayload[i]} != {payload[i]} at {i} (len {length})");
+// }
+
+// Assert.Equal(expectedPayload, new ArraySegment(payload, 0, length));
+// }
+
+// [Theory]
+// [InlineData(true)]
+// [InlineData(false)]
+// public void EncodesHeadersInMultiplePayloadsWhenSpaceNotAvailable(bool exactSize)
+// {
+// var statusCode = 200;
+// var headers = new[]
+// {
+// new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"),
+// new KeyValuePair("content-type", "text/html; charset=utf-8"),
+// new KeyValuePair("server", "Kestrel")
+// };
+
+// var expectedStatusCodePayload = new byte[]
+// {
+// 0x88
+// };
+
+// var expectedDateHeaderPayload = new byte[]
+// {
+// 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d, 0x4d,
+// 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20, 0x4a,
+// 0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37, 0x20,
+// 0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33, 0x30,
+// 0x20, 0x47, 0x4d, 0x54
+// };
+
+// var expectedContentTypeHeaderPayload = new byte[]
+// {
+// 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
+// 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x18, 0x74,
+// 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c,
+// 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
+// 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38
+// };
+
+// var expectedServerHeaderPayload = new byte[]
+// {
+// 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
+// 0x07, 0x4b, 0x65, 0x73, 0x74, 0x72, 0x65, 0x6c
+// };
+
+// Span payload = new byte[1024];
+// var offset = 0;
+// var headerEnumerator = GetHeadersEnumerator(headers);
+
+// // When !exactSize, slices are one byte short of fitting the next header
+// var sliceLength = expectedStatusCodePayload.Length + (exactSize ? 0 : expectedDateHeaderPayload.Length - 1);
+// Assert.False(HPackHeaderWriter.BeginEncodeHeaders(statusCode, headerEnumerator, payload.Slice(offset, sliceLength), out var length));
+// Assert.Equal(expectedStatusCodePayload.Length, length);
+// Assert.Equal(expectedStatusCodePayload, payload.Slice(0, length).ToArray());
+
+// offset += length;
+
+// sliceLength = expectedDateHeaderPayload.Length + (exactSize ? 0 : expectedContentTypeHeaderPayload.Length - 1);
+// Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length));
+// Assert.Equal(expectedDateHeaderPayload.Length, length);
+// Assert.Equal(expectedDateHeaderPayload, payload.Slice(offset, length).ToArray());
+
+// offset += length;
+
+// sliceLength = expectedContentTypeHeaderPayload.Length + (exactSize ? 0 : expectedServerHeaderPayload.Length - 1);
+// Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length));
+// Assert.Equal(expectedContentTypeHeaderPayload.Length, length);
+// Assert.Equal(expectedContentTypeHeaderPayload, payload.Slice(offset, length).ToArray());
+
+// offset += length;
+
+// sliceLength = expectedServerHeaderPayload.Length;
+// Assert.True(HPackHeaderWriter.ContinueEncodeHeaders(headerEnumerator, payload.Slice(offset, sliceLength), out length));
+// Assert.Equal(expectedServerHeaderPayload.Length, length);
+// Assert.Equal(expectedServerHeaderPayload, payload.Slice(offset, length).ToArray());
+// }
+
+// private static Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable> headers)
+// {
+// var groupedHeaders = headers
+// .GroupBy(k => k.Key)
+// .ToDictionary(g => g.Key, g => new StringValues(g.Select(gg => gg.Value).ToArray()));
+
+// var enumerator = new Http2HeadersEnumerator();
+// enumerator.Initialize(groupedHeaders);
+// return enumerator;
+// }
+// }
+//}
diff --git a/src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs b/src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs
index 6300fcf85bd9..30913d952e35 100644
--- a/src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs
+++ b/src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs
@@ -41,7 +41,7 @@ public async Task WriteWindowUpdate_UnsetsReservedBit()
{
// Arrange
var pipe = new Pipe(new PipeOptions(_dirtyMemoryPool, PipeScheduler.Inline, PipeScheduler.Inline));
- var frameWriter = new Http2FrameWriter(pipe.Writer, null, null, null, null, null, null, _dirtyMemoryPool, new Mock().Object);
+ var frameWriter = CreateFrameWriter(pipe);
// Act
await frameWriter.WriteWindowUpdateAsync(1, 1);
@@ -52,12 +52,22 @@ public async Task WriteWindowUpdate_UnsetsReservedBit()
Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x01 }, payload.Skip(Http2FrameReader.HeaderLength).Take(4).ToArray());
}
+ private Http2FrameWriter CreateFrameWriter(Pipe pipe)
+ {
+ var serviceContext = new Internal.ServiceContext
+ {
+ ServerOptions = new KestrelServerOptions(),
+ Log = new Mock().Object
+ };
+ return new Http2FrameWriter(pipe.Writer, null, null, null, null, null, null, _dirtyMemoryPool, serviceContext);
+ }
+
[Fact]
public async Task WriteGoAway_UnsetsReservedBit()
{
// Arrange
var pipe = new Pipe(new PipeOptions(_dirtyMemoryPool, PipeScheduler.Inline, PipeScheduler.Inline));
- var frameWriter = new Http2FrameWriter(pipe.Writer, null, null, null, null, null, null, _dirtyMemoryPool, new Mock().Object);
+ var frameWriter = CreateFrameWriter(pipe);
// Act
await frameWriter.WriteGoAwayAsync(1, Http2ErrorCode.NO_ERROR);
diff --git a/src/Servers/Kestrel/Core/test/Http2HPackEncoderTests.cs b/src/Servers/Kestrel/Core/test/Http2HPackEncoderTests.cs
new file mode 100644
index 000000000000..c9dcdb00ecaa
--- /dev/null
+++ b/src/Servers/Kestrel/Core/test/Http2HPackEncoderTests.cs
@@ -0,0 +1,479 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// 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.Linq;
+using System.Net.Http.HPack;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
+{
+ public class Http2HPackEncoderTests
+ {
+ [Fact]
+ public void BeginEncodeHeaders_Status302_NewIndexValue()
+ {
+ Span buffer = new byte[1024 * 16];
+
+ var headers = new HttpResponseHeaders();
+ var enumerator = new Http2HeadersEnumerator();
+ enumerator.Initialize(headers);
+
+ var hpackEncoder = new HPackEncoder();
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length));
+
+ var result = buffer.Slice(0, length).ToArray();
+ var hex = BitConverter.ToString(result);
+ Assert.Equal("48-03-33-30-32", hex);
+
+ var statusHeader = GetHeaderEntry(hpackEncoder, 0);
+ Assert.Equal(":status", statusHeader.Name);
+ Assert.Equal("302", statusHeader.Value);
+ }
+
+ [Fact]
+ public void BeginEncodeHeaders_CacheControlPrivate_NewIndexValue()
+ {
+ Span buffer = new byte[1024 * 16];
+
+ var headers = new HttpResponseHeaders();
+ headers.HeaderCacheControl = "private";
+
+ var enumerator = new Http2HeadersEnumerator();
+ enumerator.Initialize(headers);
+
+ var hpackEncoder = new HPackEncoder();
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length));
+
+ var result = buffer.Slice(5, length - 5).ToArray();
+ var hex = BitConverter.ToString(result);
+ Assert.Equal("58-07-70-72-69-76-61-74-65", hex);
+
+ var statusHeader = GetHeaderEntry(hpackEncoder, 0);
+ Assert.Equal("Cache-Control", statusHeader.Name);
+ Assert.Equal("private", statusHeader.Value);
+ }
+
+ [Fact]
+ public void BeginEncodeHeaders_MaxHeaderTableSizeExceeded_EvictionsToFit()
+ {
+ // Test follows example https://tools.ietf.org/html/rfc7541#appendix-C.5
+
+ Span buffer = new byte[1024 * 16];
+
+ var headers = new HttpResponseHeaders();
+ headers.HeaderCacheControl = "private";
+ headers.HeaderDate = "Mon, 21 Oct 2013 20:13:21 GMT";
+ headers.HeaderLocation = "https://www.example.com";
+
+ var enumerator = new Http2HeadersEnumerator();
+
+ var hpackEncoder = new HPackEncoder(maxHeaderTableSize: 256);
+
+ // First response
+ enumerator.Initialize(headers);
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length));
+
+ var result = buffer.Slice(0, length).ToArray();
+ var hex = BitConverter.ToString(result);
+ Assert.Equal(
+ "48-03-33-30-32-58-07-70-72-69-76-61-74-65-61-1D-" +
+ "4D-6F-6E-2C-20-32-31-20-4F-63-74-20-32-30-31-33-" +
+ "20-32-30-3A-31-33-3A-32-31-20-47-4D-54-6E-17-68-" +
+ "74-74-70-73-3A-2F-2F-77-77-77-2E-65-78-61-6D-70-" +
+ "6C-65-2E-63-6F-6D", hex);
+
+ var entries = GetHeaderEntries(hpackEncoder);
+ Assert.Collection(entries,
+ e =>
+ {
+ Assert.Equal("Location", e.Name);
+ Assert.Equal("https://www.example.com", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Date", e.Name);
+ Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Cache-Control", e.Name);
+ Assert.Equal("private", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal(":status", e.Name);
+ Assert.Equal("302", e.Value);
+ });
+
+ // Second response
+ enumerator.Initialize(headers);
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(307, hpackEncoder, enumerator, buffer, out length));
+
+ result = buffer.Slice(0, length).ToArray();
+ hex = BitConverter.ToString(result);
+ Assert.Equal("48-03-33-30-37-C1-C0-BF", hex);
+
+ entries = GetHeaderEntries(hpackEncoder);
+ Assert.Collection(entries,
+ e =>
+ {
+ Assert.Equal(":status", e.Name);
+ Assert.Equal("307", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Location", e.Name);
+ Assert.Equal("https://www.example.com", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Date", e.Name);
+ Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Cache-Control", e.Name);
+ Assert.Equal("private", e.Value);
+ });
+
+ // Third response
+ headers.HeaderDate = "Mon, 21 Oct 2013 20:13:22 GMT";
+ headers.HeaderContentEncoding = "gzip";
+ headers.HeaderSetCookie = "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1";
+
+ enumerator.Initialize(headers);
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(200, hpackEncoder, enumerator, buffer, out length));
+
+ result = buffer.Slice(0, length).ToArray();
+ hex = BitConverter.ToString(result);
+ Assert.Equal(
+ "88-C1-61-1D-4D-6F-6E-2C-20-32-31-20-4F-63-74-20-" +
+ "32-30-31-33-20-32-30-3A-31-33-3A-32-32-20-47-4D-" +
+ "54-5A-04-67-7A-69-70-C1-1F-28-38-66-6F-6F-3D-41-" +
+ "53-44-4A-4B-48-51-4B-42-5A-58-4F-51-57-45-4F-50-" +
+ "49-55-41-58-51-57-45-4F-49-55-3B-20-6D-61-78-2D-" +
+ "61-67-65-3D-33-36-30-30-3B-20-76-65-72-73-69-6F-" +
+ "6E-3D-31", hex);
+
+ entries = GetHeaderEntries(hpackEncoder);
+ Assert.Collection(entries,
+ e =>
+ {
+ Assert.Equal("Content-Encoding", e.Name);
+ Assert.Equal("gzip", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Date", e.Name);
+ Assert.Equal("Mon, 21 Oct 2013 20:13:22 GMT", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal(":status", e.Name);
+ Assert.Equal("307", e.Value);
+ },
+ e =>
+ {
+ Assert.Equal("Location", e.Name);
+ Assert.Equal("https://www.example.com", e.Value);
+ });
+ }
+
+ [Theory]
+ [InlineData("Set-Cookie", true)]
+ [InlineData("Content-Disposition", true)]
+ [InlineData("Content-Length", false)]
+ public void BeginEncodeHeaders_ExcludedHeaders_NotAddedToTable(string headerName, bool neverIndex)
+ {
+ Span buffer = new byte[1024 * 16];
+
+ var headers = new HttpResponseHeaders();
+ headers.Append(headerName, "1");
+
+ var enumerator = new Http2HeadersEnumerator();
+ enumerator.Initialize(headers);
+
+ var hpackEncoder = new HPackEncoder(maxHeaderTableSize: Http2PeerSettings.DefaultHeaderTableSize);
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out _));
+
+ if (neverIndex)
+ {
+ Assert.Equal(0x10, buffer[0] & 0x10);
+ }
+ else
+ {
+ Assert.Equal(0, buffer[0] & 0x40);
+ }
+
+ Assert.Empty(GetHeaderEntries(hpackEncoder));
+ }
+
+ [Fact]
+ public void BeginEncodeHeaders_HeaderExceedHeaderTableSize_NoIndexAndNoHeaderEntry()
+ {
+ Span buffer = new byte[1024 * 16];
+
+ var headers = new HttpResponseHeaders();
+ headers.Append("x-Custom", new string('!', (int)Http2PeerSettings.DefaultHeaderTableSize));
+
+ var enumerator = new Http2HeadersEnumerator();
+ enumerator.Initialize(headers);
+
+ var hpackEncoder = new HPackEncoder();
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(200, hpackEncoder, enumerator, buffer, out var length));
+
+ Assert.Empty(GetHeaderEntries(hpackEncoder));
+ }
+
+ public static TheoryData[], byte[], int?> SinglePayloadData
+ {
+ get
+ {
+ var data = new TheoryData[], byte[], int?>();
+
+ // Lowercase header name letters only
+ data.Add(
+ new[]
+ {
+ new KeyValuePair("CustomHeader", "CustomValue"),
+ },
+ new byte[]
+ {
+ // 12 c u s t o m
+ 0x40, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
+ // h e a d e r 11 C
+ 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0b, 0x43,
+ // u s t o m V a l
+ 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x61, 0x6c,
+ // u e
+ 0x75, 0x65
+ },
+ null);
+ // Lowercase header name letters only
+ data.Add(
+ new[]
+ {
+ new KeyValuePair("CustomHeader!#$%&'*+-.^_`|~", "CustomValue"),
+ },
+ new byte[]
+ {
+ // 27 c u s t o m
+ 0x40, 0x1b, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
+ // h e a d e r ! #
+ 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x21, 0x23,
+ // $ % & ' * + - .
+ 0x24, 0x25, 0x26, 0x27, 0x2a, 0x2b, 0x2d, 0x2e,
+ // ^ _ ` | ~ 11 C u
+ 0x5e, 0x5f, 0x60, 0x7c, 0x7e, 0x0b, 0x43, 0x75,
+ // s t o m V a l u
+ 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x61, 0x6c, 0x75,
+ // e
+ 0x65
+ },
+ null);
+ // Single Payload
+ data.Add(
+ new[]
+ {
+ new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"),
+ new KeyValuePair("content-type", "text/html; charset=utf-8"),
+ new KeyValuePair("server", "Kestrel")
+ },
+ new byte[]
+ {
+ 0x88, 0x40, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d,
+ 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20,
+ 0x4a, 0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37,
+ 0x20, 0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33,
+ 0x30, 0x20, 0x47, 0x4d, 0x54, 0x40, 0x0c, 0x63,
+ 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74,
+ 0x79, 0x70, 0x65, 0x18, 0x74, 0x65, 0x78, 0x74,
+ 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x20, 0x63,
+ 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x75,
+ 0x74, 0x66, 0x2d, 0x38, 0x40, 0x06, 0x73, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x07, 0x4b, 0x65, 0x73,
+ 0x74, 0x72, 0x65, 0x6c
+ },
+ 200);
+
+ return data;
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(SinglePayloadData))]
+ public void EncodesHeadersInSinglePayloadWhenSpaceAvailable(KeyValuePair[] headers, byte[] expectedPayload, int? statusCode)
+ {
+ HPackEncoder hpackEncoder = new HPackEncoder();
+
+ var payload = new byte[1024];
+ var length = 0;
+ if (statusCode.HasValue)
+ {
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(statusCode.Value, hpackEncoder, GetHeadersEnumerator(headers), payload, out length));
+ }
+ else
+ {
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, GetHeadersEnumerator(headers), payload, out length));
+ }
+ Assert.Equal(expectedPayload.Length, length);
+
+ for (var i = 0; i < length; i++)
+ {
+ Assert.True(expectedPayload[i] == payload[i], $"{expectedPayload[i]} != {payload[i]} at {i} (len {length})");
+ }
+
+ Assert.Equal(expectedPayload, new ArraySegment(payload, 0, length));
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void EncodesHeadersInMultiplePayloadsWhenSpaceNotAvailable(bool exactSize)
+ {
+ var statusCode = 200;
+ var headers = new[]
+ {
+ new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"),
+ new KeyValuePair("content-type", "text/html; charset=utf-8"),
+ new KeyValuePair("server", "Kestrel")
+ };
+
+ var expectedStatusCodePayload = new byte[]
+ {
+ 0x88
+ };
+
+ var expectedDateHeaderPayload = new byte[]
+ {
+ 0x40, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d, 0x4d,
+ 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20, 0x4a,
+ 0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37, 0x20,
+ 0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33, 0x30,
+ 0x20, 0x47, 0x4d, 0x54
+ };
+
+ var expectedContentTypeHeaderPayload = new byte[]
+ {
+ 0x40, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
+ 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x18, 0x74,
+ 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c,
+ 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
+ 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38
+ };
+
+ var expectedServerHeaderPayload = new byte[]
+ {
+ 0x40, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
+ 0x07, 0x4b, 0x65, 0x73, 0x74, 0x72, 0x65, 0x6c
+ };
+
+ var hpackEncoder = new HPackEncoder();
+
+ Span payload = new byte[1024];
+ var offset = 0;
+ var headerEnumerator = GetHeadersEnumerator(headers);
+
+ // When !exactSize, slices are one byte short of fitting the next header
+ var sliceLength = expectedStatusCodePayload.Length + (exactSize ? 0 : expectedDateHeaderPayload.Length - 1);
+ Assert.False(HPackHeaderWriter.BeginEncodeHeaders(statusCode, hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out var length));
+ Assert.Equal(expectedStatusCodePayload.Length, length);
+ Assert.Equal(expectedStatusCodePayload, payload.Slice(0, length).ToArray());
+
+ offset += length;
+
+ sliceLength = expectedDateHeaderPayload.Length + (exactSize ? 0 : expectedContentTypeHeaderPayload.Length - 1);
+ Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out length));
+ Assert.Equal(expectedDateHeaderPayload.Length, length);
+ Assert.Equal(expectedDateHeaderPayload, payload.Slice(offset, length).ToArray());
+
+ offset += length;
+
+ sliceLength = expectedContentTypeHeaderPayload.Length + (exactSize ? 0 : expectedServerHeaderPayload.Length - 1);
+ Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out length));
+ Assert.Equal(expectedContentTypeHeaderPayload.Length, length);
+ Assert.Equal(expectedContentTypeHeaderPayload, payload.Slice(offset, length).ToArray());
+
+ offset += length;
+
+ sliceLength = expectedServerHeaderPayload.Length;
+ Assert.True(HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out length));
+ Assert.Equal(expectedServerHeaderPayload.Length, length);
+ Assert.Equal(expectedServerHeaderPayload, payload.Slice(offset, length).ToArray());
+ }
+
+ [Fact]
+ public void BeginEncodeHeaders_MaxHeaderTableSizeUpdated_SizeUpdateInHeaders()
+ {
+ Span buffer = new byte[1024 * 16];
+
+ var hpackEncoder = new HPackEncoder();
+ hpackEncoder.UpdateMaxHeaderTableSize(100);
+
+ var enumerator = new Http2HeadersEnumerator();
+
+ // First request
+ enumerator.Initialize(new Dictionary());
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out var length));
+
+ Assert.Equal(2, length);
+
+ const byte DynamicTableSizeUpdateMask = 0xe0;
+
+ var integerDecoder = new IntegerDecoder();
+ Assert.False(integerDecoder.BeginTryDecode((byte)(buffer[0] & ~DynamicTableSizeUpdateMask), prefixLength: 5, out _));
+ Assert.True(integerDecoder.TryDecode(buffer[1], out var result));
+
+ Assert.Equal(100, result);
+
+ // Second request
+ enumerator.Initialize(new Dictionary());
+ Assert.True(HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out length));
+
+ Assert.Equal(0, length);
+ }
+
+ private static Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable> headers)
+ {
+ var groupedHeaders = headers
+ .GroupBy(k => k.Key)
+ .ToDictionary(g => g.Key, g => new StringValues(g.Select(gg => gg.Value).ToArray()));
+
+ var enumerator = new Http2HeadersEnumerator();
+ enumerator.Initialize(groupedHeaders);
+ return enumerator;
+ }
+
+ private EncoderHeaderEntry GetHeaderEntry(HPackEncoder encoder, int index)
+ {
+ var entry = encoder.Head;
+ while (index-- >= 0)
+ {
+ entry = entry.Before;
+ }
+ return entry;
+ }
+
+ private List GetHeaderEntries(HPackEncoder encoder)
+ {
+ var headers = new List();
+
+ var entry = encoder.Head;
+ while (entry.Before != encoder.Head)
+ {
+ entry = entry.Before;
+ headers.Add(entry);
+ };
+
+ return headers;
+ }
+ }
+}
diff --git a/src/Servers/Kestrel/Core/test/KestrelServerLimitsTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerLimitsTests.cs
index 5c7623337c8d..fd8146804e17 100644
--- a/src/Servers/Kestrel/Core/test/KestrelServerLimitsTests.cs
+++ b/src/Servers/Kestrel/Core/test/KestrelServerLimitsTests.cs
@@ -333,11 +333,10 @@ public void Http2HeaderTableSizeDefault()
[Theory]
[InlineData(int.MinValue)]
[InlineData(-1)]
- [InlineData(0)]
public void Http2HeaderTableSizeInvalid(int value)
{
var ex = Assert.Throws(() => new KestrelServerLimits().Http2.HeaderTableSize = value);
- Assert.StartsWith(CoreStrings.GreaterThanZeroRequired, ex.Message);
+ Assert.StartsWith(CoreStrings.GreaterThanOrEqualToZeroRequired, ex.Message);
}
[Fact]
diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionBenchmarkBase.cs
similarity index 92%
rename from src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionBenchmark.cs
rename to src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionBenchmarkBase.cs
index b3e8f15403ce..3886a38b09d0 100644
--- a/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionBenchmarkBase.cs
@@ -8,6 +8,7 @@
using System.IO;
using System.IO.Pipelines;
using System.Linq;
+using System.Net.Http.HPack;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
@@ -24,28 +25,25 @@
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
- public class Http2ConnectionBenchmark
+ public abstract class Http2ConnectionBenchmarkBase
{
private MemoryPool _memoryPool;
private HttpRequestHeaders _httpRequestHeaders;
private Http2Connection _connection;
+ private HPackEncoder _hpackEncoder;
private Http2HeadersEnumerator _requestHeadersEnumerator;
private int _currentStreamId;
private byte[] _headersBuffer;
private DuplexPipe.DuplexPipePair _connectionPair;
private Http2Frame _httpFrame;
- private string _responseData;
private int _dataWritten;
- [Params(0, 10, 1024 * 1024)]
- public int ResponseDataLength { get; set; }
+ protected abstract Task ProcessRequest(HttpContext httpContext);
- [GlobalSetup]
- public void GlobalSetup()
+ public virtual void GlobalSetup()
{
_memoryPool = SlabMemoryPoolFactory.Create();
_httpFrame = new Http2Frame();
- _responseData = new string('!', ResponseDataLength);
var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
@@ -58,6 +56,7 @@ public void GlobalSetup()
_httpRequestHeaders.Append(HeaderNames.Authority, new StringValues("localhost:80"));
_headersBuffer = new byte[1024 * 16];
+ _hpackEncoder = new HPackEncoder();
var serviceContext = new ServiceContext
{
@@ -83,7 +82,7 @@ public void GlobalSetup()
_currentStreamId = 1;
- _ = _connection.ProcessRequestsAsync(new DummyApplication(c => ResponseDataLength == 0 ? Task.CompletedTask : c.Response.WriteAsync(_responseData), new MockHttpContextFactory()));
+ _ = _connection.ProcessRequestsAsync(new DummyApplication(ProcessRequest, new MockHttpContextFactory()));
_connectionPair.Application.Output.Write(Http2Connection.ClientPreface);
_connectionPair.Application.Output.WriteSettings(new Http2PeerSettings
@@ -102,11 +101,11 @@ public void GlobalSetup()
}
[Benchmark]
- public async Task EmptyRequest()
+ public async Task MakeRequest()
{
_requestHeadersEnumerator.Initialize(_httpRequestHeaders);
_requestHeadersEnumerator.MoveNext();
- _connectionPair.Application.Output.WriteStartStream(streamId: _currentStreamId, _requestHeadersEnumerator, _headersBuffer, endStream: true, frame: _httpFrame);
+ _connectionPair.Application.Output.WriteStartStream(streamId: _currentStreamId, _hpackEncoder, _requestHeadersEnumerator, _headersBuffer, endStream: true, frame: _httpFrame);
await _connectionPair.Application.Output.FlushAsync();
while (true)
diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionEmptyBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionEmptyBenchmark.cs
new file mode 100644
index 000000000000..6859b5b675dc
--- /dev/null
+++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionEmptyBenchmark.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Performance
+{
+ public class Http2ConnectionBenchmark : Http2ConnectionBenchmarkBase
+ {
+ [Params(0, 128, 1024)]
+ public int ResponseDataLength { get; set; }
+
+ private string _responseData;
+
+ [GlobalSetup]
+ public override void GlobalSetup()
+ {
+ base.GlobalSetup();
+ _responseData = new string('!', ResponseDataLength);
+ }
+
+ protected override Task ProcessRequest(HttpContext httpContext)
+ {
+ return ResponseDataLength == 0 ? Task.CompletedTask : httpContext.Response.WriteAsync(_responseData);
+ }
+ }
+}
diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionHeadersBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionHeadersBenchmark.cs
new file mode 100644
index 000000000000..5ac19dd0b9ce
--- /dev/null
+++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Http2ConnectionHeadersBenchmark.cs
@@ -0,0 +1,48 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Performance
+{
+ public class Http2ConnectionHeadersBenchmark : Http2ConnectionBenchmarkBase
+ {
+ [Params(1, 4, 32)]
+ public int HeadersCount { get; set; }
+
+ [Params(true, false)]
+ public bool HeadersChange { get; set; }
+
+ private int _headerIndex;
+ private string[] _headerNames;
+
+ [GlobalSetup]
+ public override void GlobalSetup()
+ {
+ base.GlobalSetup();
+
+ _headerNames = new string[HeadersCount * (HeadersChange ? 1000 : 1)];
+ for (var i = 0; i < _headerNames.Length; i++)
+ {
+ _headerNames[i] = "CustomHeader" + i;
+ }
+ }
+
+ protected override Task ProcessRequest(HttpContext httpContext)
+ {
+ for (var i = 0; i < HeadersCount; i++)
+ {
+ var headerName = _headerNames[_headerIndex % HeadersCount];
+ httpContext.Response.Headers[headerName] = "The quick brown fox jumps over the lazy dog.";
+ if (HeadersChange)
+ {
+ _headerIndex++;
+ }
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Http2FrameWriterBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Http2FrameWriterBenchmark.cs
index 839558d1a3ad..ff58ed573d27 100644
--- a/src/Servers/Kestrel/perf/Kestrel.Performance/Http2FrameWriterBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Http2FrameWriterBenchmark.cs
@@ -36,7 +36,7 @@ public void GlobalSetup()
minResponseDataRate: null,
"TestConnectionId",
_memoryPool,
- new KestrelTrace(NullLogger.Instance));
+ new Core.Internal.ServiceContext());
_responseHeaders = new HttpResponseHeaders();
_responseHeaders.HeaderContentType = "application/json";
diff --git a/src/Servers/Kestrel/shared/test/PipeWriterHttp2FrameExtensions.cs b/src/Servers/Kestrel/shared/test/PipeWriterHttp2FrameExtensions.cs
index 481278ae4297..a99db7dfe4e5 100644
--- a/src/Servers/Kestrel/shared/test/PipeWriterHttp2FrameExtensions.cs
+++ b/src/Servers/Kestrel/shared/test/PipeWriterHttp2FrameExtensions.cs
@@ -25,13 +25,13 @@ public static void WriteSettings(this PipeWriter writer, Http2PeerSettings clien
writer.Write(payload);
}
- public static void WriteStartStream(this PipeWriter writer, int streamId, Http2HeadersEnumerator headers, byte[] headerEncodingBuffer, bool endStream, Http2Frame frame = null)
+ public static void WriteStartStream(this PipeWriter writer, int streamId, HPackEncoder hpackEncoder, Http2HeadersEnumerator headers, byte[] headerEncodingBuffer, bool endStream, Http2Frame frame = null)
{
frame ??= new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
var buffer = headerEncodingBuffer.AsSpan();
- var done = HPackHeaderWriter.BeginEncodeHeaders(headers, buffer, out var length);
+ var done = HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, headers, buffer, out var length);
frame.PayloadLength = length;
if (done)
@@ -51,7 +51,7 @@ public static void WriteStartStream(this PipeWriter writer, int streamId, Http2H
{
frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
- done = HPackHeaderWriter.ContinueEncodeHeaders(headers, buffer, out length);
+ done = HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headers, buffer, out length);
frame.PayloadLength = length;
if (done)
diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs
index 7e4336c5a8d6..1a7bd27c2445 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs
+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs
@@ -14,10 +14,12 @@
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
@@ -58,7 +60,7 @@ await InitializeConnectionAsync(async c =>
await SendWindowUpdateAsync(streamId: 1, 65535);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -90,7 +92,7 @@ await ExpectAsync(Http2FrameType.DATA,
await StartStreamAsync(3, GetHeaders(responseBodySize: 3), endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
@@ -101,7 +103,7 @@ await ExpectAsync(Http2FrameType.HEADERS,
await StartStreamAsync(5, GetHeaders(responseBodySize: 3), endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 5);
@@ -197,7 +199,7 @@ await InitializeConnectionAsync(async c =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -274,7 +276,7 @@ public async Task RequestHeaderStringReuse_MultipleStreams_KnownHeaderReused()
await StartStreamAsync(1, requestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -293,7 +295,7 @@ await ExpectAsync(Http2FrameType.PING,
await StartStreamAsync(3, requestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 6,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 3);
@@ -323,7 +325,7 @@ await InitializeConnectionAsync(async context =>
serverTcs.SetResult(null);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -356,7 +358,7 @@ public async Task StreamPool_MultipleStreamsConcurrent_StreamsReturnedToPool()
await SendDataAsync(1, _helloBytes, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -371,7 +373,7 @@ await ExpectAsync(Http2FrameType.DATA,
await SendDataAsync(3, _helloBytes, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
await ExpectAsync(Http2FrameType.DATA,
@@ -415,7 +417,7 @@ await InitializeConnectionAsync(async context =>
appDelegateTcs.TrySetResult(null);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -438,7 +440,7 @@ await ExpectAsync(Http2FrameType.PING,
appDelegateTcs.TrySetResult(null);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 6,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 3);
@@ -475,7 +477,7 @@ await InitializeConnectionAsync(async context =>
serverTcs.SetResult(null);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -593,7 +595,7 @@ public async Task ServerSettings_ChangesRequestMaxFrameSize()
await SendDataAsync(1, new byte[length], endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
// The client's settings is still defaulted to Http2PeerSettings.MinAllowedMaxFrameSize so the echo response will come back in two separate frames
@@ -622,7 +624,7 @@ public async Task DATA_Received_ReadByStream()
await SendDataAsync(1, _helloWorldBytes, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -648,7 +650,7 @@ public async Task DATA_Received_MaxSize_ReadByStream()
await SendDataAsync(1, _maxData, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -691,7 +693,7 @@ public async Task DATA_Received_GreaterThanInitialWindowSize_ReadByStream()
}
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -812,7 +814,7 @@ public async Task DATA_Received_Multiple_ReadByStream()
await SendDataAsync(1, _noData, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -840,7 +842,7 @@ public async Task DATA_Received_Multiplexed_ReadByStreams()
await SendDataAsync(1, _helloBytes, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var stream1DataFrame1 = await ExpectAsync(Http2FrameType.DATA,
@@ -851,7 +853,7 @@ await ExpectAsync(Http2FrameType.HEADERS,
await SendDataAsync(3, _helloBytes, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
var stream3DataFrame1 = await ExpectAsync(Http2FrameType.DATA,
@@ -920,7 +922,7 @@ public async Task DATA_Received_Multiplexed_GreaterThanInitialWindowSize_ReadByS
}
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -972,7 +974,7 @@ await ExpectAsync(Http2FrameType.HEADERS,
withStreamId: 0);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
@@ -1050,7 +1052,7 @@ await InitializeConnectionAsync(async context =>
stream3ReadFinished.TrySetResult(null);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
await ExpectAsync(Http2FrameType.DATA,
@@ -1065,7 +1067,7 @@ await ExpectAsync(Http2FrameType.DATA,
stream1ReadFinished.TrySetResult(null);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -1092,7 +1094,7 @@ public async Task DATA_Received_WithPadding_ReadByStream(byte padLength)
await SendDataWithPaddingAsync(1, _helloWorldBytes, padLength, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -1137,7 +1139,7 @@ public async Task DATA_Received_WithPadding_CountsTowardsInputFlowControl(byte p
}
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -1196,7 +1198,7 @@ public async Task DATA_Received_ButNotConsumedByApp_CountsTowardsInputFlowContro
}
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1265,7 +1267,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(1, _maxData, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -1440,7 +1442,7 @@ public async Task DATA_Received_StreamClosed_ConnectionError()
await StartStreamAsync(1, _postRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1482,7 +1484,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 3);
@@ -1532,7 +1534,7 @@ public async Task DATA_Received_StreamClosedImplicitly_ConnectionError()
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 3);
@@ -1636,7 +1638,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -1657,7 +1659,7 @@ await ExpectAsync(Http2FrameType.DATA,
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 6,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 3);
@@ -1691,7 +1693,7 @@ public async Task DATA_Sent_DespiteStreamOutputFlowControl_IfEmptyAndEndsStream(
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1706,7 +1708,7 @@ public async Task HEADERS_Received_Decoded()
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1726,7 +1728,7 @@ public async Task HEADERS_Received_WithPadding_Decoded(byte padLength)
await SendHeadersWithPaddingAsync(1, _browserRequestHeaders, padLength, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1743,7 +1745,7 @@ public async Task HEADERS_Received_WithPriority_Decoded()
await SendHeadersWithPriorityAsync(1, _browserRequestHeaders, priority: 42, streamDependency: 0, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1763,7 +1765,7 @@ public async Task HEADERS_Received_WithPriorityAndPadding_Decoded(byte padLength
await SendHeadersWithPaddingAndPriorityAsync(1, _browserRequestHeaders, padLength, priority: 42, streamDependency: 0, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1789,7 +1791,7 @@ public async Task HEADERS_Received_WithTrailers_Available(bool sendData)
// The second stream should end first, since the first one is waiting for the request body.
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 3);
@@ -1801,7 +1803,7 @@ await ExpectAsync(Http2FrameType.HEADERS,
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _requestTrailers);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 6,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1835,7 +1837,7 @@ public async Task HEADERS_Received_ContainsExpect100Continue_100ContinueSent()
await SendDataAsync(1, _helloBytes, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -1885,17 +1887,163 @@ await InitializeConnectionAsync(async context =>
finishSecondRequest.TrySetResult(null);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 3);
finishFirstRequest.TrySetResult(null);
+ await ExpectAsync(Http2FrameType.HEADERS,
+ withLength: 6,
+ withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
+ withStreamId: 1);
+
+ await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
+ }
+
+ [Fact]
+ public async Task HEADERS_HeaderTableSizeLimitZero_Received_DynamicTableUpdate()
+ {
+ _serviceContext.ServerOptions.Limits.Http2.HeaderTableSize = 0;
+
+ await InitializeConnectionAsync(_noopApplication, expectedSettingsCount: 4);
+
+ await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
+
+ _hpackEncoder.UpdateMaxHeaderTableSize(0);
+
+ var headerFrame = await ExpectAsync(Http2FrameType.HEADERS,
+ withLength: 38,
+ withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
+ withStreamId: 1);
+
+ const byte DynamicTableSizeUpdateMask = 0xe0;
+
+ var integerDecoder = new IntegerDecoder();
+ Assert.True(integerDecoder.BeginTryDecode((byte)(headerFrame.Payload.Span[0] & ~DynamicTableSizeUpdateMask), prefixLength: 5, out var result));
+
+ // Dynamic table update from the server
+ Assert.Equal(0, result);
+
+ await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
+
+ await ExpectAsync(Http2FrameType.HEADERS,
+ withLength: 37,
+ withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
+ withStreamId: 3);
+
+ await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
+ }
+
+ [Fact]
+ public async Task HEADERS_ResponseSetsIgnoreIndexAndNeverIndexValues_HeadersParsed()
+ {
+ await InitializeConnectionAsync(c =>
+ {
+ c.Response.ContentLength = 0;
+ c.Response.Headers[HeaderNames.SetCookie] = "SetCookie!";
+ c.Response.Headers[HeaderNames.ContentDisposition] = "ContentDisposition!";
+
+ return Task.CompletedTask;
+ });
+
+ await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
+
+ var frame = await ExpectAsync(Http2FrameType.HEADERS,
+ withLength: 90,
+ withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
+ withStreamId: 1);
+
+ var handler = new TestHttpHeadersHandler();
+
+ var hpackDecoder = new HPackDecoder();
+ hpackDecoder.Decode(new ReadOnlySequence(frame.Payload), endHeaders: true, handler);
+ hpackDecoder.CompleteDecode();
+
+ Assert.Equal("200", handler.Headers[":status"]);
+ Assert.Equal("SetCookie!", handler.Headers[HeaderNames.SetCookie]);
+ Assert.Equal("ContentDisposition!", handler.Headers[HeaderNames.ContentDisposition]);
+ Assert.Equal("0", handler.Headers[HeaderNames.ContentLength]);
+
+ await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
+
+ frame = await ExpectAsync(Http2FrameType.HEADERS,
+ withLength: 60,
+ withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
+ withStreamId: 3);
+
+ handler = new TestHttpHeadersHandler();
+
+ hpackDecoder.Decode(new ReadOnlySequence(frame.Payload), endHeaders: true, handler);
+ hpackDecoder.CompleteDecode();
+
+ Assert.Equal("200", handler.Headers[":status"]);
+ Assert.Equal("SetCookie!", handler.Headers[HeaderNames.SetCookie]);
+ Assert.Equal("ContentDisposition!", handler.Headers[HeaderNames.ContentDisposition]);
+ Assert.Equal("0", handler.Headers[HeaderNames.ContentLength]);
+
+ await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
+ }
+
+ private class TestHttpHeadersHandler : IHttpHeadersHandler
+ {
+ public readonly Dictionary Headers = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ public void OnHeader(ReadOnlySpan name, ReadOnlySpan value)
+ {
+ var nameString = Encoding.ASCII.GetString(name);
+ var valueString = Encoding.ASCII.GetString(value);
+
+ if (Headers.TryGetValue(nameString, out var values))
+ {
+ var l = values.ToList();
+ l.Add(valueString);
+
+ Headers[nameString] = new StringValues(l.ToArray());
+ }
+ else
+ {
+ Headers[nameString] = new StringValues(valueString);
+ }
+ }
+
+ public void OnHeadersComplete(bool endStream)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnStaticIndexedHeader(int index)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnStaticIndexedHeader(int index, ReadOnlySpan value)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [Fact]
+ public async Task HEADERS_DisableDynamicHeaderCompression_HeadersNotCompressed()
+ {
+ _serviceContext.ServerOptions.AllowResponseHeaderCompression = false;
+
+ await InitializeConnectionAsync(_noopApplication);
+
+ await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
+
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
+ await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
+
+ await ExpectAsync(Http2FrameType.HEADERS,
+ withLength: 37,
+ withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
+ withStreamId: 3);
+
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
}
@@ -1918,7 +2066,7 @@ public async Task HEADERS_OverMaxStreamLimit_Refused()
requestBlocker.SetResult(0);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1961,7 +2109,7 @@ public async Task HEADERS_Received_StreamClosed_ConnectionError()
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -2004,7 +2152,7 @@ public async Task HEADERS_Received_StreamClosedImplicitly_ConnectionError()
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 3);
@@ -2228,7 +2376,7 @@ public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeade
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -2373,7 +2521,7 @@ public async Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_N
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -2511,7 +2659,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -2536,7 +2684,7 @@ await ExpectAsync(Http2FrameType.DATA,
// The headers, but not the data for stream 3, can be sent prior to any window updates.
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
@@ -2615,12 +2763,12 @@ await InitializeConnectionAsync(async context =>
}
});
- async Task VerifyStreamBackpressure(int streamId)
+ async Task VerifyStreamBackpressure(int streamId, int headersLength)
{
await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: headersLength,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: streamId);
@@ -2633,9 +2781,9 @@ await ExpectAsync(Http2FrameType.HEADERS,
Assert.False(writeTasks[streamId].IsCompleted);
}
- await VerifyStreamBackpressure(1);
- await VerifyStreamBackpressure(3);
- await VerifyStreamBackpressure(5);
+ await VerifyStreamBackpressure(1, 32);
+ await VerifyStreamBackpressure(3, 2);
+ await VerifyStreamBackpressure(5, 2);
await SendRstStreamAsync(1);
await writeTasks[1].DefaultTimeout();
@@ -2913,6 +3061,7 @@ public async Task SETTINGS_Custom_Sent()
{
CreateConnection();
+ _connection.ServerSettings.HeaderTableSize = 0;
_connection.ServerSettings.MaxConcurrentStreams = 1;
_connection.ServerSettings.MaxHeaderListSize = 4 * 1024;
_connection.ServerSettings.InitialWindowSize = 1024 * 1024 * 10;
@@ -2923,23 +3072,27 @@ public async Task SETTINGS_Custom_Sent()
await SendSettingsAsync();
var frame = await ExpectAsync(Http2FrameType.SETTINGS,
- withLength: Http2FrameReader.SettingSize * 3,
+ withLength: Http2FrameReader.SettingSize * 4,
withFlags: 0,
withStreamId: 0);
// Only non protocol defaults are sent
var settings = Http2FrameReader.ReadSettings(frame.PayloadSequence);
- Assert.Equal(3, settings.Count);
+ Assert.Equal(4, settings.Count);
var setting = settings[0];
+ Assert.Equal(Http2SettingsParameter.SETTINGS_HEADER_TABLE_SIZE, setting.Parameter);
+ Assert.Equal(0u, setting.Value);
+
+ setting = settings[1];
Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_CONCURRENT_STREAMS, setting.Parameter);
Assert.Equal(1u, setting.Value);
- setting = settings[1];
+ setting = settings[2];
Assert.Equal(Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE, setting.Parameter);
Assert.Equal(1024 * 1024 * 10u, setting.Value);
- setting = settings[2];
+ setting = settings[3];
Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_HEADER_LIST_SIZE, setting.Parameter);
Assert.Equal(4 * 1024u, setting.Value);
@@ -3100,7 +3253,7 @@ public async Task SETTINGS_Received_ChangesAllowedResponseMaxFrameSize()
_connection.ServerSettings.MaxFrameSize = Http2PeerSettings.MaxAllowedMaxFrameSize;
// This includes the default response headers such as :status, etc
- var defaultResponseHeaderLength = 33;
+ var defaultResponseHeaderLength = 32;
var headerValueLength = Http2PeerSettings.MinAllowedMaxFrameSize;
// First byte is always 0
// Second byte is the length of header name which is 1
@@ -3170,7 +3323,7 @@ await InitializeConnectionAsync(context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -3204,7 +3357,56 @@ await ExpectAsync(Http2FrameType.SETTINGS,
withFlags: (byte)Http2SettingsFrameFlags.ACK,
withStreamId: 0);
- await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
+ // Start request
+ await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
+
+ var headerFrame = await ExpectAsync(Http2FrameType.HEADERS,
+ withLength: 36,
+ withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
+ withStreamId: 1);
+
+ // Headers start with :status = 200
+ Assert.Equal(0x88, headerFrame.Payload.Span[0]);
+
+ await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
+ }
+
+ [Fact]
+ public async Task SETTINGS_Received_WithLargeHeaderTableSizeLimit_ChangesHeaderTableSize()
+ {
+ _serviceContext.ServerOptions.Limits.Http2.HeaderTableSize = 40000;
+
+ await InitializeConnectionAsync(_noopApplication, expectedSettingsCount: 4);
+
+ // Update client settings
+ _clientSettings.HeaderTableSize = 65536; // Chrome's default, larger than the 4kb spec default
+ await SendSettingsAsync();
+
+ // ACK
+ await ExpectAsync(Http2FrameType.SETTINGS,
+ withLength: 0,
+ withFlags: (byte)Http2SettingsFrameFlags.ACK,
+ withStreamId: 0);
+
+ // Start request
+ await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
+
+ var headerFrame = await ExpectAsync(Http2FrameType.HEADERS,
+ withLength: 40,
+ withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
+ withStreamId: 1);
+
+ const byte DynamicTableSizeUpdateMask = 0xe0;
+
+ var integerDecoder = new IntegerDecoder();
+ Assert.False(integerDecoder.BeginTryDecode((byte)(headerFrame.Payload.Span[0] & ~DynamicTableSizeUpdateMask), prefixLength: 5, out _));
+ Assert.False(integerDecoder.TryDecode(headerFrame.Payload.Span[1], out _));
+ Assert.False(integerDecoder.TryDecode(headerFrame.Payload.Span[2], out _));
+ Assert.True(integerDecoder.TryDecode(headerFrame.Payload.Span[3], out var result));
+
+ Assert.Equal(40000, result);
+
+ await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
@@ -3319,7 +3521,7 @@ public async Task GOAWAY_Received_SetsConnectionStateToClosingAndWaitForAllStrea
await SendDataAsync(1, _helloBytes, true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -3332,7 +3534,7 @@ await ExpectAsync(Http2FrameType.DATA,
withStreamId: 1);
await SendDataAsync(3, _helloBytes, true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
await ExpectAsync(Http2FrameType.DATA,
@@ -3405,7 +3607,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -3428,13 +3630,13 @@ await ExpectAsync(Http2FrameType.DATA,
// The headers, but not the data for the stream, can still be sent.
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
await StartStreamAsync(5, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 5);
@@ -3493,12 +3695,12 @@ await InitializeConnectionAsync(async context =>
}
});
- async Task VerifyStreamBackpressure(int streamId)
+ async Task VerifyStreamBackpressure(int streamId, int headersLength)
{
await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: headersLength,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: streamId);
@@ -3511,9 +3713,9 @@ await ExpectAsync(Http2FrameType.HEADERS,
Assert.False(writeTasks[streamId].IsCompleted);
}
- await VerifyStreamBackpressure(1);
- await VerifyStreamBackpressure(3);
- await VerifyStreamBackpressure(5);
+ await VerifyStreamBackpressure(1, 32);
+ await VerifyStreamBackpressure(3, 2);
+ await VerifyStreamBackpressure(5, 2);
// Close all pipes and wait for response to drain
_pair.Application.Output.Complete();
@@ -3731,7 +3933,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -3788,7 +3990,7 @@ public async Task WINDOW_UPDATE_Received_OnStream_Respected()
await SendDataAsync(1, _helloWorldBytes, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -3827,7 +4029,7 @@ public async Task WINDOW_UPDATE_Received_OnStream_Respected_WhenInitialWindowSiz
await SendDataAsync(1, _helloWorldBytes, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -3879,7 +4081,7 @@ public async Task CONTINUATION_Received_Decoded()
await StartStreamAsync(1, _twoContinuationsRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -3906,7 +4108,7 @@ public async Task CONTINUATION_Received_WithTrailers_Available(bool sendData)
// The second stream should end first, since the first one is waiting for the request body.
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 3);
@@ -3929,7 +4131,7 @@ await ExpectAsync(Http2FrameType.HEADERS,
await SendContinuationAsync(1, Http2ContinuationFrameFlags.END_HEADERS, trailers);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 6,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -4027,7 +4229,7 @@ public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudo
await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -4042,7 +4244,7 @@ public async Task CONTINUATION_Sent_WhenHeadersLargerThanFrameLength()
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 12343,
+ withLength: 12342,
withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 1);
var continuationFrame1 = await ExpectAsync(Http2FrameType.CONTINUATION,
@@ -4201,7 +4403,7 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYAndWaitsForStreams
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -4251,7 +4453,7 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhe
await SendDataAsync(1, _helloBytes, true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -4284,8 +4486,8 @@ public async Task AcceptNewStreamsDuringClosingConnection()
await StartStreamAsync(3, _browserRequestHeaders, endStream: false);
await SendDataAsync(1, _helloBytes, true);
- await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ var f = await ExpectAsync(Http2FrameType.HEADERS,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -4298,7 +4500,7 @@ await ExpectAsync(Http2FrameType.DATA,
withStreamId: 1);
await SendDataAsync(3, _helloBytes, true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
await ExpectAsync(Http2FrameType.DATA,
@@ -4388,7 +4590,7 @@ public async Task AppDoesNotReadRequestBody_ResetsAndDrainsRequest(int intFinalF
await StartStreamAsync(1, headers, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs
index a59207ccda35..efed04f3023a 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs
+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs
@@ -79,7 +79,7 @@ public async Task HEADERS_Received_CustomMethod_Accepted()
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 52,
+ withLength: 51,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -104,7 +104,7 @@ public async Task HEADERS_Received_CONNECTMethod_Accepted()
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 53,
+ withLength: 52,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -131,7 +131,7 @@ public async Task HEADERS_Received_OPTIONSStar_LeftOutOfPath()
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 57,
+ withLength: 56,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -159,7 +159,7 @@ public async Task HEADERS_Received_OPTIONSSlash_Accepted()
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 58,
+ withLength: 57,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -193,7 +193,7 @@ await InitializeConnectionAsync(context =>
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 100,
+ withLength: 99,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -235,7 +235,7 @@ await InitializeConnectionAsync(context =>
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -297,7 +297,7 @@ public async Task HEADERS_Received_MissingAuthority_200Status()
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -326,7 +326,7 @@ public async Task HEADERS_Received_EmptyAuthority_200Status()
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -355,7 +355,7 @@ public async Task HEADERS_Received_MissingAuthorityFallsBackToHost_200Status()
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 47,
+ withLength: 46,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -386,7 +386,7 @@ public async Task HEADERS_Received_EmptyAuthorityIgnoredOverHost_200Status()
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 47,
+ withLength: 46,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -417,7 +417,7 @@ public async Task HEADERS_Received_AuthorityOverridesHost_200Status()
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 47,
+ withLength: 46,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -448,7 +448,7 @@ public async Task HEADERS_Received_AuthorityOverridesInvalidHost_200Status()
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 47,
+ withLength: 46,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -570,7 +570,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -611,7 +611,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -655,7 +655,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(1, new byte[8], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -698,7 +698,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(1, new byte[8], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -751,7 +751,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(1, new byte[8], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -983,7 +983,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1015,7 +1015,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 38,
+ withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.RST_STREAM,
@@ -1054,7 +1054,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 38,
+ withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -1092,7 +1092,7 @@ await InitializeConnectionAsync(context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1125,7 +1125,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -1160,7 +1160,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -1198,7 +1198,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -1236,7 +1236,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -1276,7 +1276,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -1323,7 +1323,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -1361,7 +1361,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -1397,7 +1397,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -1441,7 +1441,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 38,
+ withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -1475,7 +1475,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -1508,7 +1508,7 @@ await InitializeConnectionAsync(context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 38,
+ withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -1552,7 +1552,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1591,7 +1591,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: false);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 41,
+ withLength: 40,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1634,7 +1634,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1674,7 +1674,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(1, new byte[6], endStream: false);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 41,
+ withLength: 40,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1733,7 +1733,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(1, new byte[6], endStream: false);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 41,
+ withLength: 40,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1788,7 +1788,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -1814,7 +1814,7 @@ await InitializeConnectionAsync(context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -1852,7 +1852,7 @@ await InitializeConnectionAsync(context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
@@ -1883,7 +1883,7 @@ await InitializeConnectionAsync(context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame1 = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var trailersFrame1 = await ExpectAsync(Http2FrameType.HEADERS,
@@ -1894,12 +1894,12 @@ await InitializeConnectionAsync(context =>
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
var headersFrame2 = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 6,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 3);
var trailersFrame2 = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 25,
+ withLength: 1,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 3);
@@ -1930,7 +1930,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -1980,7 +1980,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -2039,7 +2039,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -2074,7 +2074,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -2124,7 +2124,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true).DefaultTimeout();
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1).DefaultTimeout();
@@ -2189,7 +2189,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -2235,7 +2235,7 @@ await InitializeConnectionAsync(context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -2269,7 +2269,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -2532,7 +2532,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -2623,7 +2623,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -2671,7 +2671,7 @@ await InitializeConnectionAsync(async context =>
// Just the StatusCode gets written before aborting in the continuation frame
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
@@ -2700,7 +2700,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -2743,7 +2743,7 @@ await ExpectAsync(Http2FrameType.SETTINGS,
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -2789,7 +2789,7 @@ await InitializeConnectionAsync(httpContext =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -2835,7 +2835,7 @@ await InitializeConnectionAsync(async httpContext =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -2884,7 +2884,7 @@ await InitializeConnectionAsync(async httpContext =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -2937,7 +2937,7 @@ await InitializeConnectionAsync(async httpContext =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -2987,7 +2987,7 @@ await InitializeConnectionAsync(async httpContext =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -3037,7 +3037,7 @@ void NonAsyncMethod()
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -3080,7 +3080,7 @@ await InitializeConnectionAsync(async httpContext =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -3126,7 +3126,7 @@ await InitializeConnectionAsync(async httpContext =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -3168,7 +3168,7 @@ await InitializeConnectionAsync(async httpContext =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -3213,7 +3213,7 @@ await InitializeConnectionAsync(async httpContext =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -3279,7 +3279,7 @@ void NonAsyncMethod()
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 38,
+ withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -3325,7 +3325,7 @@ await InitializeConnectionAsync(httpContext =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 38,
+ withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -3361,7 +3361,7 @@ await InitializeConnectionAsync(async httpContext =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 38,
+ withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -3413,7 +3413,7 @@ await InitializeConnectionAsync(async httpContext =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 38,
+ withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -3465,7 +3465,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -3498,7 +3498,7 @@ await InitializeConnectionAsync(async context =>
// Don't receive content length because we called WriteAsync which caused an invalid response
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS | (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 1);
@@ -3531,7 +3531,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -3583,7 +3583,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -3639,7 +3639,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
@@ -3705,7 +3705,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -3761,7 +3761,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -3826,7 +3826,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -3885,7 +3885,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -3941,7 +3941,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -4003,7 +4003,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -4077,7 +4077,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -4153,7 +4153,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 38,
+ withLength: 37,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -4224,7 +4224,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 38,
+ withLength: 37,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -4296,7 +4296,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -4380,7 +4380,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: false);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -4461,7 +4461,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -4548,7 +4548,7 @@ await InitializeConnectionAsync(async context =>
await StartStreamAsync(1, headers, endStream: false);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
@@ -4608,7 +4608,7 @@ await InitializeConnectionAsync(context =>
await StartStreamAsync(1, LatinHeaderData, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs
index f80e5ad386e7..03b5e277ed88 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs
+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs
@@ -121,6 +121,7 @@ protected static IEnumerable> ReadRateRequestHeader
internal readonly Http2PeerSettings _clientSettings = new Http2PeerSettings();
internal readonly HPackDecoder _hpackDecoder;
+ internal readonly HPackEncoder _hpackEncoder;
private readonly byte[] _headerEncodingBuffer = new byte[Http2PeerSettings.MinAllowedMaxFrameSize];
internal readonly TimeoutControl _timeoutControl;
@@ -165,6 +166,7 @@ protected static IEnumerable> ReadRateRequestHeader
public Http2TestBase()
{
_hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize);
+ _hpackEncoder = new HPackEncoder();
_timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object);
_mockTimeoutControl = new Mock(_timeoutControl) { CallBase = true };
@@ -501,7 +503,7 @@ protected Task StartStreamAsync(int streamId, IEnumerable(TaskCreationOptions.RunContinuationsAsynchronously);
_runningStreams[streamId] = tcs;
- writableBuffer.WriteStartStream(streamId, GetHeadersEnumerator(headers), _headerEncodingBuffer, endStream);
+ writableBuffer.WriteStartStream(streamId, _hpackEncoder, GetHeadersEnumerator(headers), _headerEncodingBuffer, endStream);
return FlushAsync(writableBuffer);
}
@@ -541,7 +543,7 @@ protected Task SendHeadersWithPaddingAsync(int streamId, IEnumerable SendHeadersAsync(int streamId, Http2HeadersFrameFlags
frame.PrepareHeaders(flags, streamId);
var buffer = _headerEncodingBuffer.AsMemory();
- var done = HPackHeaderWriter.BeginEncodeHeaders(headersEnumerator, buffer.Span, out var length);
+ var done = HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, headersEnumerator, buffer.Span, out var length);
frame.PayloadLength = length;
Http2FrameWriter.WriteHeader(frame, outputWriter);
@@ -815,7 +817,7 @@ internal async Task SendContinuationAsync(int streamId, Http2ContinuationF
frame.PrepareContinuation(flags, streamId);
var buffer = _headerEncodingBuffer.AsMemory();
- var done = HPackHeaderWriter.ContinueEncodeHeaders(headersEnumerator, buffer.Span, out var length);
+ var done = HPackHeaderWriter.ContinueEncodeHeaders(_hpackEncoder, headersEnumerator, buffer.Span, out var length);
frame.PayloadLength = length;
Http2FrameWriter.WriteHeader(frame, outputWriter);
@@ -843,7 +845,7 @@ internal async Task SendContinuationAsync(int streamId, Http2ContinuationF
frame.PrepareContinuation(flags, streamId);
var buffer = _headerEncodingBuffer.AsMemory();
- var done = HPackHeaderWriter.BeginEncodeHeaders(GetHeadersEnumerator(headers), buffer.Span, out var length);
+ var done = HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, GetHeadersEnumerator(headers), buffer.Span, out var length);
frame.PayloadLength = length;
Http2FrameWriter.WriteHeader(frame, outputWriter);
diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs
index 087664a7e069..4432e85dc601 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs
+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs
@@ -101,7 +101,7 @@ public async Task HEADERS_NotReceivedAfterFirstRequest_WithinKeepAliveTimeout_Cl
_mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny(), TimeoutReason.RequestHeaders), Times.Once);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
@@ -283,7 +283,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsC
await SendDataAsync(1, _helloWorldBytes, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -336,7 +336,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsC
await SendDataAsync(1, _maxData, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -390,7 +390,7 @@ public async Task DATA_Sent_TooSlowlyDueToFlowControlOnSmallWrite_AbortsConnecti
await SendDataAsync(1, _helloWorldBytes, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -445,7 +445,7 @@ public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnLargeWrite_AbortsCo
await SendDataAsync(1, _maxData, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -501,7 +501,7 @@ public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnMultipleStreams_Abo
await SendDataAsync(1, _maxData, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
@@ -513,7 +513,7 @@ await ExpectAsync(Http2FrameType.DATA,
await SendDataAsync(3, _maxData, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
await ExpectAsync(Http2FrameType.DATA,
@@ -567,7 +567,7 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP
await SendDataAsync(1, _helloWorldBytes, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -616,7 +616,7 @@ public async Task DATA_Received_TooSlowlyOnLargeRead_AbortsConnectionAfterRateTi
await SendDataAsync(1, _maxData, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -669,7 +669,7 @@ public async Task DATA_Received_TooSlowlyOnMultipleStreams_AbortsConnectionAfter
await SendDataAsync(1, _maxData, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -682,7 +682,7 @@ await ExpectAsync(Http2FrameType.DATA,
await SendDataAsync(3, _maxData, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
await ExpectAsync(Http2FrameType.DATA,
@@ -738,7 +738,7 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon
await SendDataAsync(1, _maxData, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -756,7 +756,7 @@ await ExpectAsync(Http2FrameType.DATA,
await SendDataAsync(3, _maxData, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
await ExpectAsync(Http2FrameType.DATA,
@@ -813,7 +813,7 @@ await InitializeConnectionAsync(context =>
await SendDataAsync(1, _helloWorldBytes, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
@@ -885,7 +885,7 @@ await InitializeConnectionAsync(async context =>
await SendDataAsync(3, _helloWorldBytes, endStream: false);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 33,
+ withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
await ExpectAsync(Http2FrameType.DATA,
@@ -902,7 +902,7 @@ await ExpectAsync(Http2FrameType.DATA,
backpressureTcs.SetResult(null);
await ExpectAsync(Http2FrameType.HEADERS,
- withLength: 37,
+ withLength: 6,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs
index 18faacc44d28..f53e083bc3da 100644
--- a/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs
+++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs
@@ -1118,7 +1118,7 @@ public async Task ResponseHeaders_MultipleFrames_Accepted(string scheme)
Assert.Equal(oneKbString + i, response.Headers.GetValues("header" + i).Single());
}
- Assert.Single(TestSink.Writes.Where(context => context.Message.Contains("sending HEADERS frame for stream ID 1 with length 15612 and flags END_STREAM")));
+ Assert.Single(TestSink.Writes.Where(context => context.Message.Contains("sending HEADERS frame for stream ID 1 with length 15610 and flags END_STREAM")));
Assert.Equal(2, TestSink.Writes.Where(context => context.Message.Contains("sending CONTINUATION frame for stream ID 1 with length 15585 and flags NONE")).Count());
Assert.Single(TestSink.Writes.Where(context => context.Message.Contains("sending CONTINUATION frame for stream ID 1 with length 14546 and flags END_HEADERS")));
diff --git a/src/Shared/Hpack/EncoderHeaderEntry.cs b/src/Shared/Hpack/EncoderHeaderEntry.cs
new file mode 100644
index 000000000000..75a0aebde240
--- /dev/null
+++ b/src/Shared/Hpack/EncoderHeaderEntry.cs
@@ -0,0 +1,73 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Net.Http.HPack
+{
+ [DebuggerDisplay("Name = {Name} Value = {Value}")]
+ internal class EncoderHeaderEntry
+ {
+ // Header name and value
+ public string Name;
+ public string Value;
+
+ // Chained list of headers in the same bucket
+ public EncoderHeaderEntry Next;
+ public int Hash;
+
+ // Compute dynamic table index
+ public int Index;
+
+ // Doubly linked list
+ public EncoderHeaderEntry Before;
+ public EncoderHeaderEntry After;
+
+ ///
+ /// Initialize header values. An entry will be reinitialized when reused.
+ ///
+ public void Initialize(int hash, string name, string value, int index, EncoderHeaderEntry next)
+ {
+ Debug.Assert(name != null);
+ Debug.Assert(value != null);
+
+ Name = name;
+ Value = value;
+ Index = index;
+ Hash = hash;
+ Next = next;
+ }
+
+ public uint CalculateSize()
+ {
+ return (uint)HeaderField.GetLength(Name.Length, Value.Length);
+ }
+
+ ///
+ /// Remove entry from the linked list and reset header values.
+ ///
+ public void Remove()
+ {
+ Before.After = After;
+ After.Before = Before;
+ Before = null;
+ After = null;
+ Next = null;
+ Hash = 0;
+ Name = null;
+ Value = null;
+ }
+
+ ///
+ /// Add before an entry in the linked list.
+ ///
+ public void AddBefore(EncoderHeaderEntry existingEntry)
+ {
+ After = existingEntry;
+ Before = existingEntry.Before;
+ Before.After = this;
+ After.Before = this;
+ }
+ }
+}
diff --git a/src/Shared/Hpack/HPackEncoder.Dynamic.cs b/src/Shared/Hpack/HPackEncoder.Dynamic.cs
new file mode 100644
index 000000000000..f8e7f4c93dfb
--- /dev/null
+++ b/src/Shared/Hpack/HPackEncoder.Dynamic.cs
@@ -0,0 +1,295 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+using System.Diagnostics;
+
+namespace System.Net.Http.HPack
+{
+ internal partial class HPackEncoder
+ {
+ public const int DefaultHeaderTableSize = 4096;
+
+ // Internal for testing
+ internal readonly EncoderHeaderEntry Head;
+
+ private readonly bool _allowDynamicCompression;
+ private readonly EncoderHeaderEntry[] _headerBuckets;
+ private readonly byte _hashMask;
+ private uint _headerTableSize;
+ private uint _maxHeaderTableSize;
+ private bool _pendingTableSizeUpdate;
+ private EncoderHeaderEntry? _removed;
+
+ public HPackEncoder(bool allowDynamicCompression = true, uint maxHeaderTableSize = DefaultHeaderTableSize)
+ {
+ _allowDynamicCompression = allowDynamicCompression;
+ _maxHeaderTableSize = maxHeaderTableSize;
+ Head = new EncoderHeaderEntry();
+ Head.Initialize(-1, string.Empty, string.Empty, int.MaxValue, null);
+ // Bucket count balances memory usage and the expected low number of headers (constrained by the header table size).
+ // Performance with different bucket counts hasn't been measured in detail.
+ _headerBuckets = new EncoderHeaderEntry[16];
+ _hashMask = (byte)(_headerBuckets.Length - 1);
+ Head.Before = Head.After = Head;
+ }
+
+ public void UpdateMaxHeaderTableSize(uint maxHeaderTableSize)
+ {
+ if (_maxHeaderTableSize != maxHeaderTableSize)
+ {
+ _maxHeaderTableSize = maxHeaderTableSize;
+
+ // Dynamic table size update will be written next HEADERS frame
+ _pendingTableSizeUpdate = true;
+
+ // Check capacity and remove entries that exceed the new capacity
+ EnsureCapacity(0);
+ }
+ }
+
+ public bool EnsureDynamicTableSizeUpdate(Span buffer, out int length)
+ {
+ // Check if there is a table size update that should be encoded
+ if (_pendingTableSizeUpdate)
+ {
+ bool success = EncodeDynamicTableSizeUpdate((int)_maxHeaderTableSize, buffer, out length);
+ _pendingTableSizeUpdate = false;
+ return success;
+ }
+
+ length = 0;
+ return true;
+ }
+
+ public bool EncodeHeader(Span buffer, int staticTableIndex, HeaderEncodingHint encodingHint, string name, string value, out int bytesWritten)
+ {
+ Debug.Assert(!_pendingTableSizeUpdate, "Dynamic table size update should be encoded before headers.");
+
+ // Never index sensitive value.
+ if (encodingHint == HeaderEncodingHint.NeverIndex)
+ {
+ int index = ResolveDynamicTableIndex(staticTableIndex, name);
+
+ return index == -1
+ ? EncodeLiteralHeaderFieldNeverIndexingNewName(name, value, buffer, out bytesWritten)
+ : EncodeLiteralHeaderFieldNeverIndexing(index, value, buffer, out bytesWritten);
+ }
+
+ // No dynamic table. Only use the static table.
+ if (!_allowDynamicCompression || _maxHeaderTableSize == 0 || encodingHint == HeaderEncodingHint.IgnoreIndex)
+ {
+ return staticTableIndex == -1
+ ? EncodeLiteralHeaderFieldWithoutIndexingNewName(name, value, buffer, out bytesWritten)
+ : EncodeLiteralHeaderFieldWithoutIndexing(staticTableIndex, value, buffer, out bytesWritten);
+ }
+
+ // Header is greater than the maximum table size.
+ // Don't attempt to add dynamic header as all existing dynamic headers will be removed.
+ if (HeaderField.GetLength(name.Length, value.Length) > _maxHeaderTableSize)
+ {
+ int index = ResolveDynamicTableIndex(staticTableIndex, name);
+
+ return index == -1
+ ? EncodeLiteralHeaderFieldWithoutIndexingNewName(name, value, buffer, out bytesWritten)
+ : EncodeLiteralHeaderFieldWithoutIndexing(index, value, buffer, out bytesWritten);
+ }
+
+ return EncodeDynamicHeader(buffer, staticTableIndex, name, value, out bytesWritten);
+ }
+
+ private int ResolveDynamicTableIndex(int staticTableIndex, string name)
+ {
+ if (staticTableIndex != -1)
+ {
+ // Prefer static table index.
+ return staticTableIndex;
+ }
+
+ return CalculateDynamicTableIndex(name);
+ }
+
+ private bool EncodeDynamicHeader(Span buffer, int staticTableIndex, string name, string value, out int bytesWritten)
+ {
+ EncoderHeaderEntry? headerField = GetEntry(name, value);
+ if (headerField != null)
+ {
+ // Already exists in dynamic table. Write index.
+ int index = CalculateDynamicTableIndex(headerField.Index);
+ return EncodeIndexedHeaderField(index, buffer, out bytesWritten);
+ }
+ else
+ {
+ // Doesn't exist in dynamic table. Add new entry to dynamic table.
+ uint headerSize = (uint)HeaderField.GetLength(name.Length, value.Length);
+
+ int index = ResolveDynamicTableIndex(staticTableIndex, name);
+ bool success = index == -1
+ ? EncodeLiteralHeaderFieldIndexingNewName(name, value, buffer, out bytesWritten)
+ : EncodeLiteralHeaderFieldIndexing(index, value, buffer, out bytesWritten);
+
+ if (success)
+ {
+ EnsureCapacity(headerSize);
+ AddHeaderEntry(name, value, headerSize);
+ }
+
+ return success;
+ }
+ }
+
+ ///
+ /// Ensure there is capacity for the new header. If there is not enough capacity then remove
+ /// existing headers until space is available.
+ ///
+ private void EnsureCapacity(uint headerSize)
+ {
+ Debug.Assert(headerSize <= _maxHeaderTableSize, "Header is bigger than dynamic table size.");
+
+ while (_maxHeaderTableSize - _headerTableSize < headerSize)
+ {
+ EncoderHeaderEntry? removed = RemoveHeaderEntry();
+ Debug.Assert(removed != null);
+
+ // Removed entries are tracked to be reused.
+ PushRemovedEntry(removed);
+ }
+ }
+
+ private EncoderHeaderEntry? GetEntry(string name, string value)
+ {
+ if (_headerTableSize == 0)
+ {
+ return null;
+ }
+ int hash = name.GetHashCode();
+ int bucketIndex = CalculateBucketIndex(hash);
+ for (EncoderHeaderEntry? e = _headerBuckets[bucketIndex]; e != null; e = e.Next)
+ {
+ // We've already looked up entries based on a hash of the name.
+ // Compare value before name as it is more likely to be different.
+ if (e.Hash == hash &&
+ string.Equals(value, e.Value, StringComparison.Ordinal) &&
+ string.Equals(name, e.Name, StringComparison.Ordinal))
+ {
+ return e;
+ }
+ }
+ return null;
+ }
+
+ private int CalculateDynamicTableIndex(string name)
+ {
+ if (_headerTableSize == 0)
+ {
+ return -1;
+ }
+ int hash = name.GetHashCode();
+ int bucketIndex = CalculateBucketIndex(hash);
+ for (EncoderHeaderEntry? e = _headerBuckets[bucketIndex]; e != null; e = e.Next)
+ {
+ if (e.Hash == hash && string.Equals(name, e.Name, StringComparison.Ordinal))
+ {
+ return CalculateDynamicTableIndex(e.Index);
+ }
+ }
+ return -1;
+ }
+
+ private int CalculateDynamicTableIndex(int index)
+ {
+ return index == -1 ? -1 : index - Head.Before.Index + 1 + H2StaticTable.Count;
+ }
+
+ private void AddHeaderEntry(string name, string value, uint headerSize)
+ {
+ Debug.Assert(headerSize <= _maxHeaderTableSize, "Header is bigger than dynamic table size.");
+ Debug.Assert(headerSize <= _maxHeaderTableSize - _headerTableSize, "Not enough room in dynamic table.");
+
+ int hash = name.GetHashCode();
+ int bucketIndex = CalculateBucketIndex(hash);
+ EncoderHeaderEntry? oldEntry = _headerBuckets[bucketIndex];
+ // Attempt to reuse removed entry
+ EncoderHeaderEntry? newEntry = PopRemovedEntry() ?? new EncoderHeaderEntry();
+ newEntry.Initialize(hash, name, value, Head.Before.Index - 1, oldEntry);
+ _headerBuckets[bucketIndex] = newEntry;
+ newEntry.AddBefore(Head);
+ _headerTableSize += headerSize;
+ }
+
+ private void PushRemovedEntry(EncoderHeaderEntry removed)
+ {
+ if (_removed != null)
+ {
+ removed.Next = _removed;
+ }
+ _removed = removed;
+ }
+
+ private EncoderHeaderEntry? PopRemovedEntry()
+ {
+ if (_removed != null)
+ {
+ EncoderHeaderEntry? removed = _removed;
+ _removed = _removed.Next;
+ return removed;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Remove the oldest entry.
+ ///
+ private EncoderHeaderEntry? RemoveHeaderEntry()
+ {
+ if (_headerTableSize == 0)
+ {
+ return null;
+ }
+ EncoderHeaderEntry? eldest = Head.After;
+ int hash = eldest.Hash;
+ int bucketIndex = CalculateBucketIndex(hash);
+ EncoderHeaderEntry? prev = _headerBuckets[bucketIndex];
+ EncoderHeaderEntry? e = prev;
+ while (e != null)
+ {
+ EncoderHeaderEntry next = e.Next;
+ if (e == eldest)
+ {
+ if (prev == eldest)
+ {
+ _headerBuckets[bucketIndex] = next;
+ }
+ else
+ {
+ prev.Next = next;
+ }
+ _headerTableSize -= eldest.CalculateSize();
+ eldest.Remove();
+ return eldest;
+ }
+ prev = e;
+ e = next;
+ }
+ return null;
+ }
+
+ private int CalculateBucketIndex(int hash)
+ {
+ return hash & _hashMask;
+ }
+ }
+
+ ///
+ /// Hint for how the header should be encoded as HPack. This value can be overriden.
+ /// For example, a header that is larger than the dynamic table won't be indexed.
+ ///
+ internal enum HeaderEncodingHint
+ {
+ Index,
+ IgnoreIndex,
+ NeverIndex
+ }
+}
diff --git a/src/Shared/Hpack/README.md b/src/Shared/Hpack/README.md
new file mode 100644
index 000000000000..d18485cceac6
--- /dev/null
+++ b/src/Shared/Hpack/README.md
@@ -0,0 +1,3 @@
+HPack dynamic compression. These files are kept separate to help avoid ASP.NET Core dependencies being added to them.
+
+Runtime currently doesn't implement HPack dynamic compression. These files will move into runtime shareable code in the future when support is added to runtime.
\ No newline at end of file
diff --git a/src/Shared/Hpack/StatusCodes.cs b/src/Shared/Hpack/StatusCodes.cs
new file mode 100644
index 000000000000..eb67205586f0
--- /dev/null
+++ b/src/Shared/Hpack/StatusCodes.cs
@@ -0,0 +1,151 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Text;
+
+namespace System.Net.Http.HPack
+{
+ internal static partial class StatusCodes
+ {
+ public static string ToStatusString(int statusCode)
+ {
+ switch (statusCode)
+ {
+ case (int)HttpStatusCode.Continue:
+ return "100";
+ case (int)HttpStatusCode.SwitchingProtocols:
+ return "101";
+ case (int)HttpStatusCode.Processing:
+ return "102";
+
+ case (int)HttpStatusCode.OK:
+ return "200";
+ case (int)HttpStatusCode.Created:
+ return "201";
+ case (int)HttpStatusCode.Accepted:
+ return "202";
+ case (int)HttpStatusCode.NonAuthoritativeInformation:
+ return "203";
+ case (int)HttpStatusCode.NoContent:
+ return "204";
+ case (int)HttpStatusCode.ResetContent:
+ return "205";
+ case (int)HttpStatusCode.PartialContent:
+ return "206";
+ case (int)HttpStatusCode.MultiStatus:
+ return "207";
+ case (int)HttpStatusCode.AlreadyReported:
+ return "208";
+ case (int)HttpStatusCode.IMUsed:
+ return "226";
+
+ case (int)HttpStatusCode.MultipleChoices:
+ return "300";
+ case (int)HttpStatusCode.MovedPermanently:
+ return "301";
+ case (int)HttpStatusCode.Found:
+ return "302";
+ case (int)HttpStatusCode.SeeOther:
+ return "303";
+ case (int)HttpStatusCode.NotModified:
+ return "304";
+ case (int)HttpStatusCode.UseProxy:
+ return "305";
+ case (int)HttpStatusCode.Unused:
+ return "306";
+ case (int)HttpStatusCode.TemporaryRedirect:
+ return "307";
+ case (int)HttpStatusCode.PermanentRedirect:
+ return "308";
+
+ case (int)HttpStatusCode.BadRequest:
+ return "400";
+ case (int)HttpStatusCode.Unauthorized:
+ return "401";
+ case (int)HttpStatusCode.PaymentRequired:
+ return "402";
+ case (int)HttpStatusCode.Forbidden:
+ return "403";
+ case (int)HttpStatusCode.NotFound:
+ return "404";
+ case (int)HttpStatusCode.MethodNotAllowed:
+ return "405";
+ case (int)HttpStatusCode.NotAcceptable:
+ return "406";
+ case (int)HttpStatusCode.ProxyAuthenticationRequired:
+ return "407";
+ case (int)HttpStatusCode.RequestTimeout:
+ return "408";
+ case (int)HttpStatusCode.Conflict:
+ return "409";
+ case (int)HttpStatusCode.Gone:
+ return "410";
+ case (int)HttpStatusCode.LengthRequired:
+ return "411";
+ case (int)HttpStatusCode.PreconditionFailed:
+ return "412";
+ case (int)HttpStatusCode.RequestEntityTooLarge:
+ return "413";
+ case (int)HttpStatusCode.RequestUriTooLong:
+ return "414";
+ case (int)HttpStatusCode.UnsupportedMediaType:
+ return "415";
+ case (int)HttpStatusCode.RequestedRangeNotSatisfiable:
+ return "416";
+ case (int)HttpStatusCode.ExpectationFailed:
+ return "417";
+ case (int)418:
+ return "418";
+ case (int)419:
+ return "419";
+ case (int)HttpStatusCode.MisdirectedRequest:
+ return "421";
+ case (int)HttpStatusCode.UnprocessableEntity:
+ return "422";
+ case (int)HttpStatusCode.Locked:
+ return "423";
+ case (int)HttpStatusCode.FailedDependency:
+ return "424";
+ case (int)HttpStatusCode.UpgradeRequired:
+ return "426";
+ case (int)HttpStatusCode.PreconditionRequired:
+ return "428";
+ case (int)HttpStatusCode.TooManyRequests:
+ return "429";
+ case (int)HttpStatusCode.RequestHeaderFieldsTooLarge:
+ return "431";
+ case (int)HttpStatusCode.UnavailableForLegalReasons:
+ return "451";
+
+ case (int)HttpStatusCode.InternalServerError:
+ return "500";
+ case (int)HttpStatusCode.NotImplemented:
+ return "501";
+ case (int)HttpStatusCode.BadGateway:
+ return "502";
+ case (int)HttpStatusCode.ServiceUnavailable:
+ return "503";
+ case (int)HttpStatusCode.GatewayTimeout:
+ return "504";
+ case (int)HttpStatusCode.HttpVersionNotSupported:
+ return "505";
+ case (int)HttpStatusCode.VariantAlsoNegotiates:
+ return "506";
+ case (int)HttpStatusCode.InsufficientStorage:
+ return "507";
+ case (int)HttpStatusCode.LoopDetected:
+ return "508";
+ case (int)HttpStatusCode.NotExtended:
+ return "510";
+ case (int)HttpStatusCode.NetworkAuthenticationRequired:
+ return "511";
+
+ default:
+ return statusCode.ToString(CultureInfo.InvariantCulture);
+
+ }
+ }
+ }
+}
diff --git a/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs b/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs
index d09f1841349d..97cdea1c50f7 100644
--- a/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs
+++ b/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs
@@ -8,7 +8,7 @@
namespace System.Net.Http.HPack
{
- internal static class HPackEncoder
+ internal partial class HPackEncoder
{
// Things we should add:
// * Huffman encoding
@@ -109,6 +109,70 @@ public static bool EncodeLiteralHeaderFieldWithoutIndexing(int index, string val
return false;
}
+ /// Encodes a "Literal Header Field never Indexing".
+ public static bool EncodeLiteralHeaderFieldNeverIndexing(int index, string value, Span destination, out int bytesWritten)
+ {
+ // From https://tools.ietf.org/html/rfc7541#section-6.2.3
+ // ------------------------------------------------------
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 0 | 0 | 1 | Index (4+) |
+ // +---+---+-----------------------+
+ // | H | Value Length (7+) |
+ // +---+---------------------------+
+ // | Value String (Length octets) |
+ // +-------------------------------+
+
+ if ((uint)destination.Length >= 2)
+ {
+ destination[0] = 0x10;
+ if (IntegerEncoder.Encode(index, 4, destination, out int indexLength))
+ {
+ Debug.Assert(indexLength >= 1);
+ if (EncodeStringLiteral(value, destination.Slice(indexLength), out int nameLength))
+ {
+ bytesWritten = indexLength + nameLength;
+ return true;
+ }
+ }
+ }
+
+ bytesWritten = 0;
+ return false;
+ }
+
+ /// Encodes a "Literal Header Field with Indexing".
+ public static bool EncodeLiteralHeaderFieldIndexing(int index, string value, Span destination, out int bytesWritten)
+ {
+ // From https://tools.ietf.org/html/rfc7541#section-6.2.2
+ // ------------------------------------------------------
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 1 | Index (6+) |
+ // +---+---+-----------------------+
+ // | H | Value Length (7+) |
+ // +---+---------------------------+
+ // | Value String (Length octets) |
+ // +-------------------------------+
+
+ if ((uint)destination.Length >= 2)
+ {
+ destination[0] = 0x40;
+ if (IntegerEncoder.Encode(index, 6, destination, out int indexLength))
+ {
+ Debug.Assert(indexLength >= 1);
+ if (EncodeStringLiteral(value, destination.Slice(indexLength), out int nameLength))
+ {
+ bytesWritten = indexLength + nameLength;
+ return true;
+ }
+ }
+ }
+
+ bytesWritten = 0;
+ return false;
+ }
+
///
/// Encodes a "Literal Header Field without Indexing", but only the index portion;
/// a subsequent call to EncodeStringLiteral must be used to encode the associated value.
@@ -144,6 +208,27 @@ public static bool EncodeLiteralHeaderFieldWithoutIndexing(int index, Span
return false;
}
+ /// Encodes a "Literal Header Field with Indexing - New Name".
+ public static bool EncodeLiteralHeaderFieldIndexingNewName(string name, string value, Span destination, out int bytesWritten)
+ {
+ // From https://tools.ietf.org/html/rfc7541#section-6.2.2
+ // ------------------------------------------------------
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 1 | 0 |
+ // +---+---+-----------------------+
+ // | H | Name Length (7+) |
+ // +---+---------------------------+
+ // | Name String (Length octets) |
+ // +---+---------------------------+
+ // | H | Value Length (7+) |
+ // +---+---------------------------+
+ // | Value String (Length octets) |
+ // +-------------------------------+
+
+ return EncodeLiteralHeaderNewNameCore(0x40, name, value, destination, out bytesWritten);
+ }
+
/// Encodes a "Literal Header Field without Indexing - New Name".
public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, string value, Span destination, out int bytesWritten)
{
@@ -162,9 +247,35 @@ public static bool EncodeLiteralHeaderFieldWithoutIndexingNewName(string name, s
// | Value String (Length octets) |
// +-------------------------------+
+ return EncodeLiteralHeaderNewNameCore(0, name, value, destination, out bytesWritten);
+ }
+
+ /// Encodes a "Literal Header Field never Indexing - New Name".
+ public static bool EncodeLiteralHeaderFieldNeverIndexingNewName(string name, string value, Span destination, out int bytesWritten)
+ {
+ // From https://tools.ietf.org/html/rfc7541#section-6.2.3
+ // ------------------------------------------------------
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 0 | 0 | 1 | 0 |
+ // +---+---+-----------------------+
+ // | H | Name Length (7+) |
+ // +---+---------------------------+
+ // | Name String (Length octets) |
+ // +---+---------------------------+
+ // | H | Value Length (7+) |
+ // +---+---------------------------+
+ // | Value String (Length octets) |
+ // +-------------------------------+
+
+ return EncodeLiteralHeaderNewNameCore(0x10, name, value, destination, out bytesWritten);
+ }
+
+ private static bool EncodeLiteralHeaderNewNameCore(byte mask, string name, string value, Span destination, out int bytesWritten)
+ {
if ((uint)destination.Length >= 3)
{
- destination[0] = 0;
+ destination[0] = mask;
if (EncodeLiteralHeaderName(name, destination.Slice(1), out int nameLength) &&
EncodeStringLiteral(value, destination.Slice(1 + nameLength), out int valueLength))
{
@@ -372,6 +483,25 @@ public static bool EncodeStringLiteral(string value, Span destination, out
return false;
}
+ public static bool EncodeDynamicTableSizeUpdate(int value, Span destination, out int bytesWritten)
+ {
+ // From https://tools.ietf.org/html/rfc7541#section-6.3
+ // ----------------------------------------------------
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 0 | 1 | Max size (5+) |
+ // +---+---------------------------+
+
+ if (destination.Length != 0)
+ {
+ destination[0] = 0x20;
+ return IntegerEncoder.Encode(value, 5, destination, out bytesWritten);
+ }
+
+ bytesWritten = 0;
+ return false;
+ }
+
public static bool EncodeStringLiterals(ReadOnlySpan values, string? separator, Span destination, out int bytesWritten)
{
bytesWritten = 0;
diff --git a/src/Shared/runtime/Http2/Hpack/StatusCodes.cs b/src/Shared/runtime/Http2/Hpack/StatusCodes.cs
index b701fa79f41a..01c42abbc524 100644
--- a/src/Shared/runtime/Http2/Hpack/StatusCodes.cs
+++ b/src/Shared/runtime/Http2/Hpack/StatusCodes.cs
@@ -7,7 +7,7 @@
namespace System.Net.Http.HPack
{
- internal static class StatusCodes
+ internal static partial class StatusCodes
{
// This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static