This commit is contained in:
Justin Kotalik 2018-12-27 11:12:33 -08:00
commit 7269dbb73f
8 changed files with 153 additions and 77 deletions

View File

@ -44,11 +44,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
var deploymentResult = await DeployAsync(deploymentParameters); var deploymentResult = await DeployAsync(deploymentParameters);
var handler = new HttpClientHandler var client = CreateNonValidatingClient(deploymentResult);
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true
};
var client = deploymentResult.CreateClient(handler);
var response = await client.GetAsync("HttpsHelloWorld"); var response = await client.GetAsync("HttpsHelloWorld");
var responseText = await response.Content.ReadAsStringAsync(); var responseText = await response.Content.ReadAsStringAsync();
if (variant.HostingModel == HostingModel.OutOfProcess) if (variant.HostingModel == HostingModel.OutOfProcess)
@ -94,7 +90,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
}); });
var deploymentResult = await DeployAsync(deploymentParameters); var deploymentResult = await DeployAsync(deploymentParameters);
Assert.Equal(deploymentParameters.ApplicationBaseUriHint + appName, await deploymentResult.HttpClient.GetStringAsync($"/{appName}/ServerAddresses")); var client = CreateNonValidatingClient(deploymentResult);
Assert.Equal(deploymentParameters.ApplicationBaseUriHint + appName, await client.GetStringAsync($"/{appName}/ServerAddresses"));
} }
[ConditionalFact] [ConditionalFact]
@ -115,7 +113,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_HTTPS_PORT"] = "123"; deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_HTTPS_PORT"] = "123";
var deploymentResult = await DeployAsync(deploymentParameters); var deploymentResult = await DeployAsync(deploymentParameters);
Assert.Equal("123", await deploymentResult.HttpClient.GetStringAsync("/HTTPS_PORT")); var client = CreateNonValidatingClient(deploymentResult);
Assert.Equal("123", await client.GetStringAsync("/HTTPS_PORT"));
} }
[ConditionalFact] [ConditionalFact]
@ -142,7 +142,18 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
}); });
var deploymentResult = await DeployAsync(deploymentParameters); var deploymentResult = await DeployAsync(deploymentParameters);
Assert.Equal("NOVALUE", await deploymentResult.HttpClient.GetStringAsync("/HTTPS_PORT")); var client = CreateNonValidatingClient(deploymentResult);
Assert.Equal("NOVALUE", await client.GetStringAsync("/HTTPS_PORT"));
}
private static HttpClient CreateNonValidatingClient(IISDeploymentResult deploymentResult)
{
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true
};
return deploymentResult.CreateClient(handler);
} }
public static int GetNextSSLPort(int avoid = 0) public static int GetNextSSLPort(int avoid = 0)

View File

@ -5,59 +5,63 @@ using System;
using System.Buffers; using System.Buffers;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Text; using System.Text;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
internal static class ChunkWriter internal static class ChunkWriter
{ {
private static readonly ArraySegment<byte> _endChunkBytes = CreateAsciiByteArraySegment("\r\n");
private static readonly byte[] _hex = Encoding.ASCII.GetBytes("0123456789abcdef"); private static readonly byte[] _hex = Encoding.ASCII.GetBytes("0123456789abcdef");
private static ArraySegment<byte> CreateAsciiByteArraySegment(string text) public static int BeginChunkBytes(int dataCount, Span<byte> span)
{ {
var bytes = Encoding.ASCII.GetBytes(text);
return new ArraySegment<byte>(bytes);
}
public static ArraySegment<byte> BeginChunkBytes(int dataCount)
{
var bytes = new byte[10]
{
_hex[((dataCount >> 0x1c) & 0x0f)],
_hex[((dataCount >> 0x18) & 0x0f)],
_hex[((dataCount >> 0x14) & 0x0f)],
_hex[((dataCount >> 0x10) & 0x0f)],
_hex[((dataCount >> 0x0c) & 0x0f)],
_hex[((dataCount >> 0x08) & 0x0f)],
_hex[((dataCount >> 0x04) & 0x0f)],
_hex[((dataCount >> 0x00) & 0x0f)],
(byte)'\r',
(byte)'\n',
};
// Determine the most-significant non-zero nibble // Determine the most-significant non-zero nibble
int total, shift; int total, shift;
total = (dataCount > 0xffff) ? 0x10 : 0x00; var count = dataCount;
dataCount >>= total; total = (count > 0xffff) ? 0x10 : 0x00;
shift = (dataCount > 0x00ff) ? 0x08 : 0x00; count >>= total;
dataCount >>= shift; shift = (count > 0x00ff) ? 0x08 : 0x00;
count >>= shift;
total |= shift; total |= shift;
total |= (dataCount > 0x000f) ? 0x04 : 0x00; total |= (count > 0x000f) ? 0x04 : 0x00;
var offset = 7 - (total >> 2); count = (total >> 2) + 3;
return new ArraySegment<byte>(bytes, offset, 10 - offset);
var offset = 0;
ref var startHex = ref _hex[0];
for (shift = total; shift >= 0; shift -= 4)
{
// Using Unsafe.Add to elide the bounds check on _hex as the & 0x0f definately
// constrains it to the range 0x0 - 0xf, matching the bounds of the array
span[offset] = Unsafe.Add(ref startHex, ((dataCount >> shift) & 0x0f));
offset++;
}
span[count - 2] = (byte)'\r';
span[count - 1] = (byte)'\n';
return count;
} }
internal static int WriteBeginChunkBytes(ref BufferWriter<PipeWriter> start, int dataCount) internal static void WriteBeginChunkBytes(this ref BufferWriter<PipeWriter> start, int dataCount)
{ {
var chunkSegment = BeginChunkBytes(dataCount); // 10 bytes is max length + \r\n
start.Write(new ReadOnlySpan<byte>(chunkSegment.Array, chunkSegment.Offset, chunkSegment.Count)); start.Ensure(10);
return chunkSegment.Count;
var count = BeginChunkBytes(dataCount, start.Span);
start.Advance(count);
} }
internal static void WriteEndChunkBytes(ref BufferWriter<PipeWriter> start) internal static void WriteEndChunkBytes(this ref BufferWriter<PipeWriter> start)
{ {
start.Write(new ReadOnlySpan<byte>(_endChunkBytes.Array, _endChunkBytes.Offset, _endChunkBytes.Count)); start.Ensure(2);
var span = start.Span;
// CRLF done in reverse order so the 1st index will elide the bounds check for the 0th index
span[1] = (byte)'\n';
span[0] = (byte)'\r';
start.Advance(2);
} }
} }
} }

View File

@ -280,16 +280,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return new ForContentLength(keepAlive, contentLength, context); return new ForContentLength(keepAlive, contentLength, context);
} }
// Avoid slowing down most common case // If we got here, request contains no Content-Length or Transfer-Encoding header.
if (!object.ReferenceEquals(context.Method, HttpMethods.Get)) // Reject with 411 Length Required.
if (context.Method == HttpMethod.Post || context.Method == HttpMethod.Put)
{ {
// If we got here, request contains no Content-Length or Transfer-Encoding header. var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10;
// Reject with 411 Length Required. BadHttpRequestException.Throw(requestRejectionReason, context.Method);
if (context.Method == HttpMethod.Post || context.Method == HttpMethod.Put)
{
var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10;
BadHttpRequestException.Throw(requestRejectionReason, context.Method);
}
} }
return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose; return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose;

View File

@ -927,9 +927,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
var writer = new BufferWriter<PipeWriter>(writableBuffer); var writer = new BufferWriter<PipeWriter>(writableBuffer);
ChunkWriter.WriteBeginChunkBytes(ref writer, buffer.Length); writer.WriteBeginChunkBytes(buffer.Length);
writer.Write(buffer.Span); writer.Write(buffer.Span);
ChunkWriter.WriteEndChunkBytes(ref writer); writer.WriteEndChunkBytes();
writer.Commit(); writer.Commit();
bytesWritten = writer.BytesCommitted; bytesWritten = writer.BytesCommitted;
@ -938,12 +938,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return bytesWritten; return bytesWritten;
} }
private static ArraySegment<byte> CreateAsciiByteArraySegment(string text)
{
var bytes = Encoding.ASCII.GetBytes(text);
return new ArraySegment<byte>(bytes);
}
public void ProduceContinue() public void ProduceContinue()
{ {
if (HasResponseStarted) if (HasResponseStarted)

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq; using System;
using System.Text; using System.Text;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Xunit; using Xunit;
@ -11,28 +11,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
public class ChunkWriterTests public class ChunkWriterTests
{ {
[Theory] [Theory]
[InlineData(1, "1\r\n")] [InlineData(0x00, "0\r\n")]
[InlineData(10, "a\r\n")] [InlineData(0x01, "1\r\n")]
[InlineData(0x08, "8\r\n")] [InlineData(0x08, "8\r\n")]
[InlineData(0x10, "10\r\n")] [InlineData(0x0a, "a\r\n")]
[InlineData(0x0f, "f\r\n")]
[InlineData(0x010, "10\r\n")]
[InlineData(0x080, "80\r\n")] [InlineData(0x080, "80\r\n")]
[InlineData(0x100, "100\r\n")] [InlineData(0x0ff, "ff\r\n")]
[InlineData(0x0100, "100\r\n")]
[InlineData(0x0800, "800\r\n")] [InlineData(0x0800, "800\r\n")]
[InlineData(0x1000, "1000\r\n")] [InlineData(0x0fff, "fff\r\n")]
[InlineData(0x01000, "1000\r\n")]
[InlineData(0x08000, "8000\r\n")] [InlineData(0x08000, "8000\r\n")]
[InlineData(0x10000, "10000\r\n")] [InlineData(0x0ffff, "ffff\r\n")]
[InlineData(0x010000, "10000\r\n")]
[InlineData(0x080000, "80000\r\n")] [InlineData(0x080000, "80000\r\n")]
[InlineData(0x100000, "100000\r\n")] [InlineData(0x0fffff, "fffff\r\n")]
[InlineData(0x0100000, "100000\r\n")]
[InlineData(0x0800000, "800000\r\n")] [InlineData(0x0800000, "800000\r\n")]
[InlineData(0x1000000, "1000000\r\n")] [InlineData(0x0ffffff, "ffffff\r\n")]
[InlineData(0x01000000, "1000000\r\n")]
[InlineData(0x08000000, "8000000\r\n")] [InlineData(0x08000000, "8000000\r\n")]
[InlineData(0x10000000, "10000000\r\n")] [InlineData(0x0fffffff, "fffffff\r\n")]
[InlineData(0x010000000, "10000000\r\n")]
[InlineData(0x7fffffffL, "7fffffff\r\n")] [InlineData(0x7fffffffL, "7fffffff\r\n")]
public void ChunkedPrefixMustBeHexCrLfWithoutLeadingZeros(int dataCount, string expected) public void ChunkedPrefixMustBeHexCrLfWithoutLeadingZeros(int dataCount, string expected)
{ {
var beginChunkBytes = ChunkWriter.BeginChunkBytes(dataCount); Span<byte> span = new byte[10];
var count = ChunkWriter.BeginChunkBytes(dataCount, span);
Assert.Equal(Encoding.ASCII.GetBytes(expected), beginChunkBytes.ToArray()); Assert.Equal(Encoding.ASCII.GetBytes(expected), span.Slice(0, count).ToArray());
} }
} }
} }

