Eliminate HTTP2 HPack enumerator allocations (#19393)
This commit is contained in:
parent
4902672a3e
commit
f34e8128c7
|
|
@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
private readonly object _writeLock = new object();
|
private readonly object _writeLock = new object();
|
||||||
private readonly Http2Frame _outgoingFrame;
|
private readonly Http2Frame _outgoingFrame;
|
||||||
private readonly HPackEncoder _hpackEncoder = new HPackEncoder();
|
private readonly HPackEncoder _hpackEncoder = new HPackEncoder();
|
||||||
|
private readonly Http2HeadersEnumerator _headersEnumerator = new Http2HeadersEnumerator();
|
||||||
private readonly ConcurrentPipeWriter _outputWriter;
|
private readonly ConcurrentPipeWriter _outputWriter;
|
||||||
private readonly ConnectionContext _connectionContext;
|
private readonly ConnectionContext _connectionContext;
|
||||||
private readonly Http2Connection _http2Connection;
|
private readonly Http2Connection _http2Connection;
|
||||||
|
|
@ -160,7 +161,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
| Padding (*) ...
|
| Padding (*) ...
|
||||||
+---------------------------------------------------------------+
|
+---------------------------------------------------------------+
|
||||||
*/
|
*/
|
||||||
public void WriteResponseHeaders(int streamId, int statusCode, Http2HeadersFrameFlags headerFrameFlags, IHeaderDictionary headers)
|
public void WriteResponseHeaders(int streamId, int statusCode, Http2HeadersFrameFlags headerFrameFlags, HttpResponseHeaders headers)
|
||||||
{
|
{
|
||||||
lock (_writeLock)
|
lock (_writeLock)
|
||||||
{
|
{
|
||||||
|
|
@ -171,9 +172,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_headersEnumerator.Initialize(headers);
|
||||||
_outgoingFrame.PrepareHeaders(headerFrameFlags, streamId);
|
_outgoingFrame.PrepareHeaders(headerFrameFlags, streamId);
|
||||||
var buffer = _headerEncodingBuffer.AsSpan();
|
var buffer = _headerEncodingBuffer.AsSpan();
|
||||||
var done = _hpackEncoder.BeginEncode(statusCode, EnumerateHeaders(headers), buffer, out var payloadLength);
|
var done = _hpackEncoder.BeginEncode(statusCode, _headersEnumerator, buffer, out var payloadLength);
|
||||||
FinishWritingHeaders(streamId, payloadLength, done);
|
FinishWritingHeaders(streamId, payloadLength, done);
|
||||||
}
|
}
|
||||||
catch (HPackEncodingException hex)
|
catch (HPackEncodingException hex)
|
||||||
|
|
@ -196,9 +198,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_headersEnumerator.Initialize(headers);
|
||||||
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.END_STREAM, streamId);
|
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.END_STREAM, streamId);
|
||||||
var buffer = _headerEncodingBuffer.AsSpan();
|
var buffer = _headerEncodingBuffer.AsSpan();
|
||||||
var done = _hpackEncoder.BeginEncode(EnumerateHeaders(headers), buffer, out var payloadLength);
|
var done = _hpackEncoder.BeginEncode(_headersEnumerator, buffer, out var payloadLength);
|
||||||
FinishWritingHeaders(streamId, payloadLength, done);
|
FinishWritingHeaders(streamId, payloadLength, done);
|
||||||
}
|
}
|
||||||
catch (HPackEncodingException hex)
|
catch (HPackEncodingException hex)
|
||||||
|
|
@ -662,16 +665,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
flowControl.Abort();
|
flowControl.Abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<KeyValuePair<string, string>> EnumerateHeaders(IHeaderDictionary headers)
|
|
||||||
{
|
|
||||||
foreach (var header in headers)
|
|
||||||
{
|
|
||||||
foreach (var value in header.Value)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<string, string>(header.Key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
// 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.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
{
|
||||||
|
internal sealed class Http2HeadersEnumerator : IEnumerator<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
private bool _isTrailers;
|
||||||
|
private HttpResponseHeaders.Enumerator _headersEnumerator;
|
||||||
|
private HttpResponseTrailers.Enumerator _trailersEnumerator;
|
||||||
|
private IEnumerator<KeyValuePair<string, StringValues>> _genericEnumerator;
|
||||||
|
private StringValues.Enumerator _stringValuesEnumerator;
|
||||||
|
|
||||||
|
public KeyValuePair<string, string> Current { get; private set; }
|
||||||
|
object IEnumerator.Current => Current;
|
||||||
|
|
||||||
|
public Http2HeadersEnumerator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(HttpResponseHeaders headers)
|
||||||
|
{
|
||||||
|
_headersEnumerator = headers.GetEnumerator();
|
||||||
|
_trailersEnumerator = default;
|
||||||
|
_genericEnumerator = null;
|
||||||
|
_isTrailers = false;
|
||||||
|
|
||||||
|
_stringValuesEnumerator = default;
|
||||||
|
Current = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(HttpResponseTrailers headers)
|
||||||
|
{
|
||||||
|
_headersEnumerator = default;
|
||||||
|
_trailersEnumerator = headers.GetEnumerator();
|
||||||
|
_genericEnumerator = null;
|
||||||
|
_isTrailers = true;
|
||||||
|
|
||||||
|
_stringValuesEnumerator = default;
|
||||||
|
Current = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(IDictionary<string, StringValues> headers)
|
||||||
|
{
|
||||||
|
_headersEnumerator = default;
|
||||||
|
_trailersEnumerator = default;
|
||||||
|
_genericEnumerator = headers.GetEnumerator();
|
||||||
|
_isTrailers = false;
|
||||||
|
|
||||||
|
_stringValuesEnumerator = default;
|
||||||
|
Current = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (MoveNextOnStringEnumerator())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetNextStringEnumerator(out _stringValuesEnumerator))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MoveNextOnStringEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCurrentKey()
|
||||||
|
{
|
||||||
|
if (_genericEnumerator != null)
|
||||||
|
{
|
||||||
|
return _genericEnumerator.Current.Key;
|
||||||
|
}
|
||||||
|
else if (_isTrailers)
|
||||||
|
{
|
||||||
|
return _trailersEnumerator.Current.Key;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _headersEnumerator.Current.Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MoveNextOnStringEnumerator()
|
||||||
|
{
|
||||||
|
var result = _stringValuesEnumerator.MoveNext();
|
||||||
|
Current = result ? new KeyValuePair<string, string>(GetCurrentKey(), _stringValuesEnumerator.Current) : default;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetNextStringEnumerator(out StringValues.Enumerator enumerator)
|
||||||
|
{
|
||||||
|
if (_genericEnumerator != null)
|
||||||
|
{
|
||||||
|
if (!_genericEnumerator.MoveNext())
|
||||||
|
{
|
||||||
|
enumerator = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
enumerator = _genericEnumerator.Current.Value.GetEnumerator();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_isTrailers)
|
||||||
|
{
|
||||||
|
if (!_trailersEnumerator.MoveNext())
|
||||||
|
{
|
||||||
|
enumerator = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
enumerator = _trailersEnumerator.Current.Value.GetEnumerator();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!_headersEnumerator.MoveNext())
|
||||||
|
{
|
||||||
|
enumerator = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
enumerator = _headersEnumerator.Current.Value.GetEnumerator();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
if (_genericEnumerator != null)
|
||||||
|
{
|
||||||
|
_genericEnumerator.Reset();
|
||||||
|
}
|
||||||
|
else if (_isTrailers)
|
||||||
|
{
|
||||||
|
_trailersEnumerator.Reset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_headersEnumerator.Reset();
|
||||||
|
}
|
||||||
|
_stringValuesEnumerator = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,12 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Net.Http.HPack;
|
using System.Net.Http.HPack;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
@ -94,11 +99,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
var length = 0;
|
var length = 0;
|
||||||
if (statusCode.HasValue)
|
if (statusCode.HasValue)
|
||||||
{
|
{
|
||||||
Assert.True(encoder.BeginEncode(statusCode.Value, headers, payload, out length));
|
Assert.True(encoder.BeginEncode(statusCode.Value, GetHeadersEnumerator(headers), payload, out length));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Assert.True(encoder.BeginEncode(headers, payload, out length));
|
Assert.True(encoder.BeginEncode(GetHeadersEnumerator(headers), payload, out length));
|
||||||
}
|
}
|
||||||
Assert.Equal(expectedPayload.Length, length);
|
Assert.Equal(expectedPayload.Length, length);
|
||||||
|
|
||||||
|
|
@ -159,7 +164,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
// When !exactSize, slices are one byte short of fitting the next header
|
// When !exactSize, slices are one byte short of fitting the next header
|
||||||
var sliceLength = expectedStatusCodePayload.Length + (exactSize ? 0 : expectedDateHeaderPayload.Length - 1);
|
var sliceLength = expectedStatusCodePayload.Length + (exactSize ? 0 : expectedDateHeaderPayload.Length - 1);
|
||||||
Assert.False(encoder.BeginEncode(statusCode, headers, payload.Slice(offset, sliceLength), out var length));
|
Assert.False(encoder.BeginEncode(statusCode, GetHeadersEnumerator(headers), payload.Slice(offset, sliceLength), out var length));
|
||||||
Assert.Equal(expectedStatusCodePayload.Length, length);
|
Assert.Equal(expectedStatusCodePayload.Length, length);
|
||||||
Assert.Equal(expectedStatusCodePayload, payload.Slice(0, length).ToArray());
|
Assert.Equal(expectedStatusCodePayload, payload.Slice(0, length).ToArray());
|
||||||
|
|
||||||
|
|
@ -184,5 +189,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
Assert.Equal(expectedServerHeaderPayload.Length, length);
|
Assert.Equal(expectedServerHeaderPayload.Length, length);
|
||||||
Assert.Equal(expectedServerHeaderPayload, payload.Slice(offset, length).ToArray());
|
Assert.Equal(expectedServerHeaderPayload, payload.Slice(offset, length).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable<KeyValuePair<string, string>> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
// 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.Buffers;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
{
|
||||||
|
public class Http2FrameWriterBenchmark
|
||||||
|
{
|
||||||
|
private MemoryPool<byte> _memoryPool;
|
||||||
|
private Pipe _pipe;
|
||||||
|
private Http2FrameWriter _frameWriter;
|
||||||
|
private HttpResponseHeaders _responseHeaders;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void GlobalSetup()
|
||||||
|
{
|
||||||
|
_memoryPool = SlabMemoryPoolFactory.Create();
|
||||||
|
|
||||||
|
var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
|
||||||
|
_pipe = new Pipe(options);
|
||||||
|
|
||||||
|
_frameWriter = new Http2FrameWriter(
|
||||||
|
new NullPipeWriter(),
|
||||||
|
connectionContext: null,
|
||||||
|
http2Connection: null,
|
||||||
|
new OutputFlowControl(initialWindowSize: uint.MaxValue),
|
||||||
|
timeoutControl: null,
|
||||||
|
minResponseDataRate: null,
|
||||||
|
"TestConnectionId",
|
||||||
|
_memoryPool,
|
||||||
|
new KestrelTrace(NullLogger.Instance));
|
||||||
|
|
||||||
|
_responseHeaders = new HttpResponseHeaders();
|
||||||
|
_responseHeaders.HeaderContentType = "application/json";
|
||||||
|
_responseHeaders.HeaderContentLength = "1024";
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void WriteResponseHeaders()
|
||||||
|
{
|
||||||
|
_frameWriter.WriteResponseHeaders(0, 200, Http2HeadersFrameFlags.END_HEADERS, _responseHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
[GlobalCleanup]
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_pipe.Writer.Complete();
|
||||||
|
_memoryPool?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
// 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.IO.Pipelines;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
{
|
||||||
|
internal class NullPipeWriter : PipeWriter
|
||||||
|
{
|
||||||
|
private byte[] _buffer = new byte[1024 * 128];
|
||||||
|
|
||||||
|
public override void Advance(int bytes)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void CancelPendingFlush()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Complete(Exception exception = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValueTask<FlushResult> FlushAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return new ValueTask<FlushResult>(new FlushResult(false, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Memory<byte> GetMemory(int sizeHint = 0)
|
||||||
|
{
|
||||||
|
return _buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Span<byte> GetSpan(int sizeHint = 0)
|
||||||
|
{
|
||||||
|
return _buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,4 +38,4 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -504,7 +505,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
|
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
|
||||||
|
|
||||||
var buffer = _headerEncodingBuffer.AsSpan();
|
var buffer = _headerEncodingBuffer.AsSpan();
|
||||||
var done = _hpackEncoder.BeginEncode(headers, buffer, out var length);
|
var done = _hpackEncoder.BeginEncode(GetHeadersEnumerator(headers), buffer, out var length);
|
||||||
frame.PayloadLength = length;
|
frame.PayloadLength = length;
|
||||||
|
|
||||||
if (done)
|
if (done)
|
||||||
|
|
@ -539,6 +540,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
return FlushAsync(writableBuffer);
|
return FlushAsync(writableBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable<KeyValuePair<string, string>> 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;
|
||||||
|
}
|
||||||
|
|
||||||
/* https://tools.ietf.org/html/rfc7540#section-6.2
|
/* https://tools.ietf.org/html/rfc7540#section-6.2
|
||||||
+---------------+
|
+---------------+
|
||||||
|Pad Length? (8)|
|
|Pad Length? (8)|
|
||||||
|
|
@ -565,7 +577,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
extendedHeader[0] = padLength;
|
extendedHeader[0] = padLength;
|
||||||
var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength);
|
var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength);
|
||||||
|
|
||||||
_hpackEncoder.BeginEncode(headers, payload, out var length);
|
_hpackEncoder.BeginEncode(GetHeadersEnumerator(headers), payload, out var length);
|
||||||
var padding = buffer.Slice(extendedHeaderLength + length, padLength);
|
var padding = buffer.Slice(extendedHeaderLength + length, padLength);
|
||||||
padding.Fill(0);
|
padding.Fill(0);
|
||||||
|
|
||||||
|
|
@ -608,7 +620,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
extendedHeader[4] = priority;
|
extendedHeader[4] = priority;
|
||||||
var payload = buffer.Slice(extendedHeaderLength);
|
var payload = buffer.Slice(extendedHeaderLength);
|
||||||
|
|
||||||
_hpackEncoder.BeginEncode(headers, payload, out var length);
|
_hpackEncoder.BeginEncode(GetHeadersEnumerator(headers), payload, out var length);
|
||||||
|
|
||||||
frame.PayloadLength = extendedHeaderLength + length;
|
frame.PayloadLength = extendedHeaderLength + length;
|
||||||
|
|
||||||
|
|
@ -655,7 +667,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
extendedHeader[5] = priority;
|
extendedHeader[5] = priority;
|
||||||
var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength);
|
var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength);
|
||||||
|
|
||||||
_hpackEncoder.BeginEncode(headers, payload, out var length);
|
_hpackEncoder.BeginEncode(GetHeadersEnumerator(headers), payload, out var length);
|
||||||
var padding = buffer.Slice(extendedHeaderLength + length, padLength);
|
var padding = buffer.Slice(extendedHeaderLength + length, padLength);
|
||||||
padding.Fill(0);
|
padding.Fill(0);
|
||||||
|
|
||||||
|
|
@ -776,7 +788,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
frame.PrepareHeaders(flags, streamId);
|
frame.PrepareHeaders(flags, streamId);
|
||||||
var buffer = _headerEncodingBuffer.AsMemory();
|
var buffer = _headerEncodingBuffer.AsMemory();
|
||||||
var done = _hpackEncoder.BeginEncode(headers, buffer.Span, out var length);
|
var done = _hpackEncoder.BeginEncode(GetHeadersEnumerator(headers), buffer.Span, out var length);
|
||||||
frame.PayloadLength = length;
|
frame.PayloadLength = length;
|
||||||
|
|
||||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||||
|
|
@ -869,7 +881,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
frame.PrepareContinuation(flags, streamId);
|
frame.PrepareContinuation(flags, streamId);
|
||||||
var buffer = _headerEncodingBuffer.AsMemory();
|
var buffer = _headerEncodingBuffer.AsMemory();
|
||||||
var done = _hpackEncoder.BeginEncode(headers, buffer.Span, out var length);
|
var done = _hpackEncoder.BeginEncode(GetHeadersEnumerator(headers), buffer.Span, out var length);
|
||||||
frame.PayloadLength = length;
|
frame.PayloadLength = length;
|
||||||
|
|
||||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using IHttpHeadersHandler = System.Net.Http.IHttpHeadersHandler;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http2Cat
|
namespace Microsoft.AspNetCore.Http2Cat
|
||||||
{
|
{
|
||||||
|
|
@ -235,7 +236,7 @@ namespace Microsoft.AspNetCore.Http2Cat
|
||||||
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
|
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
|
||||||
|
|
||||||
var buffer = _headerEncodingBuffer.AsSpan();
|
var buffer = _headerEncodingBuffer.AsSpan();
|
||||||
var done = _hpackEncoder.BeginEncode(headers, buffer, out var length);
|
var done = _hpackEncoder.BeginEncode(headers.GetEnumerator(), buffer, out var length);
|
||||||
frame.PayloadLength = length;
|
frame.PayloadLength = length;
|
||||||
|
|
||||||
if (done)
|
if (done)
|
||||||
|
|
@ -334,7 +335,7 @@ namespace Microsoft.AspNetCore.Http2Cat
|
||||||
extendedHeader[0] = padLength;
|
extendedHeader[0] = padLength;
|
||||||
var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength);
|
var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength);
|
||||||
|
|
||||||
_hpackEncoder.BeginEncode(headers, payload, out var length);
|
_hpackEncoder.BeginEncode(headers.GetEnumerator(), payload, out var length);
|
||||||
var padding = buffer.Slice(extendedHeaderLength + length, padLength);
|
var padding = buffer.Slice(extendedHeaderLength + length, padLength);
|
||||||
padding.Fill(0);
|
padding.Fill(0);
|
||||||
|
|
||||||
|
|
@ -376,7 +377,7 @@ namespace Microsoft.AspNetCore.Http2Cat
|
||||||
extendedHeader[4] = priority;
|
extendedHeader[4] = priority;
|
||||||
var payload = buffer.Slice(extendedHeaderLength);
|
var payload = buffer.Slice(extendedHeaderLength);
|
||||||
|
|
||||||
_hpackEncoder.BeginEncode(headers, payload, out var length);
|
_hpackEncoder.BeginEncode(headers.GetEnumerator(), payload, out var length);
|
||||||
|
|
||||||
frame.PayloadLength = extendedHeaderLength + length;
|
frame.PayloadLength = extendedHeaderLength + length;
|
||||||
|
|
||||||
|
|
@ -422,7 +423,7 @@ namespace Microsoft.AspNetCore.Http2Cat
|
||||||
extendedHeader[5] = priority;
|
extendedHeader[5] = priority;
|
||||||
var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength);
|
var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength);
|
||||||
|
|
||||||
_hpackEncoder.BeginEncode(headers, payload, out var length);
|
_hpackEncoder.BeginEncode(headers.GetEnumerator(), payload, out var length);
|
||||||
var padding = buffer.Slice(extendedHeaderLength + length, padLength);
|
var padding = buffer.Slice(extendedHeaderLength + length, padLength);
|
||||||
padding.Fill(0);
|
padding.Fill(0);
|
||||||
|
|
||||||
|
|
@ -548,7 +549,7 @@ namespace Microsoft.AspNetCore.Http2Cat
|
||||||
|
|
||||||
frame.PrepareHeaders(flags, streamId);
|
frame.PrepareHeaders(flags, streamId);
|
||||||
var buffer = _headerEncodingBuffer.AsMemory();
|
var buffer = _headerEncodingBuffer.AsMemory();
|
||||||
var done = _hpackEncoder.BeginEncode(headers, buffer.Span, out var length);
|
var done = _hpackEncoder.BeginEncode(headers.GetEnumerator(), buffer.Span, out var length);
|
||||||
frame.PayloadLength = length;
|
frame.PayloadLength = length;
|
||||||
|
|
||||||
WriteHeader(frame, outputWriter);
|
WriteHeader(frame, outputWriter);
|
||||||
|
|
@ -641,7 +642,7 @@ namespace Microsoft.AspNetCore.Http2Cat
|
||||||
|
|
||||||
frame.PrepareContinuation(flags, streamId);
|
frame.PrepareContinuation(flags, streamId);
|
||||||
var buffer = _headerEncodingBuffer.AsMemory();
|
var buffer = _headerEncodingBuffer.AsMemory();
|
||||||
var done = _hpackEncoder.BeginEncode(headers, buffer.Span, out var length);
|
var done = _hpackEncoder.BeginEncode(headers.GetEnumerator(), buffer.Span, out var length);
|
||||||
frame.PayloadLength = length;
|
frame.PayloadLength = length;
|
||||||
|
|
||||||
WriteHeader(frame, outputWriter);
|
WriteHeader(frame, outputWriter);
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,30 @@
|
||||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||||
// See THIRD-PARTY-NOTICES.TXT in the project root for license information.
|
// See THIRD-PARTY-NOTICES.TXT in the project root for license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
#if KESTREL
|
||||||
|
using HeadersEnumerator = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2HeadersEnumerator;
|
||||||
|
#else
|
||||||
|
using HeadersEnumerator = System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, string>>;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace System.Net.Http.HPack
|
namespace System.Net.Http.HPack
|
||||||
{
|
{
|
||||||
internal class HPackEncoder
|
internal class HPackEncoder
|
||||||
{
|
{
|
||||||
private IEnumerator<KeyValuePair<string, string>> _enumerator;
|
private HeadersEnumerator _enumerator;
|
||||||
|
|
||||||
public bool BeginEncode(IEnumerable<KeyValuePair<string, string>> headers, Span<byte> buffer, out int length)
|
public bool BeginEncode(HeadersEnumerator enumerator, Span<byte> buffer, out int length)
|
||||||
{
|
{
|
||||||
_enumerator = headers.GetEnumerator();
|
_enumerator = enumerator;
|
||||||
_enumerator.MoveNext();
|
_enumerator.MoveNext();
|
||||||
|
|
||||||
return Encode(buffer, out length);
|
return Encode(buffer, out length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool BeginEncode(int statusCode, IEnumerable<KeyValuePair<string, string>> headers, Span<byte> buffer, out int length)
|
public bool BeginEncode(int statusCode, HeadersEnumerator enumerator, Span<byte> buffer, out int length)
|
||||||
{
|
{
|
||||||
_enumerator = headers.GetEnumerator();
|
_enumerator = enumerator;
|
||||||
_enumerator.MoveNext();
|
_enumerator.MoveNext();
|
||||||
|
|
||||||
int statusCodeLength = EncodeStatusCode(statusCode, buffer);
|
int statusCodeLength = EncodeStatusCode(statusCode, buffer);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue