Use less stack for HttpResponseHeaders.CopyToFast (#7724)
This commit is contained in:
parent
46fe595606
commit
423de42849
File diff suppressed because it is too large
Load Diff
|
|
@ -10,6 +10,7 @@ using System.Runtime.Intrinsics.X86;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||
{
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
private readonly HttpRequestHeaders _collection;
|
||||
private readonly long _bits;
|
||||
private int _state;
|
||||
private int _next;
|
||||
private KeyValuePair<string, StringValues> _current;
|
||||
private readonly bool _hasUnknown;
|
||||
private Dictionary<string, StringValues>.Enumerator _unknownEnumerator;
|
||||
|
|
@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
_collection = collection;
|
||||
_bits = collection._bits;
|
||||
_state = 0;
|
||||
_next = 0;
|
||||
_current = default(KeyValuePair<string, StringValues>);
|
||||
_hasUnknown = collection.MaybeUnknown != null;
|
||||
_unknownEnumerator = _hasUnknown
|
||||
|
|
@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
public void Reset()
|
||||
{
|
||||
_state = 0;
|
||||
_next = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
return parsed;
|
||||
}
|
||||
|
||||
private static void ThrowInvalidContentLengthException(string value)
|
||||
{
|
||||
throw new InvalidOperationException(CoreStrings.FormatInvalidContentLength_InvalidNumber(value));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void SetValueUnknown(string key, in StringValues value)
|
||||
{
|
||||
|
|
@ -65,16 +70,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
Unknown[key] = value;
|
||||
}
|
||||
|
||||
private static void ThrowInvalidContentLengthException(string value)
|
||||
{
|
||||
throw new InvalidOperationException(CoreStrings.FormatInvalidContentLength_InvalidNumber(value));
|
||||
}
|
||||
|
||||
public partial struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
|
||||
{
|
||||
private readonly HttpResponseHeaders _collection;
|
||||
private readonly long _bits;
|
||||
private int _state;
|
||||
private int _next;
|
||||
private KeyValuePair<string, StringValues> _current;
|
||||
private readonly bool _hasUnknown;
|
||||
private Dictionary<string, StringValues>.Enumerator _unknownEnumerator;
|
||||
|
|
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
_collection = collection;
|
||||
_bits = collection._bits;
|
||||
_state = 0;
|
||||
_next = 0;
|
||||
_current = default;
|
||||
_hasUnknown = collection.MaybeUnknown != null;
|
||||
_unknownEnumerator = _hasUnknown
|
||||
|
|
@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
public void Reset()
|
||||
{
|
||||
_state = 0;
|
||||
_next = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
|
@ -31,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
private readonly HttpResponseTrailers _collection;
|
||||
private readonly long _bits;
|
||||
private int _state;
|
||||
private int _next;
|
||||
private KeyValuePair<string, StringValues> _current;
|
||||
private readonly bool _hasUnknown;
|
||||
private Dictionary<string, StringValues>.Enumerator _unknownEnumerator;
|
||||
|
|
@ -40,7 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
_collection = collection;
|
||||
_bits = collection._bits;
|
||||
_state = 0;
|
||||
_next = 0;
|
||||
_current = default;
|
||||
_hasUnknown = collection.MaybeUnknown != null;
|
||||
_unknownEnumerator = _hasUnknown
|
||||
|
|
@ -58,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
public void Reset()
|
||||
{
|
||||
_state = 0;
|
||||
_next = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,25 +8,23 @@ using System.Text;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||
{
|
||||
public class ResponseHeadersWritingBenchmark
|
||||
{
|
||||
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: " + Constants.ServerName);
|
||||
private static readonly byte[] _helloWorldPayload = Encoding.ASCII.GetBytes("Hello, World!");
|
||||
|
||||
private TestHttp1Connection _http1Connection;
|
||||
private HttpResponseHeaders _responseHeaders;
|
||||
private IHeaderDictionary _responseHeadersDict;
|
||||
private DateHeaderValueManager _dateHeaderValueManager;
|
||||
private Writer _writer;
|
||||
|
||||
private MemoryPool<byte> _memoryPool;
|
||||
|
||||
private DuplexPipe.DuplexPipePair _pair;
|
||||
private DateHeaderValueManager.DateHeaderValues DateHeaderValues => _dateHeaderValueManager.GetDateHeaderValues();
|
||||
|
||||
[Params(
|
||||
BenchmarkTypes.TechEmpowerPlaintext,
|
||||
|
|
@ -38,118 +36,113 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
public BenchmarkTypes Type { get; set; }
|
||||
|
||||
[Benchmark]
|
||||
public async Task Output()
|
||||
public void Output()
|
||||
{
|
||||
_http1Connection.Reset();
|
||||
_http1Connection.StatusCode = 200;
|
||||
_http1Connection.HttpVersionEnum = HttpVersion.Http11;
|
||||
_http1Connection.KeepAlive = true;
|
||||
|
||||
Task writeTask = Task.CompletedTask;
|
||||
switch (Type)
|
||||
{
|
||||
case BenchmarkTypes.TechEmpowerPlaintext:
|
||||
writeTask = TechEmpowerPlaintext();
|
||||
TechEmpowerPlaintext();
|
||||
break;
|
||||
case BenchmarkTypes.PlaintextChunked:
|
||||
writeTask = PlaintextChunked();
|
||||
PlaintextChunked();
|
||||
break;
|
||||
case BenchmarkTypes.PlaintextWithCookie:
|
||||
writeTask = PlaintextWithCookie();
|
||||
PlaintextWithCookie();
|
||||
break;
|
||||
case BenchmarkTypes.PlaintextChunkedWithCookie:
|
||||
writeTask = PlaintextChunkedWithCookie();
|
||||
PlaintextChunkedWithCookie();
|
||||
break;
|
||||
case BenchmarkTypes.LiveAspNet:
|
||||
writeTask = LiveAspNet();
|
||||
LiveAspNet();
|
||||
break;
|
||||
}
|
||||
|
||||
await writeTask;
|
||||
await _http1Connection.ProduceEndAsync();
|
||||
}
|
||||
|
||||
private Task TechEmpowerPlaintext()
|
||||
private void TechEmpowerPlaintext()
|
||||
{
|
||||
var responseHeaders = _http1Connection.ResponseHeaders;
|
||||
responseHeaders["Content-Type"] = "text/plain";
|
||||
var responseHeaders = _responseHeaders;
|
||||
responseHeaders.HeaderContentType = "text/plain";
|
||||
responseHeaders.ContentLength = _helloWorldPayload.Length;
|
||||
return _http1Connection.WriteAsync(new ArraySegment<byte>(_helloWorldPayload), default(CancellationToken));
|
||||
|
||||
var writer = new BufferWriter<PipeWriter>(_writer);
|
||||
_responseHeaders.CopyTo(ref writer);
|
||||
}
|
||||
|
||||
private Task PlaintextChunked()
|
||||
private void PlaintextChunked()
|
||||
{
|
||||
var responseHeaders = _http1Connection.ResponseHeaders;
|
||||
responseHeaders["Content-Type"] = "text/plain";
|
||||
return _http1Connection.WriteAsync(new ArraySegment<byte>(_helloWorldPayload), default(CancellationToken));
|
||||
var responseHeaders = _responseHeaders;
|
||||
responseHeaders.HeaderContentType = "text/plain";
|
||||
|
||||
var writer = new BufferWriter<PipeWriter>(_writer);
|
||||
_responseHeaders.CopyTo(ref writer);
|
||||
}
|
||||
|
||||
private Task LiveAspNet()
|
||||
private void LiveAspNet()
|
||||
{
|
||||
var responseHeaders = _http1Connection.ResponseHeaders;
|
||||
responseHeaders["Content-Encoding"] = "gzip";
|
||||
responseHeaders["Content-Type"] = "text/html; charset=utf-8";
|
||||
responseHeaders["Strict-Transport-Security"] = "max-age=31536000; includeSubdomains";
|
||||
responseHeaders["Vary"] = "Accept-Encoding";
|
||||
responseHeaders["X-Powered-By"] = "ASP.NET";
|
||||
return _http1Connection.WriteAsync(new ArraySegment<byte>(_helloWorldPayload), default(CancellationToken));
|
||||
var responseHeaders = _responseHeaders;
|
||||
responseHeaders.HeaderContentEncoding = "gzip";
|
||||
responseHeaders.HeaderContentType = "text/html; charset=utf-8";
|
||||
_responseHeadersDict["Strict-Transport-Security"] = "max-age=31536000; includeSubdomains";
|
||||
responseHeaders.HeaderVary = "Accept-Encoding";
|
||||
_responseHeadersDict["X-Powered-By"] = "ASP.NET";
|
||||
|
||||
var writer = new BufferWriter<PipeWriter>(_writer);
|
||||
_responseHeaders.CopyTo(ref writer);
|
||||
}
|
||||
|
||||
private Task PlaintextWithCookie()
|
||||
private void PlaintextWithCookie()
|
||||
{
|
||||
var responseHeaders = _http1Connection.ResponseHeaders;
|
||||
responseHeaders["Content-Type"] = "text/plain";
|
||||
responseHeaders["Set-Cookie"] = "prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric";
|
||||
var responseHeaders = _responseHeaders;
|
||||
responseHeaders.HeaderContentType = "text/plain";
|
||||
responseHeaders.HeaderSetCookie = "prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric";
|
||||
responseHeaders.ContentLength = _helloWorldPayload.Length;
|
||||
return _http1Connection.WriteAsync(new ArraySegment<byte>(_helloWorldPayload), default(CancellationToken));
|
||||
|
||||
var writer = new BufferWriter<PipeWriter>(_writer);
|
||||
_responseHeaders.CopyTo(ref writer);
|
||||
}
|
||||
|
||||
private Task PlaintextChunkedWithCookie()
|
||||
private void PlaintextChunkedWithCookie()
|
||||
{
|
||||
var responseHeaders = _http1Connection.ResponseHeaders;
|
||||
responseHeaders["Content-Type"] = "text/plain";
|
||||
responseHeaders["Set-Cookie"] = "prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric";
|
||||
return _http1Connection.WriteAsync(new ArraySegment<byte>(_helloWorldPayload), default(CancellationToken));
|
||||
var responseHeaders = _responseHeaders;
|
||||
responseHeaders.HeaderContentType = "text/plain";
|
||||
responseHeaders.HeaderSetCookie = "prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric";
|
||||
responseHeaders.HeaderTransferEncoding = "chunked";
|
||||
|
||||
var writer = new BufferWriter<PipeWriter>(_writer);
|
||||
_responseHeaders.CopyTo(ref writer);
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_responseHeaders = new HttpResponseHeaders();
|
||||
_responseHeadersDict = _responseHeaders;
|
||||
_dateHeaderValueManager = new DateHeaderValueManager();
|
||||
_dateHeaderValueManager.OnHeartbeat(DateTimeOffset.Now);
|
||||
_writer = new Writer();
|
||||
}
|
||||
|
||||
[IterationSetup]
|
||||
public void Setup()
|
||||
public void IterationSetup()
|
||||
{
|
||||
_memoryPool = KestrelMemoryPool.Create();
|
||||
var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
|
||||
_pair = DuplexPipe.CreateConnectionPair(options, options);
|
||||
|
||||
var serviceContext = new ServiceContext
|
||||
{
|
||||
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||
ServerOptions = new KestrelServerOptions(),
|
||||
Log = new MockTrace(),
|
||||
HttpParser = new HttpParser<Http1ParsingHandler>()
|
||||
};
|
||||
|
||||
var http1Connection = new TestHttp1Connection(new HttpConnectionContext
|
||||
{
|
||||
ServiceContext = serviceContext,
|
||||
ConnectionFeatures = new FeatureCollection(),
|
||||
MemoryPool = _memoryPool,
|
||||
TimeoutControl = new TimeoutControl(timeoutHandler: null),
|
||||
Transport = _pair.Transport
|
||||
});
|
||||
|
||||
http1Connection.Reset();
|
||||
serviceContext.DateHeaderValueManager.OnHeartbeat(DateTimeOffset.UtcNow);
|
||||
|
||||
_http1Connection = http1Connection;
|
||||
_responseHeaders.Reset();
|
||||
_responseHeaders.SetRawServer(Constants.ServerName, _bytesServer);
|
||||
_responseHeaders.SetRawDate(DateHeaderValues.String, DateHeaderValues.Bytes);
|
||||
}
|
||||
|
||||
[IterationCleanup]
|
||||
public void Cleanup()
|
||||
public class Writer : PipeWriter
|
||||
{
|
||||
_pair.Application.Input.Complete();
|
||||
_pair.Application.Output.Complete();
|
||||
_pair.Transport.Input.Complete();
|
||||
_pair.Transport.Output.Complete();
|
||||
_memoryPool.Dispose();
|
||||
private Memory<byte> _memory = new byte[4096 * 4];
|
||||
|
||||
public override Memory<byte> GetMemory(int sizeHint = 0) => _memory;
|
||||
|
||||
public override Span<byte> GetSpan(int sizeHint = 0) => _memory.Span;
|
||||
|
||||
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) => default;
|
||||
public override void OnReaderCompleted(Action<Exception, object> callback, object state) { }
|
||||
}
|
||||
|
||||
public enum BenchmarkTypes
|
||||
|
|
|
|||
|
|
@ -65,12 +65,12 @@ namespace CodeGenerator
|
|||
public bool FastCount { get; set; }
|
||||
public bool EnhancedSetter { get; set; }
|
||||
public bool PrimaryHeader { get; set; }
|
||||
public string TestBit() => $"(_bits & {1L << Index}L) != 0";
|
||||
public string TestTempBit() => $"(tempBits & {1L << Index}L) != 0";
|
||||
public string TestNotTempBit() => $"(tempBits & ~{1L << Index}L) == 0";
|
||||
public string TestNotBit() => $"(_bits & {1L << Index}L) == 0";
|
||||
public string SetBit() => $"_bits |= {1L << Index}L";
|
||||
public string ClearBit() => $"_bits &= ~{1L << Index}L";
|
||||
public string TestBit() => $"(_bits & {"0x" + (1L << Index).ToString("x")}L) != 0";
|
||||
public string TestTempBit() => $"(tempBits & {"0x" + (1L << Index).ToString("x")}L) != 0";
|
||||
public string TestNotTempBit() => $"(tempBits & ~{"0x" + (1L << Index).ToString("x")}L) == 0";
|
||||
public string TestNotBit() => $"(_bits & {"0x" + (1L << Index).ToString("x")}L) == 0";
|
||||
public string SetBit() => $"_bits |= {"0x" + (1L << Index).ToString("x")}L";
|
||||
public string ClearBit() => $"_bits &= ~{"0x" + (1L << Index).ToString("x")}L";
|
||||
|
||||
public string EqualIgnoreCaseBytes()
|
||||
{
|
||||
|
|
@ -133,6 +133,7 @@ namespace CodeGenerator
|
|||
"Date",
|
||||
"Content-Type",
|
||||
"Server",
|
||||
"Content-Length",
|
||||
};
|
||||
var commonHeaders = new[]
|
||||
{
|
||||
|
|
@ -264,7 +265,7 @@ namespace CodeGenerator
|
|||
.Concat(new[] { new KnownHeader
|
||||
{
|
||||
Name = "Content-Length",
|
||||
Index = -1,
|
||||
Index = 63,
|
||||
EnhancedSetter = enhancedHeaders.Contains("Content-Length"),
|
||||
PrimaryHeader = responsePrimaryHeaders.Contains("Content-Length")
|
||||
}})
|
||||
|
|
@ -286,7 +287,7 @@ namespace CodeGenerator
|
|||
|
||||
// 63 for responseHeaders as it steals one bit for Content-Length in CopyTo(ref MemoryPoolIterator output)
|
||||
Debug.Assert(responseHeaders.Length <= 63);
|
||||
Debug.Assert(responseHeaders.Max(x => x.Index) <= 62);
|
||||
Debug.Assert(responseHeaders.Count(x => x.Index == 63) == 1);
|
||||
|
||||
var loops = new[]
|
||||
{
|
||||
|
|
@ -327,9 +328,9 @@ namespace CodeGenerator
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
|
|
@ -339,7 +340,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
public partial class {loop.ClassName}
|
||||
{{{(loop.Bytes != null ?
|
||||
$@"
|
||||
private static byte[] _headerBytes = new byte[]
|
||||
private static ReadOnlySpan<byte> HeaderBytes => new byte[]
|
||||
{{
|
||||
{Each(loop.Bytes, b => $"{b},")}
|
||||
}};"
|
||||
|
|
@ -529,7 +530,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{{
|
||||
return;
|
||||
}}
|
||||
tempBits &= ~{1L << header.Index}L;
|
||||
tempBits &= ~{"0x" + (1L << header.Index).ToString("x")}L;
|
||||
}}
|
||||
")}
|
||||
}}
|
||||
|
|
@ -564,47 +565,66 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
return true;
|
||||
}}
|
||||
{(loop.ClassName == "HttpResponseHeaders" ? $@"
|
||||
internal void CopyToFast(ref BufferWriter<PipeWriter> output)
|
||||
internal unsafe void CopyToFast(ref BufferWriter<PipeWriter> output)
|
||||
{{
|
||||
var tempBits = _bits | (_contentLength.HasValue ? {1L << 63}L : 0);
|
||||
{Each(loop.Headers.Where(header => header.Identifier != "ContentLength").OrderBy(h => !h.PrimaryHeader), header => $@"
|
||||
if ({header.TestTempBit()})
|
||||
{{ {(header.EnhancedSetter == false ? "" : $@"
|
||||
if (_headers._raw{header.Identifier} != null)
|
||||
{{
|
||||
output.Write(_headers._raw{header.Identifier});
|
||||
}}
|
||||
else ")}
|
||||
{{
|
||||
var valueCount = _headers._{header.Identifier}.Count;
|
||||
for (var i = 0; i < valueCount; i++)
|
||||
var tempBits = (ulong)_bits | (_contentLength.HasValue ? {"0x" + (1L << 63).ToString("x")}L : 0);
|
||||
var next = 0;
|
||||
var keyStart = 0;
|
||||
var keyLength = 0;
|
||||
ref readonly StringValues values = ref Unsafe.AsRef<StringValues>(null);
|
||||
|
||||
do
|
||||
{{
|
||||
switch (next)
|
||||
{{{Each(loop.Headers.OrderBy(h => !h.PrimaryHeader).Select((h, i) => (Header: h, Index: i)), hi => $@"
|
||||
case {hi.Index}: // Header: ""{hi.Header.Name}""
|
||||
if ({hi.Header.TestTempBit()})
|
||||
{{
|
||||
var value = _headers._{header.Identifier}[i];
|
||||
if (value != null)
|
||||
tempBits ^= {"0x" + (1L << hi.Header.Index).ToString("x")}L;{(hi.Header.Identifier != "ContentLength" ? $@"{(hi.Header.EnhancedSetter == false ? $@"
|
||||
values = ref _headers._{hi.Header.Identifier};
|
||||
keyStart = {hi.Header.BytesOffset};
|
||||
keyLength = {hi.Header.BytesCount};
|
||||
next = {hi.Index + 1};
|
||||
break; // OutputHeader" : $@"
|
||||
if (_headers._raw{hi.Header.Identifier} != null)
|
||||
{{
|
||||
output.Write(new ReadOnlySpan<byte>(_headerBytes, {header.BytesOffset}, {header.BytesCount}));
|
||||
output.WriteAsciiNoValidation(value);
|
||||
output.Write(_headers._raw{hi.Header.Identifier});
|
||||
}}
|
||||
else
|
||||
{{
|
||||
values = ref _headers._{hi.Header.Identifier};
|
||||
keyStart = {hi.Header.BytesOffset};
|
||||
keyLength = {hi.Header.BytesCount};
|
||||
next = {hi.Index + 1};
|
||||
break; // OutputHeader
|
||||
}}")}" : $@"
|
||||
output.Write(HeaderBytes.Slice({hi.Header.BytesOffset}, {hi.Header.BytesCount}));
|
||||
output.WriteNumeric((ulong)ContentLength.Value);
|
||||
if (tempBits == 0)
|
||||
{{
|
||||
return;
|
||||
}}")}
|
||||
}}
|
||||
{(hi.Index + 1 < loop.Headers.Count() ? $"goto case {hi.Index + 1};" : "return;")}")}
|
||||
default:
|
||||
return;
|
||||
}}
|
||||
|
||||
// OutputHeader
|
||||
{{
|
||||
var valueCount = values.Count;
|
||||
var headerKey = HeaderBytes.Slice(keyStart, keyLength);
|
||||
for (var i = 0; i < valueCount; i++)
|
||||
{{
|
||||
var value = values[i];
|
||||
if (value != null)
|
||||
{{
|
||||
output.Write(headerKey);
|
||||
output.WriteAsciiNoValidation(value);
|
||||
}}
|
||||
}}
|
||||
|
||||
if({header.TestNotTempBit()})
|
||||
{{
|
||||
return;
|
||||
}}
|
||||
tempBits &= ~{1L << header.Index}L;
|
||||
}}{(header.Identifier == "Server" ? $@"
|
||||
if ((tempBits & {1L << 63}L) != 0)
|
||||
{{
|
||||
output.Write(new ReadOnlySpan<byte>(_headerBytes, {loop.Headers.First(x => x.Identifier == "ContentLength").BytesOffset}, {loop.Headers.First(x => x.Identifier == "ContentLength").BytesCount}));
|
||||
output.WriteNumeric((ulong)ContentLength.Value);
|
||||
|
||||
if((tempBits & ~{1L << 63}L) == 0)
|
||||
{{
|
||||
return;
|
||||
}}
|
||||
tempBits &= ~{1L << 63}L;
|
||||
}}" : "")}")}
|
||||
}}
|
||||
}} while (tempBits != 0);
|
||||
}}" : "")}
|
||||
{(loop.ClassName == "HttpRequestHeaders" ? $@"
|
||||
public unsafe void Append(byte* pKeyBytes, int keyLength, string value)
|
||||
|
|
@ -632,36 +652,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
public partial struct Enumerator
|
||||
{{
|
||||
// Compiled to Jump table
|
||||
public bool MoveNext()
|
||||
{{
|
||||
switch (_state)
|
||||
{{
|
||||
{Each(loop.Headers.Where(header => header.Identifier != "ContentLength"), header => $@"
|
||||
switch (_next)
|
||||
{{{Each(loop.Headers.Where(header => header.Identifier != "ContentLength"), header => $@"
|
||||
case {header.Index}:
|
||||
goto state{header.Index};
|
||||
")}
|
||||
case {loop.Headers.Count()}:
|
||||
goto state{loop.Headers.Count()};
|
||||
goto Header{header.Identifier};")}
|
||||
{(!loop.ClassName.Contains("Trailers") ? $@"case {loop.Headers.Count() - 1}:
|
||||
goto HeaderContentLength;" : "")}
|
||||
default:
|
||||
goto state_default;
|
||||
goto ExtraHeaders;
|
||||
}}
|
||||
{Each(loop.Headers.Where(header => header.Identifier != "ContentLength"), header => $@"
|
||||
state{header.Index}:
|
||||
Header{header.Identifier}: // case {header.Index}
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
_current = new KeyValuePair<string, StringValues>(""{header.Name}"", _collection._headers._{header.Identifier});
|
||||
_state = {header.Index + 1};
|
||||
_next = {header.Index + 1};
|
||||
return true;
|
||||
}}
|
||||
")}
|
||||
state{loop.Headers.Count()}:
|
||||
}}")}
|
||||
{(!loop.ClassName.Contains("Trailers") ? $@"HeaderContentLength: // case {loop.Headers.Count() - 1}
|
||||
if (_collection._contentLength.HasValue)
|
||||
{{
|
||||
_current = new KeyValuePair<string, StringValues>(""Content-Length"", HeaderUtilities.FormatNonNegativeInt64(_collection._contentLength.Value));
|
||||
_state = {loop.Headers.Count() + 1};
|
||||
_next = {loop.Headers.Count()};
|
||||
return true;
|
||||
}}
|
||||
state_default:
|
||||
}}" : "")}
|
||||
ExtraHeaders:
|
||||
if (!_hasUnknown || !_unknownEnumerator.MoveNext())
|
||||
{{
|
||||
_current = default(KeyValuePair<string, StringValues>);
|
||||
|
|
|
|||
Loading…
Reference in New Issue