View File

@ -8,10 +8,8 @@ using System.Threading;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
{ {
public class IOQueue : PipeScheduler public class IOQueue : PipeScheduler, IThreadPoolWorkItem
{ {
private static readonly WaitCallback _doWorkCallback = s => ((IOQueue)s).DoWork();
private readonly object _workSync = new object(); private readonly object _workSync = new object();
private readonly ConcurrentQueue<Work> _workItems = new ConcurrentQueue<Work>(); private readonly ConcurrentQueue<Work> _workItems = new ConcurrentQueue<Work>();
private bool _doingWork; private bool _doingWork;
@ -30,13 +28,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
{ {
if (!_doingWork) if (!_doingWork)
{ {
System.Threading.ThreadPool.UnsafeQueueUserWorkItem(_doWorkCallback, this); System.Threading.ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false);
_doingWork = true; _doingWork = true;
} }
} }
} }
private void DoWork() void IThreadPoolWorkItem.Execute()
{ {
while (true) while (true)
{ {

View File

@ -0,0 +1,63 @@
// 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 System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
public class ChunkWriterBenchmark
{
private const int InnerLoopCount = 1024;
private PipeReader _reader;
private PipeWriter _writer;
private MemoryPool<byte> _memoryPool;
[GlobalSetup]
public void Setup()
{
_memoryPool = KestrelMemoryPool.Create();
var pipe = new Pipe(new PipeOptions(_memoryPool));
_reader = pipe.Reader;
_writer = pipe.Writer;
}
[Params(0x0, 0x1, 0x10, 0x100, 0x1_000, 0x10_000, 0x100_000, 0x1_000_000)]
public int DataLength { get; set; }
[Benchmark(OperationsPerInvoke = InnerLoopCount)]
public async Task WriteBeginChunkBytes()
{
WriteBeginChunkBytes_Write();
var flushResult = _writer.FlushAsync();
var result = await _reader.ReadAsync();
_reader.AdvanceTo(result.Buffer.End, result.Buffer.End);
await flushResult;
}
private void WriteBeginChunkBytes_Write()
{
var writer = new BufferWriter<PipeWriter>(_writer);
var dataLength = DataLength;
for (int i = 0; i < InnerLoopCount; i++)
{
ChunkWriter.WriteBeginChunkBytes(ref writer, dataLength);
}
writer.Commit();
}
[GlobalCleanup]
public void Cleanup()
{
_memoryPool.Dispose();
}
}
}

View File

@ -6,6 +6,7 @@
<ServerGarbageCollection>true</ServerGarbageCollection> <ServerGarbageCollection>true</ServerGarbageCollection>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<TieredCompilation>false</TieredCompilation>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>