Merge branch 'master' of https://github.com/aspnet/aspnetcore
This commit is contained in:
commit
7269dbb73f
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue