From a31d1e024ca86a4a9053e0be105fde17c5ccae10 Mon Sep 17 00:00:00 2001 From: Cesar Blum Silveira Date: Thu, 14 Sep 2017 17:35:57 -0700 Subject: [PATCH] Merge code paths duplicated between HTTP/1.x and HTTP/2 implementations (#2017). - Most of the shared code is in the HttpProtocol class (former Frame) - Virtual calls handle protocol-specific things - Move the ProcessRequestsAsync loop to HttpProtocol - Implement HTTP/1.x request processing in Http1Connection and HTTP/2 in Http2Stream, with Http1Connection and Http2Stream subclassing those classes in order to handle the generic HttpContext parameter - Split MessageBody into Http1MessageBody and Http2MessageBody, with MessageBody containing shared member variables and methods --- ...ttp1ConnectionParsingOverheadBenchmark.cs} | 30 +- ...gBenchmark.cs => Http1WritingBenchmark.cs} | 36 +- ...serBenchmark.cs => HttpParserBenchmark.cs} | 6 +- ...on.cs => HttpProtocolFeatureCollection.cs} | 16 +- .../Kestrel.Performance.csproj | 2 +- .../Kestrel.Performance/Mocks/NullParser.cs | 2 +- .../RequestParsingBenchmark.cs | 20 +- .../ResponseHeaderCollectionBenchmark.cs | 14 +- .../ResponseHeadersWritingBenchmark.cs | 40 +- .../Internal/Http/Frame.Generated.cs | 362 ------ src/Kestrel.Core/Internal/Http/FrameOfT.cs | 252 ---- .../Http/Http1Connection.FeatureCollection.cs | 57 + .../Internal/Http/Http1Connection.cs | 491 ++++++++ ...meContext.cs => Http1ConnectionContext.cs} | 2 +- .../Internal/Http/Http1ConnectionOfT.cs | 27 + .../Internal/Http/Http1MessageBody.cs | 689 +++++++++++ ...tputProducer.cs => Http1OutputProducer.cs} | 74 +- ...FrameAdapter.cs => Http1ParsingHandler.cs} | 12 +- ....Generated.cs => HttpHeaders.Generated.cs} | 8 +- .../Http/{FrameHeaders.cs => HttpHeaders.cs} | 2 +- ...n.cs => HttpProtocol.FeatureCollection.cs} | 71 +- .../HttpProtocol.Generated.cs} | 40 +- .../Http/{Frame.cs => HttpProtocol.cs} | 747 +++++------- ...equestHeaders.cs => HttpRequestHeaders.cs} | 6 +- ...eRequestStream.cs => HttpRequestStream.cs} | 32 +- .../Internal/Http/HttpRequestTargetForm.cs | 15 + ...ponseHeaders.cs => HttpResponseHeaders.cs} | 6 +- ...esponseStream.cs => HttpResponseStream.cs} | 36 +- ...FrameStreamState.cs => HttpStreamState.cs} | 2 +- ...meDuplexStream.cs => HttpUpgradeStream.cs} | 4 +- src/Kestrel.Core/Internal/Http/HttpVersion.cs | 3 +- .../Internal/Http/IHttpOutputProducer.cs | 21 + .../Internal/Http/IHttpProtocolContext.cs | 19 + ...rameControl.cs => IHttpResponseControl.cs} | 2 +- .../Internal/Http/IMessageBody.cs | 17 - src/Kestrel.Core/Internal/Http/MessageBody.cs | 688 +---------- .../Internal/Http2/Http2Connection.cs | 10 +- .../Internal/Http2/Http2FrameWriter.cs | 25 +- .../Internal/Http2/Http2MessageBody.cs | 236 +--- .../Internal/Http2/Http2OutputProducer.cs | 58 + .../Http2/Http2Stream.FeatureCollection.cs | 281 +---- .../Internal/Http2/Http2Stream.cs | 1033 +---------------- .../Internal/Http2/Http2StreamContext.cs | 5 +- .../Internal/Http2/Http2StreamOfT.cs | 155 +-- .../Internal/Http2/Http2Streams.cs | 48 - .../Internal/Http2/IHttp2FrameWriter.cs | 4 +- .../{FrameConnection.cs => HttpConnection.cs} | 64 +- ...ionContext.cs => HttpConnectionContext.cs} | 4 +- .../Internal/HttpConnectionMiddleware.cs | 18 +- ...ionManager.cs => HttpConnectionManager.cs} | 14 +- ...ttpConnectionManagerShutdownExtensions.cs} | 6 +- ...eference.cs => HttpConnectionReference.cs} | 10 +- ...beatManager.cs => HttpHeartbeatManager.cs} | 10 +- .../Internal/Infrastructure/HttpUtilities.cs | 1 + .../Infrastructure/KestrelEventSource.cs | 12 +- .../Internal/Infrastructure/Streams.cs | 18 +- src/Kestrel.Core/Internal/ServiceContext.cs | 4 +- src/Kestrel.Core/KestrelServer.cs | 10 +- .../Internal/HttpsConnectionAdapter.cs | 6 +- ...{FrameTests.cs => Http1ConnectionTests.cs} | 336 +++--- .../Http2ConnectionTests.cs | 2 +- ...Tests.cs => HttpConnectionManagerTests.cs} | 22 +- ...nectionTests.cs => HttpConnectionTests.cs} | 286 ++--- ...ameHeadersTests.cs => HttpHeadersTests.cs} | 60 +- test/Kestrel.Core.Tests/HttpParserTests.cs | 2 +- ...ersTests.cs => HttpRequestHeadersTests.cs} | 30 +- ...reamTests.cs => HttpRequestStreamTests.cs} | 48 +- ...rsTests.cs => HttpResponseHeadersTests.cs} | 36 +- ...eamTests.cs => HttpResponseStreamTests.cs} | 32 +- test/Kestrel.Core.Tests/MessageBodyTests.cs | 144 +-- .../Kestrel.Core.Tests/OutputProducerTests.cs | 4 +- test/Kestrel.Core.Tests/PipeOptionsTests.cs | 4 +- test/Kestrel.Core.Tests/StreamsTests.cs | 8 +- ...eControl.cs => MockHttpResponseControl.cs} | 2 +- test/Kestrel.Core.Tests/TestInput.cs | 10 +- .../GeneratedCodeTests.cs | 32 +- ...Tests.cs => HttpConnectionManagerTests.cs} | 4 +- test/Kestrel.FunctionalTests/RequestTests.cs | 2 +- test/Kestrel.FunctionalTests/ResponseTests.cs | 12 +- test/Kestrel.FunctionalTests/UpgradeTests.cs | 2 +- .../LibuvOutputConsumerTests.cs | 64 +- .../{TestFrame.cs => TestHttp1Connection.cs} | 4 +- test/shared/TestServiceContext.cs | 4 +- tools/CodeGenerator/CodeGenerator.csproj | 2 +- ...on.cs => HttpProtocolFeatureCollection.cs} | 20 +- tools/CodeGenerator/KnownHeaders.cs | 17 +- tools/CodeGenerator/Program.cs | 24 +- 87 files changed, 2695 insertions(+), 4401 deletions(-) rename benchmarks/Kestrel.Performance/{FrameParsingOverheadBenchmark.cs => Http1ConnectionParsingOverheadBenchmark.cs} (68%) rename benchmarks/Kestrel.Performance/{FrameWritingBenchmark.cs => Http1WritingBenchmark.cs} (72%) rename benchmarks/Kestrel.Performance/{KestrelHttpParserBenchmark.cs => HttpParserBenchmark.cs} (93%) rename benchmarks/Kestrel.Performance/{FrameFeatureCollection.cs => HttpProtocolFeatureCollection.cs} (84%) delete mode 100644 src/Kestrel.Core/Internal/Http/Frame.Generated.cs delete mode 100644 src/Kestrel.Core/Internal/Http/FrameOfT.cs create mode 100644 src/Kestrel.Core/Internal/Http/Http1Connection.FeatureCollection.cs create mode 100644 src/Kestrel.Core/Internal/Http/Http1Connection.cs rename src/Kestrel.Core/Internal/Http/{FrameContext.cs => Http1ConnectionContext.cs} (93%) create mode 100644 src/Kestrel.Core/Internal/Http/Http1ConnectionOfT.cs create mode 100644 src/Kestrel.Core/Internal/Http/Http1MessageBody.cs rename src/Kestrel.Core/Internal/Http/{OutputProducer.cs => Http1OutputProducer.cs} (70%) rename src/Kestrel.Core/Internal/Http/{FrameAdapter.cs => Http1ParsingHandler.cs} (57%) rename src/Kestrel.Core/Internal/Http/{FrameHeaders.Generated.cs => HttpHeaders.Generated.cs} (99%) rename src/Kestrel.Core/Internal/Http/{FrameHeaders.cs => HttpHeaders.cs} (99%) rename src/Kestrel.Core/Internal/Http/{Frame.FeatureCollection.cs => HttpProtocol.FeatureCollection.cs} (76%) rename src/Kestrel.Core/Internal/{Http2/Http2Stream.Generated.cs => Http/HttpProtocol.Generated.cs} (98%) rename src/Kestrel.Core/Internal/Http/{Frame.cs => HttpProtocol.cs} (62%) rename src/Kestrel.Core/Internal/Http/{FrameRequestHeaders.cs => HttpRequestHeaders.cs} (95%) rename src/Kestrel.Core/Internal/Http/{FrameRequestStream.cs => HttpRequestStream.cs} (88%) create mode 100644 src/Kestrel.Core/Internal/Http/HttpRequestTargetForm.cs rename src/Kestrel.Core/Internal/Http/{FrameResponseHeaders.cs => HttpResponseHeaders.cs} (95%) rename src/Kestrel.Core/Internal/Http/{FrameResponseStream.cs => HttpResponseStream.cs} (81%) rename src/Kestrel.Core/Internal/Http/{FrameStreamState.cs => HttpStreamState.cs} (91%) rename src/Kestrel.Core/Internal/Http/{FrameDuplexStream.cs => HttpUpgradeStream.cs} (97%) create mode 100644 src/Kestrel.Core/Internal/Http/IHttpOutputProducer.cs create mode 100644 src/Kestrel.Core/Internal/Http/IHttpProtocolContext.cs rename src/Kestrel.Core/Internal/Http/{IFrameControl.cs => IHttpResponseControl.cs} (92%) delete mode 100644 src/Kestrel.Core/Internal/Http/IMessageBody.cs create mode 100644 src/Kestrel.Core/Internal/Http2/Http2OutputProducer.cs delete mode 100644 src/Kestrel.Core/Internal/Http2/Http2Streams.cs rename src/Kestrel.Core/Internal/{FrameConnection.cs => HttpConnection.cs} (86%) rename src/Kestrel.Core/Internal/{FrameConnectionContext.cs => HttpConnectionContext.cs} (91%) rename src/Kestrel.Core/Internal/Infrastructure/{FrameConnectionManager.cs => HttpConnectionManager.cs} (76%) rename src/Kestrel.Core/Internal/Infrastructure/{FrameConnectionManagerShutdownExtensions.cs => HttpConnectionManagerShutdownExtensions.cs} (91%) rename src/Kestrel.Core/Internal/Infrastructure/{FrameConnectionReference.cs => HttpConnectionReference.cs} (59%) rename src/Kestrel.Core/Internal/Infrastructure/{FrameHeartbeatManager.cs => HttpHeartbeatManager.cs} (65%) rename test/Kestrel.Core.Tests/{FrameTests.cs => Http1ConnectionTests.cs} (67%) rename test/Kestrel.Core.Tests/{FrameConnectionManagerTests.cs => HttpConnectionManagerTests.cs} (66%) rename test/Kestrel.Core.Tests/{FrameConnectionTests.cs => HttpConnectionTests.cs} (60%) rename test/Kestrel.Core.Tests/{FrameHeadersTests.cs => HttpHeadersTests.cs} (86%) rename test/Kestrel.Core.Tests/{FrameRequestHeadersTests.cs => HttpRequestHeadersTests.cs} (90%) rename test/Kestrel.Core.Tests/{FrameRequestStreamTests.cs => HttpRequestStreamTests.cs} (75%) rename test/Kestrel.Core.Tests/{FrameResponseHeadersTests.cs => HttpResponseHeadersTests.cs} (89%) rename test/Kestrel.Core.Tests/{FrameResponseStreamTests.cs => HttpResponseStreamTests.cs} (66%) rename test/Kestrel.Core.Tests/TestHelpers/{MockFrameControl.cs => MockHttpResponseControl.cs} (91%) rename test/Kestrel.FunctionalTests/{FrameConnectionManagerTests.cs => HttpConnectionManagerTests.cs} (96%) rename test/shared/{TestFrame.cs => TestHttp1Connection.cs} (82%) rename tools/CodeGenerator/{FrameFeatureCollection.cs => HttpProtocolFeatureCollection.cs} (87%) diff --git a/benchmarks/Kestrel.Performance/FrameParsingOverheadBenchmark.cs b/benchmarks/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs similarity index 68% rename from benchmarks/Kestrel.Performance/FrameParsingOverheadBenchmark.cs rename to benchmarks/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs index dd905485c4..b582379697 100644 --- a/benchmarks/Kestrel.Performance/FrameParsingOverheadBenchmark.cs +++ b/benchmarks/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs @@ -12,22 +12,22 @@ using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { [Config(typeof(CoreConfig))] - public class FrameParsingOverheadBenchmark + public class Http1ConnectionParsingOverheadBenchmark { private const int InnerLoopCount = 512; public ReadableBuffer _buffer; - public Frame _frame; + public Http1Connection _http1Connection; [IterationSetup] public void Setup() { var serviceContext = new ServiceContext { - HttpParserFactory = _ => NullParser.Instance, + HttpParserFactory = _ => NullParser.Instance, ServerOptions = new KestrelServerOptions() }; - var frameContext = new FrameContext + var http1ConnectionContext = new Http1ConnectionContext { ServiceContext = serviceContext, ConnectionFeatures = new FeatureCollection(), @@ -35,11 +35,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance TimeoutControl = new MockTimeoutControl() }; - _frame = new Frame(application: null, frameContext: frameContext); + _http1Connection = new Http1Connection(application: null, context: http1ConnectionContext); } [Benchmark(Baseline = true, OperationsPerInvoke = InnerLoopCount)] - public void FrameOverheadTotal() + public void Http1ConnectionOverheadTotal() { for (var i = 0; i < InnerLoopCount; i++) { @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance } [Benchmark(OperationsPerInvoke = InnerLoopCount)] - public void FrameOverheadRequestLine() + public void Http1ConnectionOverheadRequestLine() { for (var i = 0; i < InnerLoopCount; i++) { @@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance } [Benchmark(OperationsPerInvoke = InnerLoopCount)] - public void FrameOverheadRequestHeaders() + public void Http1ConnectionOverheadRequestHeaders() { for (var i = 0; i < InnerLoopCount; i++) { @@ -67,14 +67,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private void ParseRequest() { - _frame.Reset(); + _http1Connection.Reset(); - if (!_frame.TakeStartLine(_buffer, out var consumed, out var examined)) + if (!_http1Connection.TakeStartLine(_buffer, out var consumed, out var examined)) { ErrorUtilities.ThrowInvalidRequestLine(); } - if (!_frame.TakeMessageHeaders(_buffer, out consumed, out examined)) + if (!_http1Connection.TakeMessageHeaders(_buffer, out consumed, out examined)) { ErrorUtilities.ThrowInvalidRequestHeaders(); } @@ -82,9 +82,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private void ParseRequestLine() { - _frame.Reset(); + _http1Connection.Reset(); - if (!_frame.TakeStartLine(_buffer, out var consumed, out var examined)) + if (!_http1Connection.TakeStartLine(_buffer, out var consumed, out var examined)) { ErrorUtilities.ThrowInvalidRequestLine(); } @@ -92,9 +92,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private void ParseRequestHeaders() { - _frame.Reset(); + _http1Connection.Reset(); - if (!_frame.TakeMessageHeaders(_buffer, out var consumed, out var examined)) + if (!_http1Connection.TakeMessageHeaders(_buffer, out var consumed, out var examined)) { ErrorUtilities.ThrowInvalidRequestHeaders(); } diff --git a/benchmarks/Kestrel.Performance/FrameWritingBenchmark.cs b/benchmarks/Kestrel.Performance/Http1WritingBenchmark.cs similarity index 72% rename from benchmarks/Kestrel.Performance/FrameWritingBenchmark.cs rename to benchmarks/Kestrel.Performance/Http1WritingBenchmark.cs index bd280dec1d..a1a491a7dd 100644 --- a/benchmarks/Kestrel.Performance/FrameWritingBenchmark.cs +++ b/benchmarks/Kestrel.Performance/Http1WritingBenchmark.cs @@ -16,7 +16,7 @@ using Microsoft.AspNetCore.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { [Config(typeof(CoreConfig))] - public class FrameWritingBenchmark + public class Http1WritingBenchmark { // Standard completed task private static readonly Func _syncTaskFunc = (obj) => Task.CompletedTask; @@ -24,14 +24,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private static readonly Task _psuedoAsyncTask = Task.FromResult(27); private static readonly Func _psuedoAsyncTaskFunc = (obj) => _psuedoAsyncTask; - private readonly TestFrame _frame; + private readonly TestHttp1Connection _http1Connection; private (IPipeConnection Transport, IPipeConnection Application) _pair; private readonly byte[] _writeData; - public FrameWritingBenchmark() + public Http1WritingBenchmark() { - _frame = MakeFrame(); + _http1Connection = MakeHttp1Connection(); _writeData = Encoding.ASCII.GetBytes("Hello, World!"); } @@ -47,19 +47,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance [IterationSetup] public void Setup() { - _frame.Reset(); + _http1Connection.Reset(); if (Chunked) { - _frame.RequestHeaders.Add("Transfer-Encoding", "chunked"); + _http1Connection.RequestHeaders.Add("Transfer-Encoding", "chunked"); } else { - _frame.RequestHeaders.ContentLength = _writeData.Length; + _http1Connection.RequestHeaders.ContentLength = _writeData.Length; } if (!WithHeaders) { - _frame.FlushAsync().GetAwaiter().GetResult(); + _http1Connection.FlushAsync().GetAwaiter().GetResult(); } ResetState(); @@ -69,15 +69,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { if (WithHeaders) { - _frame.ResetState(); + _http1Connection.ResetState(); switch (OnStarting) { case Startup.Sync: - _frame.OnStarting(_syncTaskFunc, null); + _http1Connection.OnStarting(_syncTaskFunc, null); break; case Startup.Async: - _frame.OnStarting(_psuedoAsyncTaskFunc, null); + _http1Connection.OnStarting(_psuedoAsyncTaskFunc, null); break; } } @@ -88,10 +88,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { ResetState(); - return _frame.ResponseBody.WriteAsync(_writeData, 0, _writeData.Length, default(CancellationToken)); + return _http1Connection.ResponseBody.WriteAsync(_writeData, 0, _writeData.Length, default(CancellationToken)); } - private TestFrame MakeFrame() + private TestHttp1Connection MakeHttp1Connection() { var pipeFactory = new PipeFactory(); var pair = pipeFactory.CreateConnectionPair(); @@ -102,10 +102,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance DateHeaderValueManager = new DateHeaderValueManager(), ServerOptions = new KestrelServerOptions(), Log = new MockTrace(), - HttpParserFactory = f => new HttpParser() + HttpParserFactory = f => new HttpParser() }; - var frame = new TestFrame(application: null, context: new FrameContext + var http1Connection = new TestHttp1Connection(application: null, context: new Http1ConnectionContext { ServiceContext = serviceContext, ConnectionFeatures = new FeatureCollection(), @@ -114,10 +114,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance Transport = pair.Transport }); - frame.Reset(); - frame.InitializeStreams(MessageBody.ZeroContentLengthKeepAlive); + http1Connection.Reset(); + http1Connection.InitializeStreams(MessageBody.ZeroContentLengthKeepAlive); - return frame; + return http1Connection; } [IterationCleanup] diff --git a/benchmarks/Kestrel.Performance/KestrelHttpParserBenchmark.cs b/benchmarks/Kestrel.Performance/HttpParserBenchmark.cs similarity index 93% rename from benchmarks/Kestrel.Performance/KestrelHttpParserBenchmark.cs rename to benchmarks/Kestrel.Performance/HttpParserBenchmark.cs index d1e9d5a7aa..934b543594 100644 --- a/benchmarks/Kestrel.Performance/KestrelHttpParserBenchmark.cs +++ b/benchmarks/Kestrel.Performance/HttpParserBenchmark.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { [Config(typeof(CoreConfig))] - public class KestrelHttpParserBenchmark : IHttpRequestLineHandler, IHttpHeadersHandler + public class HttpParserBenchmark : IHttpRequestLineHandler, IHttpHeadersHandler { private readonly HttpParser _parser = new HttpParser(); @@ -76,9 +76,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private struct Adapter : IHttpRequestLineHandler, IHttpHeadersHandler { - public KestrelHttpParserBenchmark RequestHandler; + public HttpParserBenchmark RequestHandler; - public Adapter(KestrelHttpParserBenchmark requestHandler) + public Adapter(HttpParserBenchmark requestHandler) { RequestHandler = requestHandler; } diff --git a/benchmarks/Kestrel.Performance/FrameFeatureCollection.cs b/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs similarity index 84% rename from benchmarks/Kestrel.Performance/FrameFeatureCollection.cs rename to benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs index 5bb4f8603a..f637269c3c 100644 --- a/benchmarks/Kestrel.Performance/FrameFeatureCollection.cs +++ b/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs @@ -12,9 +12,9 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { [Config(typeof(CoreConfig))] - public class FrameFeatureCollection + public class HttpProtocolFeatureCollection { - private readonly Frame _frame; + private readonly Http1Connection _http1Connection; private IFeatureCollection _collection; [Benchmark(Baseline = true)] @@ -72,30 +72,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private object GetFastFeature(Type type) { - return _frame.FastFeatureGet(type); + return _http1Connection.FastFeatureGet(type); } - public FrameFeatureCollection() + public HttpProtocolFeatureCollection() { var serviceContext = new ServiceContext { - HttpParserFactory = _ => NullParser.Instance, + HttpParserFactory = _ => NullParser.Instance, ServerOptions = new KestrelServerOptions() }; - var frameContext = new FrameContext + var http1ConnectionContext = new Http1ConnectionContext { ServiceContext = serviceContext, ConnectionFeatures = new FeatureCollection(), PipeFactory = new PipeFactory() }; - _frame = new Frame(application: null, frameContext: frameContext); + _http1Connection = new Http1Connection(application: null, context: http1ConnectionContext); } [IterationSetup] public void Setup() { - _collection = _frame; + _collection = _http1Connection; } } diff --git a/benchmarks/Kestrel.Performance/Kestrel.Performance.csproj b/benchmarks/Kestrel.Performance/Kestrel.Performance.csproj index 4a6f234f0a..24548de865 100644 --- a/benchmarks/Kestrel.Performance/Kestrel.Performance.csproj +++ b/benchmarks/Kestrel.Performance/Kestrel.Performance.csproj @@ -14,7 +14,7 @@ - + diff --git a/benchmarks/Kestrel.Performance/Mocks/NullParser.cs b/benchmarks/Kestrel.Performance/Mocks/NullParser.cs index ac4e36508c..e9380cda77 100644 --- a/benchmarks/Kestrel.Performance/Mocks/NullParser.cs +++ b/benchmarks/Kestrel.Performance/Mocks/NullParser.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private readonly byte[] _connectionHeaderName = Encoding.ASCII.GetBytes("Connection"); private readonly byte[] _connectionHeaderValue = Encoding.ASCII.GetBytes("keep-alive"); - public static readonly NullParser Instance = new NullParser(); + public static readonly NullParser Instance = new NullParser(); public bool ParseHeaders(TRequestHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out int consumedBytes) { diff --git a/benchmarks/Kestrel.Performance/RequestParsingBenchmark.cs b/benchmarks/Kestrel.Performance/RequestParsingBenchmark.cs index 406c7a9f87..f7355007cc 100644 --- a/benchmarks/Kestrel.Performance/RequestParsingBenchmark.cs +++ b/benchmarks/Kestrel.Performance/RequestParsingBenchmark.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { public IPipe Pipe { get; set; } - public Frame Frame { get; set; } + public Http1Connection Http1Connection { get; set; } public PipeFactory PipeFactory { get; set; } @@ -28,10 +28,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance var serviceContext = new ServiceContext { - HttpParserFactory = f => new HttpParser(), + HttpParserFactory = f => new HttpParser(), ServerOptions = new KestrelServerOptions(), }; - var frameContext = new FrameContext + var http1ConnectionContext = new Http1ConnectionContext { ServiceContext = serviceContext, ConnectionFeatures = new FeatureCollection(), @@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance TimeoutControl = new MockTimeoutControl() }; - Frame = new Frame(application: null, frameContext: frameContext); + Http1Connection = new Http1Connection(application: null, context: http1ConnectionContext); } [Benchmark(Baseline = true, OperationsPerInvoke = RequestParsingData.InnerLoopCount)] @@ -142,16 +142,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance var readableBuffer = awaitable.GetResult().Buffer; do { - Frame.Reset(); + Http1Connection.Reset(); - if (!Frame.TakeStartLine(readableBuffer, out var consumed, out var examined)) + if (!Http1Connection.TakeStartLine(readableBuffer, out var consumed, out var examined)) { ErrorUtilities.ThrowInvalidRequestLine(); } readableBuffer = readableBuffer.Slice(consumed); - if (!Frame.TakeMessageHeaders(readableBuffer, out consumed, out examined)) + if (!Http1Connection.TakeMessageHeaders(readableBuffer, out consumed, out examined)) { ErrorUtilities.ThrowInvalidRequestHeaders(); } @@ -177,9 +177,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance var result = awaitable.GetAwaiter().GetResult(); var readableBuffer = result.Buffer; - Frame.Reset(); + Http1Connection.Reset(); - if (!Frame.TakeStartLine(readableBuffer, out var consumed, out var examined)) + if (!Http1Connection.TakeStartLine(readableBuffer, out var consumed, out var examined)) { ErrorUtilities.ThrowInvalidRequestLine(); } @@ -188,7 +188,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance result = Pipe.Reader.ReadAsync().GetAwaiter().GetResult(); readableBuffer = result.Buffer; - if (!Frame.TakeMessageHeaders(readableBuffer, out consumed, out examined)) + if (!Http1Connection.TakeMessageHeaders(readableBuffer, out consumed, out examined)) { ErrorUtilities.ThrowInvalidRequestHeaders(); } diff --git a/benchmarks/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs b/benchmarks/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs index c8d26ed2dc..b2ed0ae549 100644 --- a/benchmarks/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs +++ b/benchmarks/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel"); private static readonly DateHeaderValueManager _dateHeaderValueManager = new DateHeaderValueManager(); - private FrameResponseHeaders _responseHeadersDirect; + private HttpResponseHeaders _responseHeadersDirect; private HttpResponse _response; public enum BenchmarkTypes @@ -172,21 +172,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { var serviceContext = new ServiceContext { - HttpParserFactory = f => new HttpParser(), + HttpParserFactory = f => new HttpParser(), ServerOptions = new KestrelServerOptions() }; - var frameContext = new FrameContext + var http1ConnectionContext = new Http1ConnectionContext { ServiceContext = serviceContext, ConnectionFeatures = new FeatureCollection(), PipeFactory = new PipeFactory() }; - var frame = new Frame(application: null, frameContext: frameContext); + var http1Connection = new Http1Connection(application: null, context: http1ConnectionContext); - frame.Reset(); - _responseHeadersDirect = (FrameResponseHeaders)frame.ResponseHeaders; - var context = new DefaultHttpContext(frame); + http1Connection.Reset(); + _responseHeadersDirect = (HttpResponseHeaders)http1Connection.ResponseHeaders; + var context = new DefaultHttpContext(http1Connection); _response = new DefaultHttpResponse(context); switch (Type) diff --git a/benchmarks/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs b/benchmarks/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs index ab5c5dd260..6604d7afbb 100644 --- a/benchmarks/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs +++ b/benchmarks/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { private static readonly byte[] _helloWorldPayload = Encoding.ASCII.GetBytes("Hello, World!"); - private TestFrame _frame; + private TestHttp1Connection _http1Connection; [Params( BenchmarkTypes.TechEmpowerPlaintext, @@ -35,10 +35,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance [Benchmark] public async Task Output() { - _frame.Reset(); - _frame.StatusCode = 200; - _frame.HttpVersionEnum = HttpVersion.Http11; - _frame.KeepAlive = true; + _http1Connection.Reset(); + _http1Connection.StatusCode = 200; + _http1Connection.HttpVersionEnum = HttpVersion.Http11; + _http1Connection.KeepAlive = true; Task writeTask = Task.CompletedTask; switch (Type) @@ -61,50 +61,50 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance } await writeTask; - await _frame.ProduceEndAsync(); + await _http1Connection.ProduceEndAsync(); } private Task TechEmpowerPlaintext() { - var responseHeaders = _frame.ResponseHeaders; + var responseHeaders = _http1Connection.ResponseHeaders; responseHeaders["Content-Type"] = "text/plain"; responseHeaders.ContentLength = _helloWorldPayload.Length; - return _frame.WriteAsync(new ArraySegment(_helloWorldPayload), default(CancellationToken)); + return _http1Connection.WriteAsync(new ArraySegment(_helloWorldPayload), default(CancellationToken)); } private Task PlaintextChunked() { - var responseHeaders = _frame.ResponseHeaders; + var responseHeaders = _http1Connection.ResponseHeaders; responseHeaders["Content-Type"] = "text/plain"; - return _frame.WriteAsync(new ArraySegment(_helloWorldPayload), default(CancellationToken)); + return _http1Connection.WriteAsync(new ArraySegment(_helloWorldPayload), default(CancellationToken)); } private Task LiveAspNet() { - var responseHeaders = _frame.ResponseHeaders; + 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 _frame.WriteAsync(new ArraySegment(_helloWorldPayload), default(CancellationToken)); + return _http1Connection.WriteAsync(new ArraySegment(_helloWorldPayload), default(CancellationToken)); } private Task PlaintextWithCookie() { - var responseHeaders = _frame.ResponseHeaders; + 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"; responseHeaders.ContentLength = _helloWorldPayload.Length; - return _frame.WriteAsync(new ArraySegment(_helloWorldPayload), default(CancellationToken)); + return _http1Connection.WriteAsync(new ArraySegment(_helloWorldPayload), default(CancellationToken)); } private Task PlaintextChunkedWithCookie() { - var responseHeaders = _frame.ResponseHeaders; + 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 _frame.WriteAsync(new ArraySegment(_helloWorldPayload), default(CancellationToken)); + return _http1Connection.WriteAsync(new ArraySegment(_helloWorldPayload), default(CancellationToken)); } [IterationSetup] @@ -118,10 +118,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance DateHeaderValueManager = new DateHeaderValueManager(), ServerOptions = new KestrelServerOptions(), Log = new MockTrace(), - HttpParserFactory = f => new HttpParser() + HttpParserFactory = f => new HttpParser() }; - var frame = new TestFrame(application: null, context: new FrameContext + var http1Connection = new TestHttp1Connection(application: null, context: new Http1ConnectionContext { ServiceContext = serviceContext, ConnectionFeatures = new FeatureCollection(), @@ -131,9 +131,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance Transport = pair.Transport }); - frame.Reset(); + http1Connection.Reset(); - _frame = frame; + _http1Connection = http1Connection; } public enum BenchmarkTypes diff --git a/src/Kestrel.Core/Internal/Http/Frame.Generated.cs b/src/Kestrel.Core/Internal/Http/Frame.Generated.cs deleted file mode 100644 index c4403a60ce..0000000000 --- a/src/Kestrel.Core/Internal/Http/Frame.Generated.cs +++ /dev/null @@ -1,362 +0,0 @@ -// 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; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http -{ - public partial class Frame - { - private static readonly Type IHttpRequestFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestFeature); - private static readonly Type IHttpResponseFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpResponseFeature); - private static readonly Type IHttpRequestIdentifierFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature); - private static readonly Type IServiceProvidersFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature); - private static readonly Type IHttpRequestLifetimeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature); - private static readonly Type IHttpConnectionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature); - private static readonly Type IHttpAuthenticationFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature); - private static readonly Type IQueryFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IQueryFeature); - private static readonly Type IFormFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IFormFeature); - private static readonly Type IHttpUpgradeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature); - private static readonly Type IResponseCookiesFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature); - private static readonly Type IItemsFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IItemsFeature); - private static readonly Type ITlsConnectionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature); - private static readonly Type IHttpWebSocketFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature); - private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature); - private static readonly Type IHttpMaxRequestBodySizeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature); - private static readonly Type IHttpMinRequestBodyDataRateFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature); - private static readonly Type IHttpMinResponseDataRateFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinResponseDataRateFeature); - private static readonly Type IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature); - private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature); - - private object _currentIHttpRequestFeature; - private object _currentIHttpResponseFeature; - private object _currentIHttpRequestIdentifierFeature; - private object _currentIServiceProvidersFeature; - private object _currentIHttpRequestLifetimeFeature; - private object _currentIHttpConnectionFeature; - private object _currentIHttpAuthenticationFeature; - private object _currentIQueryFeature; - private object _currentIFormFeature; - private object _currentIHttpUpgradeFeature; - private object _currentIResponseCookiesFeature; - private object _currentIItemsFeature; - private object _currentITlsConnectionFeature; - private object _currentIHttpWebSocketFeature; - private object _currentISessionFeature; - private object _currentIHttpMaxRequestBodySizeFeature; - private object _currentIHttpMinRequestBodyDataRateFeature; - private object _currentIHttpMinResponseDataRateFeature; - private object _currentIHttpBodyControlFeature; - private object _currentIHttpSendFileFeature; - - private void FastReset() - { - _currentIHttpRequestFeature = this; - _currentIHttpResponseFeature = this; - _currentIHttpUpgradeFeature = this; - _currentIHttpRequestIdentifierFeature = this; - _currentIHttpRequestLifetimeFeature = this; - _currentIHttpConnectionFeature = this; - _currentIHttpMaxRequestBodySizeFeature = this; - _currentIHttpMinRequestBodyDataRateFeature = this; - _currentIHttpMinResponseDataRateFeature = this; - _currentIHttpBodyControlFeature = this; - - _currentIServiceProvidersFeature = null; - _currentIHttpAuthenticationFeature = null; - _currentIQueryFeature = null; - _currentIFormFeature = null; - _currentIResponseCookiesFeature = null; - _currentIItemsFeature = null; - _currentITlsConnectionFeature = null; - _currentIHttpWebSocketFeature = null; - _currentISessionFeature = null; - _currentIHttpSendFileFeature = null; - } - - internal object FastFeatureGet(Type key) - { - if (key == IHttpRequestFeatureType) - { - return _currentIHttpRequestFeature; - } - if (key == IHttpResponseFeatureType) - { - return _currentIHttpResponseFeature; - } - if (key == IHttpRequestIdentifierFeatureType) - { - return _currentIHttpRequestIdentifierFeature; - } - if (key == IServiceProvidersFeatureType) - { - return _currentIServiceProvidersFeature; - } - if (key == IHttpRequestLifetimeFeatureType) - { - return _currentIHttpRequestLifetimeFeature; - } - if (key == IHttpConnectionFeatureType) - { - return _currentIHttpConnectionFeature; - } - if (key == IHttpAuthenticationFeatureType) - { - return _currentIHttpAuthenticationFeature; - } - if (key == IQueryFeatureType) - { - return _currentIQueryFeature; - } - if (key == IFormFeatureType) - { - return _currentIFormFeature; - } - if (key == IHttpUpgradeFeatureType) - { - return _currentIHttpUpgradeFeature; - } - if (key == IResponseCookiesFeatureType) - { - return _currentIResponseCookiesFeature; - } - if (key == IItemsFeatureType) - { - return _currentIItemsFeature; - } - if (key == ITlsConnectionFeatureType) - { - return _currentITlsConnectionFeature; - } - if (key == IHttpWebSocketFeatureType) - { - return _currentIHttpWebSocketFeature; - } - if (key == ISessionFeatureType) - { - return _currentISessionFeature; - } - if (key == IHttpMaxRequestBodySizeFeatureType) - { - return _currentIHttpMaxRequestBodySizeFeature; - } - if (key == IHttpMinRequestBodyDataRateFeatureType) - { - return _currentIHttpMinRequestBodyDataRateFeature; - } - if (key == IHttpMinResponseDataRateFeatureType) - { - return _currentIHttpMinResponseDataRateFeature; - } - if (key == IHttpBodyControlFeatureType) - { - return _currentIHttpBodyControlFeature; - } - if (key == IHttpSendFileFeatureType) - { - return _currentIHttpSendFileFeature; - } - return ExtraFeatureGet(key); - } - - internal void FastFeatureSet(Type key, object feature) - { - _featureRevision++; - - if (key == IHttpRequestFeatureType) - { - _currentIHttpRequestFeature = feature; - return; - } - if (key == IHttpResponseFeatureType) - { - _currentIHttpResponseFeature = feature; - return; - } - if (key == IHttpRequestIdentifierFeatureType) - { - _currentIHttpRequestIdentifierFeature = feature; - return; - } - if (key == IServiceProvidersFeatureType) - { - _currentIServiceProvidersFeature = feature; - return; - } - if (key == IHttpRequestLifetimeFeatureType) - { - _currentIHttpRequestLifetimeFeature = feature; - return; - } - if (key == IHttpConnectionFeatureType) - { - _currentIHttpConnectionFeature = feature; - return; - } - if (key == IHttpAuthenticationFeatureType) - { - _currentIHttpAuthenticationFeature = feature; - return; - } - if (key == IQueryFeatureType) - { - _currentIQueryFeature = feature; - return; - } - if (key == IFormFeatureType) - { - _currentIFormFeature = feature; - return; - } - if (key == IHttpUpgradeFeatureType) - { - _currentIHttpUpgradeFeature = feature; - return; - } - if (key == IResponseCookiesFeatureType) - { - _currentIResponseCookiesFeature = feature; - return; - } - if (key == IItemsFeatureType) - { - _currentIItemsFeature = feature; - return; - } - if (key == ITlsConnectionFeatureType) - { - _currentITlsConnectionFeature = feature; - return; - } - if (key == IHttpWebSocketFeatureType) - { - _currentIHttpWebSocketFeature = feature; - return; - } - if (key == ISessionFeatureType) - { - _currentISessionFeature = feature; - return; - } - if (key == IHttpMaxRequestBodySizeFeatureType) - { - _currentIHttpMaxRequestBodySizeFeature = feature; - return; - } - if (key == IHttpMinRequestBodyDataRateFeatureType) - { - _currentIHttpMinRequestBodyDataRateFeature = feature; - return; - } - if (key == IHttpMinResponseDataRateFeatureType) - { - _currentIHttpMinResponseDataRateFeature = feature; - return; - } - if (key == IHttpBodyControlFeatureType) - { - _currentIHttpBodyControlFeature = feature; - return; - } - if (key == IHttpSendFileFeatureType) - { - _currentIHttpSendFileFeature = feature; - return; - }; - ExtraFeatureSet(key, feature); - } - - private IEnumerable> FastEnumerable() - { - if (_currentIHttpRequestFeature != null) - { - yield return new KeyValuePair(IHttpRequestFeatureType, _currentIHttpRequestFeature as global::Microsoft.AspNetCore.Http.Features.IHttpRequestFeature); - } - if (_currentIHttpResponseFeature != null) - { - yield return new KeyValuePair(IHttpResponseFeatureType, _currentIHttpResponseFeature as global::Microsoft.AspNetCore.Http.Features.IHttpResponseFeature); - } - if (_currentIHttpRequestIdentifierFeature != null) - { - yield return new KeyValuePair(IHttpRequestIdentifierFeatureType, _currentIHttpRequestIdentifierFeature as global::Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature); - } - if (_currentIServiceProvidersFeature != null) - { - yield return new KeyValuePair(IServiceProvidersFeatureType, _currentIServiceProvidersFeature as global::Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature); - } - if (_currentIHttpRequestLifetimeFeature != null) - { - yield return new KeyValuePair(IHttpRequestLifetimeFeatureType, _currentIHttpRequestLifetimeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature); - } - if (_currentIHttpConnectionFeature != null) - { - yield return new KeyValuePair(IHttpConnectionFeatureType, _currentIHttpConnectionFeature as global::Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature); - } - if (_currentIHttpAuthenticationFeature != null) - { - yield return new KeyValuePair(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature as global::Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature); - } - if (_currentIQueryFeature != null) - { - yield return new KeyValuePair(IQueryFeatureType, _currentIQueryFeature as global::Microsoft.AspNetCore.Http.Features.IQueryFeature); - } - if (_currentIFormFeature != null) - { - yield return new KeyValuePair(IFormFeatureType, _currentIFormFeature as global::Microsoft.AspNetCore.Http.Features.IFormFeature); - } - if (_currentIHttpUpgradeFeature != null) - { - yield return new KeyValuePair(IHttpUpgradeFeatureType, _currentIHttpUpgradeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature); - } - if (_currentIResponseCookiesFeature != null) - { - yield return new KeyValuePair(IResponseCookiesFeatureType, _currentIResponseCookiesFeature as global::Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature); - } - if (_currentIItemsFeature != null) - { - yield return new KeyValuePair(IItemsFeatureType, _currentIItemsFeature as global::Microsoft.AspNetCore.Http.Features.IItemsFeature); - } - if (_currentITlsConnectionFeature != null) - { - yield return new KeyValuePair(ITlsConnectionFeatureType, _currentITlsConnectionFeature as global::Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature); - } - if (_currentIHttpWebSocketFeature != null) - { - yield return new KeyValuePair(IHttpWebSocketFeatureType, _currentIHttpWebSocketFeature as global::Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature); - } - if (_currentISessionFeature != null) - { - yield return new KeyValuePair(ISessionFeatureType, _currentISessionFeature as global::Microsoft.AspNetCore.Http.Features.ISessionFeature); - } - if (_currentIHttpMaxRequestBodySizeFeature != null) - { - yield return new KeyValuePair(IHttpMaxRequestBodySizeFeatureType, _currentIHttpMaxRequestBodySizeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature); - } - if (_currentIHttpMinRequestBodyDataRateFeature != null) - { - yield return new KeyValuePair(IHttpMinRequestBodyDataRateFeatureType, _currentIHttpMinRequestBodyDataRateFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature); - } - if (_currentIHttpMinResponseDataRateFeature != null) - { - yield return new KeyValuePair(IHttpMinResponseDataRateFeatureType, _currentIHttpMinResponseDataRateFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinResponseDataRateFeature); - } - if (_currentIHttpBodyControlFeature != null) - { - yield return new KeyValuePair(IHttpBodyControlFeatureType, _currentIHttpBodyControlFeature as global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature); - } - if (_currentIHttpSendFileFeature != null) - { - yield return new KeyValuePair(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature); - } - - if (MaybeExtra != null) - { - foreach(var item in MaybeExtra) - { - yield return item; - } - } - } - } -} diff --git a/src/Kestrel.Core/Internal/Http/FrameOfT.cs b/src/Kestrel.Core/Internal/Http/FrameOfT.cs deleted file mode 100644 index b60b88049a..0000000000 --- a/src/Kestrel.Core/Internal/Http/FrameOfT.cs +++ /dev/null @@ -1,252 +0,0 @@ -// 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; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Protocols; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http -{ - public class Frame : Frame - { - private readonly IHttpApplication _application; - - public Frame(IHttpApplication application, FrameContext frameContext) - : base(frameContext) - { - _application = application; - } - - /// - /// Primary loop which consumes socket input, parses it for protocol framing, and invokes the - /// application delegate for as long as the socket is intended to remain open. - /// The resulting Task from this loop is preserved in a field which is used when the server needs - /// to drain and close all currently active connections. - /// - public override async Task ProcessRequestsAsync() - { - try - { - while (_keepAlive) - { - Reset(); - TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutAction.StopProcessingNextRequest); - - while (_requestProcessingStatus != RequestProcessingStatus.AppStarted) - { - var result = await Input.ReadAsync(); - - var examined = result.Buffer.End; - var consumed = result.Buffer.End; - - try - { - ParseRequest(result.Buffer, out consumed, out examined); - } - catch (InvalidOperationException) - { - if (_requestProcessingStatus == RequestProcessingStatus.ParsingHeaders) - { - throw BadHttpRequestException.GetException(RequestRejectionReason - .MalformedRequestInvalidHeaders); - } - throw; - } - finally - { - Input.Advance(consumed, examined); - } - - if (result.IsCompleted) - { - switch (_requestProcessingStatus) - { - case RequestProcessingStatus.RequestPending: - return; - case RequestProcessingStatus.ParsingRequestLine: - throw BadHttpRequestException.GetException( - RequestRejectionReason.InvalidRequestLine); - case RequestProcessingStatus.ParsingHeaders: - throw BadHttpRequestException.GetException( - RequestRejectionReason.MalformedRequestInvalidHeaders); - } - } - else if (!_keepAlive && _requestProcessingStatus == RequestProcessingStatus.RequestPending) - { - // Stop the request processing loop if the server is shutting down or there was a keep-alive timeout - // and there is no ongoing request. - return; - } - else if (RequestTimedOut) - { - // In this case, there is an ongoing request but the start line/header parsing has timed out, so send - // a 408 response. - throw BadHttpRequestException.GetException(RequestRejectionReason.RequestTimeout); - } - } - - EnsureHostHeaderExists(); - - var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this); - - if (!messageBody.RequestKeepAlive) - { - _keepAlive = false; - } - - _upgradeAvailable = messageBody.RequestUpgrade; - - InitializeStreams(messageBody); - - var context = _application.CreateContext(this); - try - { - try - { - KestrelEventSource.Log.RequestStart(this); - - await _application.ProcessRequestAsync(context); - - if (_requestAborted == 0) - { - VerifyResponseContentLength(); - } - } - catch (Exception ex) - { - ReportApplicationError(ex); - - if (ex is BadHttpRequestException) - { - throw; - } - } - finally - { - KestrelEventSource.Log.RequestStop(this); - - // Trigger OnStarting if it hasn't been called yet and the app hasn't - // already failed. If an OnStarting callback throws we can go through - // our normal error handling in ProduceEnd. - // https://github.com/aspnet/KestrelHttpServer/issues/43 - if (!HasResponseStarted && _applicationException == null && _onStarting != null) - { - await FireOnStarting(); - } - - PauseStreams(); - - if (_onCompleted != null) - { - await FireOnCompleted(); - } - } - - // If _requestAbort is set, the connection has already been closed. - if (_requestAborted == 0) - { - if (HasResponseStarted) - { - // If the response has already started, call ProduceEnd() before - // consuming the rest of the request body to prevent - // delaying clients waiting for the chunk terminator: - // - // https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663 - // - // ProduceEnd() must be called before _application.DisposeContext(), to ensure - // HttpContext.Response.StatusCode is correctly set when - // IHttpContextFactory.Dispose(HttpContext) is called. - await ProduceEnd(); - } - - // ForZeroContentLength does not complete the reader nor the writer - if (!messageBody.IsEmpty && _keepAlive) - { - // Finish reading the request body in case the app did not. - TimeoutControl.SetTimeout(Constants.RequestBodyDrainTimeout.Ticks, TimeoutAction.SendTimeoutResponse); - await messageBody.ConsumeAsync(); - TimeoutControl.CancelTimeout(); - } - - if (!HasResponseStarted) - { - await ProduceEnd(); - } - } - else if (!HasResponseStarted) - { - // If the request was aborted and no response was sent, there's no - // meaningful status code to log. - StatusCode = 0; - } - } - catch (BadHttpRequestException ex) - { - // Handle BadHttpRequestException thrown during app execution or remaining message body consumption. - // This has to be caught here so StatusCode is set properly before disposing the HttpContext - // (DisposeContext logs StatusCode). - SetBadRequestState(ex); - } - finally - { - _application.DisposeContext(context, _applicationException); - - // StopStreams should be called before the end of the "if (!_requestProcessingStopping)" block - // to ensure InitializeStreams has been called. - StopStreams(); - - if (HasStartedConsumingRequestBody) - { - RequestBodyPipe.Reader.Complete(); - - // Wait for MessageBody.PumpAsync() to call RequestBodyPipe.Writer.Complete(). - await messageBody.StopAsync(); - - // At this point both the request body pipe reader and writer should be completed. - RequestBodyPipe.Reset(); - } - } - } - } - catch (BadHttpRequestException ex) - { - // Handle BadHttpRequestException thrown during request line or header parsing. - // SetBadRequestState logs the error. - SetBadRequestState(ex); - } - catch (ConnectionResetException ex) - { - // Don't log ECONNRESET errors made between requests. Browsers like IE will reset connections regularly. - if (_requestProcessingStatus != RequestProcessingStatus.RequestPending) - { - Log.RequestProcessingError(ConnectionId, ex); - } - } - catch (IOException ex) - { - Log.RequestProcessingError(ConnectionId, ex); - } - catch (Exception ex) - { - Log.LogWarning(0, ex, CoreStrings.RequestProcessingEndError); - } - finally - { - try - { - Input.Complete(); - await TryProduceInvalidRequestResponse(); - Output.Dispose(); - } - catch (Exception ex) - { - Log.LogWarning(0, ex, CoreStrings.ConnectionShutdownError); - } - } - } - } -} diff --git a/src/Kestrel.Core/Internal/Http/Http1Connection.FeatureCollection.cs b/src/Kestrel.Core/Internal/Http/Http1Connection.FeatureCollection.cs new file mode 100644 index 0000000000..bcb7e60ebb --- /dev/null +++ b/src/Kestrel.Core/Internal/Http/Http1Connection.FeatureCollection.cs @@ -0,0 +1,57 @@ +// 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; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Features; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http +{ + public partial class Http1Connection : IHttpUpgradeFeature + { + bool IHttpUpgradeFeature.IsUpgradableRequest => IsUpgradableRequest; + + async Task IHttpUpgradeFeature.UpgradeAsync() + { + if (!((IHttpUpgradeFeature)this).IsUpgradableRequest) + { + throw new InvalidOperationException(CoreStrings.CannotUpgradeNonUpgradableRequest); + } + + if (IsUpgraded) + { + throw new InvalidOperationException(CoreStrings.UpgradeCannotBeCalledMultipleTimes); + } + + if (!ServiceContext.ConnectionManager.UpgradedConnectionCount.TryLockOne()) + { + throw new InvalidOperationException(CoreStrings.UpgradedConnectionLimitReached); + } + + IsUpgraded = true; + + ConnectionFeatures.Get()?.ReleaseConnection(); + + StatusCode = StatusCodes.Status101SwitchingProtocols; + ReasonPhrase = "Switching Protocols"; + ResponseHeaders["Connection"] = "Upgrade"; + if (!ResponseHeaders.ContainsKey("Upgrade")) + { + StringValues values; + if (RequestHeaders.TryGetValue("Upgrade", out values)) + { + ResponseHeaders["Upgrade"] = values; + } + } + + await FlushAsync(default(CancellationToken)); + + return _streams.Upgrade(); + } + } +} diff --git a/src/Kestrel.Core/Internal/Http/Http1Connection.cs b/src/Kestrel.Core/Internal/Http/Http1Connection.cs new file mode 100644 index 0000000000..9eb06465c1 --- /dev/null +++ b/src/Kestrel.Core/Internal/Http/Http1Connection.cs @@ -0,0 +1,491 @@ +// 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.Diagnostics; +using System.IO.Pipelines; +using System.Text; +using System.Text.Encodings.Web.Utf8; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http +{ + public abstract partial class Http1Connection : HttpProtocol + { + private const byte ByteAsterisk = (byte)'*'; + private const byte ByteForwardSlash = (byte)'/'; + private const string Asterisk = "*"; + + private readonly Http1ConnectionContext _context; + private readonly IHttpParser _parser; + protected readonly long _keepAliveTicks; + private readonly long _requestHeadersTimeoutTicks; + + private volatile bool _requestTimedOut; + private uint _requestCount; + + private HttpRequestTarget _requestTargetForm = HttpRequestTarget.Unknown; + private Uri _absoluteRequestTarget; + + private int _remainingRequestHeadersBytesAllowed; + + public Http1Connection(Http1ConnectionContext context) + : base(context) + { + _context = context; + _parser = ServiceContext.HttpParserFactory(new Http1ParsingHandler(this)); + _keepAliveTicks = ServerOptions.Limits.KeepAliveTimeout.Ticks; + _requestHeadersTimeoutTicks = ServerOptions.Limits.RequestHeadersTimeout.Ticks; + + Output = new Http1OutputProducer(_context.Application.Input, _context.Transport.Output, _context.ConnectionId, _context.ServiceContext.Log, _context.TimeoutControl); + } + + public IPipeReader Input => _context.Transport.Input; + + public ITimeoutControl TimeoutControl => _context.TimeoutControl; + public bool RequestTimedOut => _requestTimedOut; + + public override bool IsUpgradableRequest => _upgradeAvailable; + + /// + /// Stops the request processing loop between requests. + /// Called on all active connections when the server wants to initiate a shutdown + /// and after a keep-alive timeout. + /// + public void StopProcessingNextRequest() + { + _keepAlive = false; + Input.CancelPendingRead(); + } + + public void SendTimeoutResponse() + { + _requestTimedOut = true; + Input.CancelPendingRead(); + } + + public void ParseRequest(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) + { + consumed = buffer.Start; + examined = buffer.End; + + switch (_requestProcessingStatus) + { + case RequestProcessingStatus.RequestPending: + if (buffer.IsEmpty) + { + break; + } + + TimeoutControl.ResetTimeout(_requestHeadersTimeoutTicks, TimeoutAction.SendTimeoutResponse); + + _requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine; + goto case RequestProcessingStatus.ParsingRequestLine; + case RequestProcessingStatus.ParsingRequestLine: + if (TakeStartLine(buffer, out consumed, out examined)) + { + buffer = buffer.Slice(consumed, buffer.End); + + _requestProcessingStatus = RequestProcessingStatus.ParsingHeaders; + goto case RequestProcessingStatus.ParsingHeaders; + } + else + { + break; + } + case RequestProcessingStatus.ParsingHeaders: + if (TakeMessageHeaders(buffer, out consumed, out examined)) + { + _requestProcessingStatus = RequestProcessingStatus.AppStarted; + } + break; + } + } + + public bool TakeStartLine(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) + { + var overLength = false; + if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize) + { + buffer = buffer.Slice(buffer.Start, ServerOptions.Limits.MaxRequestLineSize); + overLength = true; + } + + var result = _parser.ParseRequestLine(new Http1ParsingHandler(this), buffer, out consumed, out examined); + if (!result && overLength) + { + ThrowRequestRejected(RequestRejectionReason.RequestLineTooLong); + } + + return result; + } + + public bool TakeMessageHeaders(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) + { + // Make sure the buffer is limited + bool overLength = false; + if (buffer.Length >= _remainingRequestHeadersBytesAllowed) + { + buffer = buffer.Slice(buffer.Start, _remainingRequestHeadersBytesAllowed); + + // If we sliced it means the current buffer bigger than what we're + // allowed to look at + overLength = true; + } + + var result = _parser.ParseHeaders(new Http1ParsingHandler(this), buffer, out consumed, out examined, out var consumedBytes); + _remainingRequestHeadersBytesAllowed -= consumedBytes; + + if (!result && overLength) + { + ThrowRequestRejected(RequestRejectionReason.HeadersExceedMaxTotalSize); + } + if (result) + { + TimeoutControl.CancelTimeout(); + } + return result; + } + + public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) + { + Debug.Assert(target.Length != 0, "Request target must be non-zero length"); + + var ch = target[0]; + if (ch == ByteForwardSlash) + { + // origin-form. + // The most common form of request-target. + // https://tools.ietf.org/html/rfc7230#section-5.3.1 + OnOriginFormTarget(method, version, target, path, query, customMethod, pathEncoded); + } + else if (ch == ByteAsterisk && target.Length == 1) + { + OnAsteriskFormTarget(method); + } + else if (target.GetKnownHttpScheme(out var scheme)) + { + OnAbsoluteFormTarget(target, query); + } + else + { + // Assume anything else is considered authority form. + // FYI: this should be an edge case. This should only happen when + // a client mistakenly thinks this server is a proxy server. + OnAuthorityFormTarget(method, target); + } + + Method = method != HttpMethod.Custom + ? HttpUtilities.MethodToString(method) ?? string.Empty + : customMethod.GetAsciiStringNonNullCharacters(); + _httpVersion = version; + + Debug.Assert(RawTarget != null, "RawTarget was not set"); + Debug.Assert(Method != null, "Method was not set"); + Debug.Assert(Path != null, "Path was not set"); + Debug.Assert(QueryString != null, "QueryString was not set"); + Debug.Assert(HttpVersion != null, "HttpVersion was not set"); + } + + private void OnOriginFormTarget(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) + { + Debug.Assert(target[0] == ByteForwardSlash, "Should only be called when path starts with /"); + + _requestTargetForm = HttpRequestTarget.OriginForm; + + // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11 + // Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8; + // then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs" + string requestUrlPath = null; + string rawTarget = null; + + try + { + // Read raw target before mutating memory. + rawTarget = target.GetAsciiStringNonNullCharacters(); + + if (pathEncoded) + { + // URI was encoded, unescape and then parse as UTF-8 + var pathLength = UrlEncoder.Decode(path, path); + + // Removing dot segments must be done after unescaping. From RFC 3986: + // + // URI producing applications should percent-encode data octets that + // correspond to characters in the reserved set unless these characters + // are specifically allowed by the URI scheme to represent data in that + // component. If a reserved character is found in a URI component and + // no delimiting role is known for that character, then it must be + // interpreted as representing the data octet corresponding to that + // character's encoding in US-ASCII. + // + // https://tools.ietf.org/html/rfc3986#section-2.2 + pathLength = PathNormalizer.RemoveDotSegments(path.Slice(0, pathLength)); + + requestUrlPath = GetUtf8String(path.Slice(0, pathLength)); + } + else + { + var pathLength = PathNormalizer.RemoveDotSegments(path); + + if (path.Length == pathLength && query.Length == 0) + { + // If no decoding was required, no dot segments were removed and + // there is no query, the request path is the same as the raw target + requestUrlPath = rawTarget; + } + else + { + requestUrlPath = path.Slice(0, pathLength).GetAsciiStringNonNullCharacters(); + } + } + } + catch (InvalidOperationException) + { + ThrowRequestTargetRejected(target); + } + + QueryString = query.GetAsciiStringNonNullCharacters(); + RawTarget = rawTarget; + Path = requestUrlPath; + } + + private void OnAuthorityFormTarget(HttpMethod method, Span target) + { + _requestTargetForm = HttpRequestTarget.AuthorityForm; + + // This is not complete validation. It is just a quick scan for invalid characters + // but doesn't check that the target fully matches the URI spec. + for (var i = 0; i < target.Length; i++) + { + var ch = target[i]; + if (!UriUtilities.IsValidAuthorityCharacter(ch)) + { + ThrowRequestTargetRejected(target); + } + } + + // The authority-form of request-target is only used for CONNECT + // requests (https://tools.ietf.org/html/rfc7231#section-4.3.6). + if (method != HttpMethod.Connect) + { + ThrowRequestRejected(RequestRejectionReason.ConnectMethodRequired); + } + + // When making a CONNECT request to establish a tunnel through one or + // more proxies, a client MUST send only the target URI's authority + // component (excluding any userinfo and its "@" delimiter) as the + // request-target.For example, + // + // CONNECT www.example.com:80 HTTP/1.1 + // + // Allowed characters in the 'host + port' section of authority. + // See https://tools.ietf.org/html/rfc3986#section-3.2 + RawTarget = target.GetAsciiStringNonNullCharacters(); + Path = string.Empty; + QueryString = string.Empty; + } + + private void OnAsteriskFormTarget(HttpMethod method) + { + _requestTargetForm = HttpRequestTarget.AsteriskForm; + + // The asterisk-form of request-target is only used for a server-wide + // OPTIONS request (https://tools.ietf.org/html/rfc7231#section-4.3.7). + if (method != HttpMethod.Options) + { + ThrowRequestRejected(RequestRejectionReason.OptionsMethodRequired); + } + + RawTarget = Asterisk; + Path = string.Empty; + QueryString = string.Empty; + } + + private void OnAbsoluteFormTarget(Span target, Span query) + { + _requestTargetForm = HttpRequestTarget.AbsoluteForm; + + // absolute-form + // https://tools.ietf.org/html/rfc7230#section-5.3.2 + + // This code should be the edge-case. + + // From the spec: + // a server MUST accept the absolute-form in requests, even though + // HTTP/1.1 clients will only send them in requests to proxies. + + RawTarget = target.GetAsciiStringNonNullCharacters(); + + // Validation of absolute URIs is slow, but clients + // should not be sending this form anyways, so perf optimization + // not high priority + + if (!Uri.TryCreate(RawTarget, UriKind.Absolute, out var uri)) + { + ThrowRequestTargetRejected(target); + } + + _absoluteRequestTarget = uri; + Path = uri.LocalPath; + // don't use uri.Query because we need the unescaped version + QueryString = query.GetAsciiStringNonNullCharacters(); + } + + private unsafe static string GetUtf8String(Span path) + { + // .NET 451 doesn't have pointer overloads for Encoding.GetString so we + // copy to an array + fixed (byte* pointer = &path.DangerousGetPinnableReference()) + { + return Encoding.UTF8.GetString(pointer, path.Length); + } + } + + public void OnHeader(Span name, Span value) + { + _requestHeadersParsed++; + if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount) + { + ThrowRequestRejected(RequestRejectionReason.TooManyHeaders); + } + var valueString = value.GetAsciiStringNonNullCharacters(); + + HttpRequestHeaders.Append(name, valueString); + } + + protected void EnsureHostHeaderExists() + { + if (_httpVersion == Http.HttpVersion.Http10) + { + return; + } + + // https://tools.ietf.org/html/rfc7230#section-5.4 + // A server MUST respond with a 400 (Bad Request) status code to any + // HTTP/1.1 request message that lacks a Host header field and to any + // request message that contains more than one Host header field or a + // Host header field with an invalid field-value. + + var host = HttpRequestHeaders.HeaderHost; + if (host.Count <= 0) + { + ThrowRequestRejected(RequestRejectionReason.MissingHostHeader); + } + else if (host.Count > 1) + { + ThrowRequestRejected(RequestRejectionReason.MultipleHostHeaders); + } + else if (_requestTargetForm == HttpRequestTarget.AuthorityForm) + { + if (!host.Equals(RawTarget)) + { + ThrowRequestRejected(RequestRejectionReason.InvalidHostHeader, host.ToString()); + } + } + else if (_requestTargetForm == HttpRequestTarget.AbsoluteForm) + { + // If the target URI includes an authority component, then a + // client MUST send a field - value for Host that is identical to that + // authority component, excluding any userinfo subcomponent and its "@" + // delimiter. + + // System.Uri doesn't not tell us if the port was in the original string or not. + // When IsDefaultPort = true, we will allow Host: with or without the default port + var authorityAndPort = _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port; + if ((host != _absoluteRequestTarget.Authority || !_absoluteRequestTarget.IsDefaultPort) + && host != authorityAndPort) + { + ThrowRequestRejected(RequestRejectionReason.InvalidHostHeader, host.ToString()); + } + } + } + + protected override void OnReset() + { + FastFeatureSet(typeof(IHttpUpgradeFeature), this); + + _requestTimedOut = false; + _requestTargetForm = HttpRequestTarget.Unknown; + _absoluteRequestTarget = null; + _remainingRequestHeadersBytesAllowed = ServerOptions.Limits.MaxRequestHeadersTotalSize + 2; + _requestCount++; + } + + protected override void OnRequestProcessingEnding() + { + Input.Complete(); + } + + protected override string CreateRequestId() + => StringUtilities.ConcatAsHexSuffix(ConnectionId, ':', _requestCount); + + protected override MessageBody CreateMessageBody() + => Http1MessageBody.For(_httpVersion, HttpRequestHeaders, this); + + protected override async Task ParseRequestAsync() + { + Reset(); + TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutAction.StopProcessingNextRequest); + + while (_requestProcessingStatus != RequestProcessingStatus.AppStarted) + { + var result = await Input.ReadAsync(); + + var examined = result.Buffer.End; + var consumed = result.Buffer.End; + + try + { + ParseRequest(result.Buffer, out consumed, out examined); + } + catch (InvalidOperationException) + { + if (_requestProcessingStatus == RequestProcessingStatus.ParsingHeaders) + { + throw BadHttpRequestException.GetException(RequestRejectionReason + .MalformedRequestInvalidHeaders); + } + throw; + } + finally + { + Input.Advance(consumed, examined); + } + + if (result.IsCompleted) + { + switch (_requestProcessingStatus) + { + case RequestProcessingStatus.RequestPending: + return false; + case RequestProcessingStatus.ParsingRequestLine: + throw BadHttpRequestException.GetException( + RequestRejectionReason.InvalidRequestLine); + case RequestProcessingStatus.ParsingHeaders: + throw BadHttpRequestException.GetException( + RequestRejectionReason.MalformedRequestInvalidHeaders); + } + } + else if (!_keepAlive && _requestProcessingStatus == RequestProcessingStatus.RequestPending) + { + // Stop the request processing loop if the server is shutting down or there was a keep-alive timeout + // and there is no ongoing request. + return false; + } + else if (RequestTimedOut) + { + // In this case, there is an ongoing request but the start line/header parsing has timed out, so send + // a 408 response. + throw BadHttpRequestException.GetException(RequestRejectionReason.RequestTimeout); + } + } + + EnsureHostHeaderExists(); + + return true; + } + } +} diff --git a/src/Kestrel.Core/Internal/Http/FrameContext.cs b/src/Kestrel.Core/Internal/Http/Http1ConnectionContext.cs similarity index 93% rename from src/Kestrel.Core/Internal/Http/FrameContext.cs rename to src/Kestrel.Core/Internal/Http/Http1ConnectionContext.cs index 399c9ac6fe..2b15279564 100644 --- a/src/Kestrel.Core/Internal/Http/FrameContext.cs +++ b/src/Kestrel.Core/Internal/Http/Http1ConnectionContext.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public class FrameContext + public class Http1ConnectionContext : IHttpProtocolContext { public string ConnectionId { get; set; } public ServiceContext ServiceContext { get; set; } diff --git a/src/Kestrel.Core/Internal/Http/Http1ConnectionOfT.cs b/src/Kestrel.Core/Internal/Http/Http1ConnectionOfT.cs new file mode 100644 index 0000000000..2e0cf25669 --- /dev/null +++ b/src/Kestrel.Core/Internal/Http/Http1ConnectionOfT.cs @@ -0,0 +1,27 @@ +// 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 Microsoft.AspNetCore.Hosting.Server; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http +{ + public class Http1Connection : Http1Connection + { + private readonly IHttpApplication _application; + + private TContext _httpContext; + + public Http1Connection(IHttpApplication application, Http1ConnectionContext context) + : base(context) + { + _application = application; + } + + protected override void CreateHttpContext() => _httpContext = _application.CreateContext(this); + + protected override void DisposeHttpContext() => _application.DisposeContext(_httpContext, _applicationException); + + protected override Task InvokeApplicationAsync() => _application.ProcessRequestAsync(_httpContext); + } +} diff --git a/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs b/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs new file mode 100644 index 0000000000..4f623108d8 --- /dev/null +++ b/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs @@ -0,0 +1,689 @@ +// 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; +using System.IO.Pipelines; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http +{ + public abstract class Http1MessageBody : MessageBody + { + private readonly Http1Connection _context; + + private bool _send100Continue = true; + private volatile bool _canceled; + private Task _pumpTask; + + protected Http1MessageBody(Http1Connection context) + : base(context) + { + _context = context; + } + + private async Task PumpAsync() + { + Exception error = null; + + try + { + var awaitable = _context.Input.ReadAsync(); + + if (!awaitable.IsCompleted) + { + TryProduceContinue(); + } + + TryStartTimingReads(); + + while (true) + { + var result = await awaitable; + + if (_context.RequestTimedOut) + { + _context.ThrowRequestRejected(RequestRejectionReason.RequestTimeout); + } + + var readableBuffer = result.Buffer; + var consumed = readableBuffer.Start; + var examined = readableBuffer.End; + + try + { + if (_canceled) + { + break; + } + + if (!readableBuffer.IsEmpty) + { + var writableBuffer = _context.RequestBodyPipe.Writer.Alloc(1); + bool done; + + try + { + done = Read(readableBuffer, writableBuffer, out consumed, out examined); + } + finally + { + writableBuffer.Commit(); + } + + var writeAwaitable = writableBuffer.FlushAsync(); + var backpressure = false; + + if (!writeAwaitable.IsCompleted) + { + // Backpressure, stop controlling incoming data rate until data is read. + backpressure = true; + TryPauseTimingReads(); + } + + await writeAwaitable; + + if (backpressure) + { + TryResumeTimingReads(); + } + + if (done) + { + break; + } + } + else if (result.IsCompleted) + { + _context.ThrowRequestRejected(RequestRejectionReason.UnexpectedEndOfRequestContent); + } + + awaitable = _context.Input.ReadAsync(); + } + finally + { + _context.Input.Advance(consumed, examined); + } + } + } + catch (Exception ex) + { + error = ex; + } + finally + { + _context.RequestBodyPipe.Writer.Complete(error); + TryStopTimingReads(); + } + } + + public override Task StopAsync() + { + if (!_context.HasStartedConsumingRequestBody) + { + return Task.CompletedTask; + } + + _canceled = true; + _context.Input.CancelPendingRead(); + return _pumpTask; + } + + protected override async Task OnConsumeAsync() + { + _context.TimeoutControl.SetTimeout(Constants.RequestBodyDrainTimeout.Ticks, TimeoutAction.SendTimeoutResponse); + + try + { + ReadResult result; + do + { + result = await _context.RequestBodyPipe.Reader.ReadAsync(); + _context.RequestBodyPipe.Reader.Advance(result.Buffer.End); + } while (!result.IsCompleted); + } + finally + { + _context.TimeoutControl.CancelTimeout(); + } + } + + private void TryProduceContinue() + { + if (_send100Continue) + { + _context.HttpResponseControl.ProduceContinue(); + _send100Continue = false; + } + } + + protected void Copy(ReadableBuffer readableBuffer, WritableBuffer writableBuffer) + { + _context.TimeoutControl.BytesRead(readableBuffer.Length); + + if (readableBuffer.IsSingleSpan) + { + writableBuffer.Write(readableBuffer.First.Span); + } + else + { + foreach (var memory in readableBuffer) + { + writableBuffer.Write(memory.Span); + } + } + } + + protected override void OnReadStarted() + { + _pumpTask = PumpAsync(); + } + + protected virtual bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined) + { + throw new NotImplementedException(); + } + + private void TryStartTimingReads() + { + if (!RequestUpgrade) + { + Log.RequestBodyStart(_context.ConnectionIdFeature, _context.TraceIdentifier); + _context.TimeoutControl.StartTimingReads(); + } + } + + private void TryPauseTimingReads() + { + if (!RequestUpgrade) + { + _context.TimeoutControl.PauseTimingReads(); + } + } + + private void TryResumeTimingReads() + { + if (!RequestUpgrade) + { + _context.TimeoutControl.ResumeTimingReads(); + } + } + + private void TryStopTimingReads() + { + if (!RequestUpgrade) + { + Log.RequestBodyDone(_context.ConnectionIdFeature, _context.TraceIdentifier); + _context.TimeoutControl.StopTimingReads(); + } + } + + public static MessageBody For( + HttpVersion httpVersion, + HttpRequestHeaders headers, + Http1Connection context) + { + // see also http://tools.ietf.org/html/rfc2616#section-4.4 + var keepAlive = httpVersion != HttpVersion.Http10; + + var connection = headers.HeaderConnection; + var upgrade = false; + if (connection.Count > 0) + { + var connectionOptions = HttpHeaders.ParseConnection(connection); + + upgrade = (connectionOptions & ConnectionOptions.Upgrade) == ConnectionOptions.Upgrade; + keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive; + } + + var transferEncoding = headers.HeaderTransferEncoding; + if (transferEncoding.Count > 0) + { + var transferCoding = HttpHeaders.GetFinalTransferCoding(headers.HeaderTransferEncoding); + + // https://tools.ietf.org/html/rfc7230#section-3.3.3 + // If a Transfer-Encoding header field + // is present in a request and the chunked transfer coding is not + // the final encoding, the message body length cannot be determined + // reliably; the server MUST respond with the 400 (Bad Request) + // status code and then close the connection. + if (transferCoding != TransferCoding.Chunked) + { + context.ThrowRequestRejected(RequestRejectionReason.FinalTransferCodingNotChunked, transferEncoding.ToString()); + } + + if (upgrade) + { + context.ThrowRequestRejected(RequestRejectionReason.UpgradeRequestCannotHavePayload); + } + + return new ForChunkedEncoding(keepAlive, context); + } + + if (headers.ContentLength.HasValue) + { + var contentLength = headers.ContentLength.Value; + + if (contentLength == 0) + { + return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose; + } + else if (upgrade) + { + context.ThrowRequestRejected(RequestRejectionReason.UpgradeRequestCannotHavePayload); + } + + return new ForContentLength(keepAlive, contentLength, context); + } + + // Avoid slowing down most common case + if (!object.ReferenceEquals(context.Method, HttpMethods.Get)) + { + // If we got here, request contains no Content-Length or Transfer-Encoding header. + // Reject with 411 Length Required. + if (HttpMethods.IsPost(context.Method) || HttpMethods.IsPut(context.Method)) + { + var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10; + context.ThrowRequestRejected(requestRejectionReason, context.Method); + } + } + + if (upgrade) + { + return new ForUpgrade(context); + } + + return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose; + } + + private class ForUpgrade : Http1MessageBody + { + public ForUpgrade(Http1Connection context) + : base(context) + { + RequestUpgrade = true; + } + + protected override bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined) + { + Copy(readableBuffer, writableBuffer); + consumed = readableBuffer.End; + examined = readableBuffer.End; + return false; + } + } + + private class ForContentLength : Http1MessageBody + { + private readonly long _contentLength; + private long _inputLength; + + public ForContentLength(bool keepAlive, long contentLength, Http1Connection context) + : base(context) + { + RequestKeepAlive = keepAlive; + _contentLength = contentLength; + _inputLength = _contentLength; + } + + protected override bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined) + { + if (_inputLength == 0) + { + throw new InvalidOperationException("Attempted to read from completed Content-Length request body."); + } + + var actual = (int)Math.Min(readableBuffer.Length, _inputLength); + _inputLength -= actual; + + consumed = readableBuffer.Move(readableBuffer.Start, actual); + examined = consumed; + + Copy(readableBuffer.Slice(0, actual), writableBuffer); + + return _inputLength == 0; + } + + protected override void OnReadStarting() + { + if (_contentLength > _context.MaxRequestBodySize) + { + _context.ThrowRequestRejected(RequestRejectionReason.RequestBodyTooLarge); + } + } + } + + /// + /// http://tools.ietf.org/html/rfc2616#section-3.6.1 + /// + private class ForChunkedEncoding : Http1MessageBody + { + // byte consts don't have a data type annotation so we pre-cast it + private const byte ByteCR = (byte)'\r'; + // "7FFFFFFF\r\n" is the largest chunk size that could be returned as an int. + private const int MaxChunkPrefixBytes = 10; + + private long _inputLength; + private long _consumedBytes; + + private Mode _mode = Mode.Prefix; + + public ForChunkedEncoding(bool keepAlive, Http1Connection context) + : base(context) + { + RequestKeepAlive = keepAlive; + } + + protected override bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined) + { + consumed = default(ReadCursor); + examined = default(ReadCursor); + + while (_mode < Mode.Trailer) + { + if (_mode == Mode.Prefix) + { + ParseChunkedPrefix(readableBuffer, out consumed, out examined); + + if (_mode == Mode.Prefix) + { + return false; + } + + readableBuffer = readableBuffer.Slice(consumed); + } + + if (_mode == Mode.Extension) + { + ParseExtension(readableBuffer, out consumed, out examined); + + if (_mode == Mode.Extension) + { + return false; + } + + readableBuffer = readableBuffer.Slice(consumed); + } + + if (_mode == Mode.Data) + { + ReadChunkedData(readableBuffer, writableBuffer, out consumed, out examined); + + if (_mode == Mode.Data) + { + return false; + } + + readableBuffer = readableBuffer.Slice(consumed); + } + + if (_mode == Mode.Suffix) + { + ParseChunkedSuffix(readableBuffer, out consumed, out examined); + + if (_mode == Mode.Suffix) + { + return false; + } + + readableBuffer = readableBuffer.Slice(consumed); + } + } + + // Chunks finished, parse trailers + if (_mode == Mode.Trailer) + { + ParseChunkedTrailer(readableBuffer, out consumed, out examined); + + if (_mode == Mode.Trailer) + { + return false; + } + + readableBuffer = readableBuffer.Slice(consumed); + } + + // _consumedBytes aren't tracked for trailer headers, since headers have seperate limits. + if (_mode == Mode.TrailerHeaders) + { + if (_context.TakeMessageHeaders(readableBuffer, out consumed, out examined)) + { + _mode = Mode.Complete; + } + } + + return _mode == Mode.Complete; + } + + private void AddAndCheckConsumedBytes(long consumedBytes) + { + _consumedBytes += consumedBytes; + + if (_consumedBytes > _context.MaxRequestBodySize) + { + _context.ThrowRequestRejected(RequestRejectionReason.RequestBodyTooLarge); + } + } + + private void ParseChunkedPrefix(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) + { + consumed = buffer.Start; + examined = buffer.Start; + var reader = new ReadableBufferReader(buffer); + var ch1 = reader.Take(); + var ch2 = reader.Take(); + + if (ch1 == -1 || ch2 == -1) + { + examined = reader.Cursor; + return; + } + + var chunkSize = CalculateChunkSize(ch1, 0); + ch1 = ch2; + + while (reader.ConsumedBytes < MaxChunkPrefixBytes) + { + if (ch1 == ';') + { + consumed = reader.Cursor; + examined = reader.Cursor; + + AddAndCheckConsumedBytes(reader.ConsumedBytes); + _inputLength = chunkSize; + _mode = Mode.Extension; + return; + } + + ch2 = reader.Take(); + if (ch2 == -1) + { + examined = reader.Cursor; + return; + } + + if (ch1 == '\r' && ch2 == '\n') + { + consumed = reader.Cursor; + examined = reader.Cursor; + + AddAndCheckConsumedBytes(reader.ConsumedBytes); + _inputLength = chunkSize; + _mode = chunkSize > 0 ? Mode.Data : Mode.Trailer; + return; + } + + chunkSize = CalculateChunkSize(ch1, chunkSize); + ch1 = ch2; + } + + // At this point, 10 bytes have been consumed which is enough to parse the max value "7FFFFFFF\r\n". + _context.ThrowRequestRejected(RequestRejectionReason.BadChunkSizeData); + } + + private void ParseExtension(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) + { + // Chunk-extensions not currently parsed + // Just drain the data + consumed = buffer.Start; + examined = buffer.Start; + + do + { + ReadCursor extensionCursor; + if (ReadCursorOperations.Seek(buffer.Start, buffer.End, out extensionCursor, ByteCR) == -1) + { + // End marker not found yet + consumed = buffer.End; + examined = buffer.End; + AddAndCheckConsumedBytes(buffer.Length); + return; + }; + + var charsToByteCRExclusive = buffer.Slice(0, extensionCursor).Length; + + var sufixBuffer = buffer.Slice(extensionCursor); + if (sufixBuffer.Length < 2) + { + consumed = extensionCursor; + examined = buffer.End; + AddAndCheckConsumedBytes(charsToByteCRExclusive); + return; + } + + sufixBuffer = sufixBuffer.Slice(0, 2); + var sufixSpan = sufixBuffer.ToSpan(); + + if (sufixSpan[1] == '\n') + { + // We consumed the \r\n at the end of the extension, so switch modes. + _mode = _inputLength > 0 ? Mode.Data : Mode.Trailer; + + consumed = sufixBuffer.End; + examined = sufixBuffer.End; + AddAndCheckConsumedBytes(charsToByteCRExclusive + 2); + } + else + { + // Don't consume suffixSpan[1] in case it is also a \r. + buffer = buffer.Slice(charsToByteCRExclusive + 1); + consumed = extensionCursor; + AddAndCheckConsumedBytes(charsToByteCRExclusive + 1); + } + } while (_mode == Mode.Extension); + } + + private void ReadChunkedData(ReadableBuffer buffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined) + { + var actual = Math.Min(buffer.Length, _inputLength); + consumed = buffer.Move(buffer.Start, actual); + examined = consumed; + + Copy(buffer.Slice(0, actual), writableBuffer); + + _inputLength -= actual; + AddAndCheckConsumedBytes(actual); + + if (_inputLength == 0) + { + _mode = Mode.Suffix; + } + } + + private void ParseChunkedSuffix(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) + { + consumed = buffer.Start; + examined = buffer.Start; + + if (buffer.Length < 2) + { + examined = buffer.End; + return; + } + + var suffixBuffer = buffer.Slice(0, 2); + var suffixSpan = suffixBuffer.ToSpan(); + if (suffixSpan[0] == '\r' && suffixSpan[1] == '\n') + { + consumed = suffixBuffer.End; + examined = suffixBuffer.End; + AddAndCheckConsumedBytes(2); + _mode = Mode.Prefix; + } + else + { + _context.ThrowRequestRejected(RequestRejectionReason.BadChunkSuffix); + } + } + + private void ParseChunkedTrailer(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) + { + consumed = buffer.Start; + examined = buffer.Start; + + if (buffer.Length < 2) + { + examined = buffer.End; + return; + } + + var trailerBuffer = buffer.Slice(0, 2); + var trailerSpan = trailerBuffer.ToSpan(); + + if (trailerSpan[0] == '\r' && trailerSpan[1] == '\n') + { + consumed = trailerBuffer.End; + examined = trailerBuffer.End; + AddAndCheckConsumedBytes(2); + _mode = Mode.Complete; + } + else + { + _mode = Mode.TrailerHeaders; + } + } + + private int CalculateChunkSize(int extraHexDigit, int currentParsedSize) + { + try + { + checked + { + if (extraHexDigit >= '0' && extraHexDigit <= '9') + { + return currentParsedSize * 0x10 + (extraHexDigit - '0'); + } + else if (extraHexDigit >= 'A' && extraHexDigit <= 'F') + { + return currentParsedSize * 0x10 + (extraHexDigit - ('A' - 10)); + } + else if (extraHexDigit >= 'a' && extraHexDigit <= 'f') + { + return currentParsedSize * 0x10 + (extraHexDigit - ('a' - 10)); + } + } + } + catch (OverflowException ex) + { + throw new IOException(CoreStrings.BadRequest_BadChunkSizeData, ex); + } + + _context.ThrowRequestRejected(RequestRejectionReason.BadChunkSizeData); + return -1; // can't happen, but compiler complains + } + + private enum Mode + { + Prefix, + Extension, + Data, + Suffix, + Trailer, + TrailerHeaders, + Complete + }; + } + } +} diff --git a/src/Kestrel.Core/Internal/Http/OutputProducer.cs b/src/Kestrel.Core/Internal/Http/Http1OutputProducer.cs similarity index 70% rename from src/Kestrel.Core/Internal/Http/OutputProducer.cs rename to src/Kestrel.Core/Internal/Http/Http1OutputProducer.cs index 2bd85c9047..61ec44467d 100644 --- a/src/Kestrel.Core/Internal/Http/OutputProducer.cs +++ b/src/Kestrel.Core/Internal/Http/Http1OutputProducer.cs @@ -5,15 +5,20 @@ using System; using System.Diagnostics; using System.IO.Pipelines; using System.Runtime.CompilerServices; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public class OutputProducer : IDisposable + public class Http1OutputProducer : IHttpOutputProducer { private static readonly ArraySegment _emptyData = new ArraySegment(new byte[0]); + private static readonly ArraySegment _continueBytes = new ArraySegment(Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n")); + private static readonly byte[] _bytesHttpVersion11 = Encoding.ASCII.GetBytes("HTTP/1.1 "); + private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n"); + private static readonly ArraySegment _endChunkedResponseBytes = new ArraySegment(Encoding.ASCII.GetBytes("0\r\n\r\n")); private readonly string _connectionId; private readonly ITimeoutControl _timeoutControl; @@ -34,7 +39,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private readonly object _flushLock = new object(); private Action _flushCompleted; - public OutputProducer( + public Http1OutputProducer( IPipeReader outputPipeReader, IPipeWriter pipeWriter, string connectionId, @@ -49,14 +54,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _flushCompleted = OnFlushCompleted; } - public Task WriteAsync(ArraySegment buffer, bool chunk = false, CancellationToken cancellationToken = default(CancellationToken)) + public Task WriteDataAsync(ArraySegment buffer, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } - return WriteAsync(buffer, cancellationToken, chunk); + return WriteAsync(buffer, cancellationToken); + } + + public Task WriteStreamSuffixAsync(CancellationToken cancellationToken) + { + return WriteAsync(_endChunkedResponseBytes, cancellationToken); } public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken)) @@ -79,6 +89,44 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } + public Task WriteAsync(Action callback, T state) + { + lock (_contextLock) + { + if (_completed) + { + return Task.CompletedTask; + } + + var buffer = _pipeWriter.Alloc(1); + callback(buffer, state); + buffer.Commit(); + } + + return FlushAsync(); + } + + public void WriteResponseHeaders(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders) + { + lock (_contextLock) + { + if (_completed) + { + return; + } + + var buffer = _pipeWriter.Alloc(1); + var writer = new WritableBufferWriter(buffer); + + writer.Write(_bytesHttpVersion11); + var statusBytes = ReasonPhrases.ToStatusBytes(statusCode, reasonPhrase); + writer.Write(statusBytes); + responseHeaders.CopyTo(ref writer); + writer.Write(_bytesEndHeaders); + buffer.Commit(); + } + } + public void Dispose() { lock (_contextLock) @@ -111,10 +159,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } + public Task Write100ContinueAsync(CancellationToken cancellationToken) + { + return WriteAsync(_continueBytes, default(CancellationToken)); + } + private Task WriteAsync( ArraySegment buffer, - CancellationToken cancellationToken, - bool chunk = false) + CancellationToken cancellationToken) { var writableBuffer = default(WritableBuffer); @@ -129,17 +181,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var writer = new WritableBufferWriter(writableBuffer); if (buffer.Count > 0) { - if (chunk) - { - ChunkWriter.WriteBeginChunkBytes(ref writer, buffer.Count); - } - writer.Write(buffer.Array, buffer.Offset, buffer.Count); - - if (chunk) - { - ChunkWriter.WriteEndChunkBytes(ref writer); - } } writableBuffer.Commit(); diff --git a/src/Kestrel.Core/Internal/Http/FrameAdapter.cs b/src/Kestrel.Core/Internal/Http/Http1ParsingHandler.cs similarity index 57% rename from src/Kestrel.Core/Internal/Http/FrameAdapter.cs rename to src/Kestrel.Core/Internal/Http/Http1ParsingHandler.cs index d0a30c0640..e4385351db 100644 --- a/src/Kestrel.Core/Internal/Http/FrameAdapter.cs +++ b/src/Kestrel.Core/Internal/Http/Http1ParsingHandler.cs @@ -5,19 +5,19 @@ using System; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public struct FrameAdapter : IHttpRequestLineHandler, IHttpHeadersHandler + public struct Http1ParsingHandler : IHttpRequestLineHandler, IHttpHeadersHandler { - public Frame Frame; + public Http1Connection Connection; - public FrameAdapter(Frame frame) + public Http1ParsingHandler(Http1Connection connection) { - Frame = frame; + Connection = connection; } public void OnHeader(Span name, Span value) - => Frame.OnHeader(name, value); + => Connection.OnHeader(name, value); public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) - => Frame.OnStartLine(method, version, target, path, query, customMethod, pathEncoded); + => Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded); } } diff --git a/src/Kestrel.Core/Internal/Http/FrameHeaders.Generated.cs b/src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs similarity index 99% rename from src/Kestrel.Core/Internal/Http/FrameHeaders.Generated.cs rename to src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs index e4a0ea6d38..0da75ae65f 100644 --- a/src/Kestrel.Core/Internal/Http/FrameHeaders.Generated.cs +++ b/src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs @@ -11,7 +11,7 @@ using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public partial class FrameRequestHeaders + public partial class HttpRequestHeaders { private long _bits = 0; @@ -2655,7 +2655,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _contentLength = null; var tempBits = _bits; _bits = 0; - if(FrameHeaders.BitCount(tempBits) > 12) + if(HttpHeaders.BitCount(tempBits) > 12) { _headers = default(HeaderReferences); return; @@ -4784,7 +4784,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - public partial class FrameResponseHeaders + public partial class HttpResponseHeaders { private static byte[] _headerBytes = new byte[] { @@ -7059,7 +7059,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _contentLength = null; var tempBits = _bits; _bits = 0; - if(FrameHeaders.BitCount(tempBits) > 12) + if(HttpHeaders.BitCount(tempBits) > 12) { _headers = default(HeaderReferences); return; diff --git a/src/Kestrel.Core/Internal/Http/FrameHeaders.cs b/src/Kestrel.Core/Internal/Http/HttpHeaders.cs similarity index 99% rename from src/Kestrel.Core/Internal/Http/FrameHeaders.cs rename to src/Kestrel.Core/Internal/Http/HttpHeaders.cs index 7331ceeadf..f0aa6218df 100644 --- a/src/Kestrel.Core/Internal/Http/FrameHeaders.cs +++ b/src/Kestrel.Core/Internal/Http/HttpHeaders.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public abstract class FrameHeaders : IHeaderDictionary + public abstract class HttpHeaders : IHeaderDictionary { protected long? _contentLength; protected bool _isReadOnly; diff --git a/src/Kestrel.Core/Internal/Http/Frame.FeatureCollection.cs b/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs similarity index 76% rename from src/Kestrel.Core/Internal/Http/Frame.FeatureCollection.cs rename to src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs index c922e2d1cf..230ba868ac 100644 --- a/src/Kestrel.Core/Internal/Http/Frame.FeatureCollection.cs +++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs @@ -15,21 +15,20 @@ using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public partial class Frame : IFeatureCollection, - IHttpRequestFeature, - IHttpResponseFeature, - IHttpUpgradeFeature, - IHttpConnectionFeature, - IHttpRequestLifetimeFeature, - IHttpRequestIdentifierFeature, - IHttpBodyControlFeature, - IHttpMaxRequestBodySizeFeature, - IHttpMinRequestBodyDataRateFeature, - IHttpMinResponseDataRateFeature + public partial class HttpProtocol : IFeatureCollection, + IHttpRequestFeature, + IHttpResponseFeature, + IHttpConnectionFeature, + IHttpRequestLifetimeFeature, + IHttpRequestIdentifierFeature, + IHttpBodyControlFeature, + IHttpMaxRequestBodySizeFeature, + IHttpMinRequestBodyDataRateFeature, + IHttpMinResponseDataRateFeature { - // NOTE: When feature interfaces are added to or removed from this Frame class implementation, + // NOTE: When feature interfaces are added to or removed from this HttpProtocol class implementation, // then the list of `implementedFeatures` in the generated code project MUST also be updated. - // See also: tools/Microsoft.AspNetCore.Server.Kestrel.GeneratedCode/FrameFeatureCollection.cs + // See also: tools/Microsoft.AspNetCore.Server.Kestrel.GeneratedCode/HttpProtocolFeatureCollection.cs private int _featureRevision; @@ -77,8 +76,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http MaybeExtra.Add(new KeyValuePair(key, value)); } - public IFrameControl FrameControl { get; set; } - string IHttpRequestFeature.Protocol { get => HttpVersion; @@ -165,8 +162,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http bool IHttpResponseFeature.HasStarted => HasResponseStarted; - bool IHttpUpgradeFeature.IsUpgradableRequest => _upgradeAvailable; - bool IFeatureCollection.IsReadOnly => false; int IFeatureCollection.Revision => _featureRevision; @@ -213,7 +208,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http set => AllowSynchronousIO = value; } - bool IHttpMaxRequestBodySizeFeature.IsReadOnly => HasStartedConsumingRequestBody || _wasUpgraded; + bool IHttpMaxRequestBodySizeFeature.IsReadOnly => HasStartedConsumingRequestBody || IsUpgraded; long? IHttpMaxRequestBodySizeFeature.MaxRequestBodySize { @@ -224,7 +219,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { throw new InvalidOperationException(CoreStrings.MaxRequestBodySizeCannotBeModifiedAfterRead); } - if (_wasUpgraded) + if (IsUpgraded) { throw new InvalidOperationException(CoreStrings.MaxRequestBodySizeCannotBeModifiedForUpgradedRequests); } @@ -275,44 +270,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http OnCompleted(callback, state); } - async Task IHttpUpgradeFeature.UpgradeAsync() - { - if (!((IHttpUpgradeFeature)this).IsUpgradableRequest) - { - throw new InvalidOperationException(CoreStrings.CannotUpgradeNonUpgradableRequest); - } - - if (_wasUpgraded) - { - throw new InvalidOperationException(CoreStrings.UpgradeCannotBeCalledMultipleTimes); - } - - if (!ServiceContext.ConnectionManager.UpgradedConnectionCount.TryLockOne()) - { - throw new InvalidOperationException(CoreStrings.UpgradedConnectionLimitReached); - } - - _wasUpgraded = true; - - ConnectionFeatures.Get()?.ReleaseConnection(); - - StatusCode = StatusCodes.Status101SwitchingProtocols; - ReasonPhrase = "Switching Protocols"; - ResponseHeaders["Connection"] = "Upgrade"; - if (!ResponseHeaders.ContainsKey("Upgrade")) - { - StringValues values; - if (RequestHeaders.TryGetValue("Upgrade", out values)) - { - ResponseHeaders["Upgrade"] = values; - } - } - - await FlushAsync(default(CancellationToken)); - - return _frameStreams.Upgrade(); - } - IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); diff --git a/src/Kestrel.Core/Internal/Http2/Http2Stream.Generated.cs b/src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs similarity index 98% rename from src/Kestrel.Core/Internal/Http2/Http2Stream.Generated.cs rename to src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs index 4652b01e41..5c57080c1f 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Stream.Generated.cs +++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public partial class Http2Stream + public partial class HttpProtocol { private static readonly Type IHttpRequestFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestFeature); private static readonly Type IHttpResponseFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpResponseFeature); @@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private static readonly Type IQueryFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IQueryFeature); private static readonly Type IFormFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IFormFeature); private static readonly Type IHttpUpgradeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature); + private static readonly Type IHttp2StreamIdFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttp2StreamIdFeature); private static readonly Type IResponseCookiesFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature); private static readonly Type IItemsFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IItemsFeature); private static readonly Type ITlsConnectionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature); @@ -28,7 +29,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private static readonly Type IHttpMinResponseDataRateFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinResponseDataRateFeature); private static readonly Type IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature); private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature); - private static readonly Type IHttp2StreamIdFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttp2StreamIdFeature); private object _currentIHttpRequestFeature; private object _currentIHttpResponseFeature; @@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private object _currentIQueryFeature; private object _currentIFormFeature; private object _currentIHttpUpgradeFeature; + private object _currentIHttp2StreamIdFeature; private object _currentIResponseCookiesFeature; private object _currentIItemsFeature; private object _currentITlsConnectionFeature; @@ -50,13 +51,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private object _currentIHttpMinResponseDataRateFeature; private object _currentIHttpBodyControlFeature; private object _currentIHttpSendFileFeature; - private object _currentIHttp2StreamIdFeature; private void FastReset() { _currentIHttpRequestFeature = this; _currentIHttpResponseFeature = this; - _currentIHttpUpgradeFeature = this; _currentIHttpRequestIdentifierFeature = this; _currentIHttpRequestLifetimeFeature = this; _currentIHttpConnectionFeature = this; @@ -64,12 +63,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 _currentIHttpMinRequestBodyDataRateFeature = this; _currentIHttpMinResponseDataRateFeature = this; _currentIHttpBodyControlFeature = this; - _currentIHttp2StreamIdFeature = this; _currentIServiceProvidersFeature = null; _currentIHttpAuthenticationFeature = null; _currentIQueryFeature = null; _currentIFormFeature = null; + _currentIHttpUpgradeFeature = null; + _currentIHttp2StreamIdFeature = null; _currentIResponseCookiesFeature = null; _currentIItemsFeature = null; _currentITlsConnectionFeature = null; @@ -120,6 +120,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { return _currentIHttpUpgradeFeature; } + if (key == IHttp2StreamIdFeatureType) + { + return _currentIHttp2StreamIdFeature; + } if (key == IResponseCookiesFeatureType) { return _currentIResponseCookiesFeature; @@ -160,14 +164,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { return _currentIHttpSendFileFeature; } - if (key == IHttp2StreamIdFeatureType) - { - return _currentIHttp2StreamIdFeature; - } return ExtraFeatureGet(key); } - internal void FastFeatureSet(Type key, object feature) + protected void FastFeatureSet(Type key, object feature) { _featureRevision++; @@ -221,6 +221,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 _currentIHttpUpgradeFeature = feature; return; } + if (key == IHttp2StreamIdFeatureType) + { + _currentIHttp2StreamIdFeature = feature; + return; + } if (key == IResponseCookiesFeatureType) { _currentIResponseCookiesFeature = feature; @@ -270,11 +275,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { _currentIHttpSendFileFeature = feature; return; - } - if (key == IHttp2StreamIdFeatureType) - { - _currentIHttp2StreamIdFeature = feature; - return; }; ExtraFeatureSet(key, feature); } @@ -321,6 +321,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { yield return new KeyValuePair(IHttpUpgradeFeatureType, _currentIHttpUpgradeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature); } + if (_currentIHttp2StreamIdFeature != null) + { + yield return new KeyValuePair(IHttp2StreamIdFeatureType, _currentIHttp2StreamIdFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttp2StreamIdFeature); + } if (_currentIResponseCookiesFeature != null) { yield return new KeyValuePair(IResponseCookiesFeatureType, _currentIResponseCookiesFeature as global::Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature); @@ -361,10 +365,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { yield return new KeyValuePair(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature); } - if (_currentIHttp2StreamIdFeature != null) - { - yield return new KeyValuePair(IHttp2StreamIdFeatureType, _currentIHttp2StreamIdFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttp2StreamIdFeature); - } if (MaybeExtra != null) { diff --git a/src/Kestrel.Core/Internal/Http/Frame.cs b/src/Kestrel.Core/Internal/Http/HttpProtocol.cs similarity index 62% rename from src/Kestrel.Core/Internal/Http/Frame.cs rename to src/Kestrel.Core/Internal/Http/HttpProtocol.cs index 64c24e2813..e99f929a1f 100644 --- a/src/Kestrel.Core/Internal/Http/Frame.cs +++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.cs @@ -10,11 +10,11 @@ using System.Linq; using System.Net; using System.Runtime.CompilerServices; using System.Text; -using System.Text.Encodings.Web.Utf8; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; @@ -23,43 +23,29 @@ using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public abstract partial class Frame : IFrameControl + public abstract partial class HttpProtocol : IHttpResponseControl { - private const byte ByteAsterisk = (byte)'*'; - private const byte ByteForwardSlash = (byte)'/'; - private const byte BytePercentage = (byte)'%'; - - private static readonly ArraySegment _endChunkedResponseBytes = CreateAsciiByteArraySegment("0\r\n\r\n"); - private static readonly ArraySegment _continueBytes = CreateAsciiByteArraySegment("HTTP/1.1 100 Continue\r\n\r\n"); - private static readonly Action _writeHeaders = WriteResponseHeaders; - private static readonly byte[] _bytesConnectionClose = Encoding.ASCII.GetBytes("\r\nConnection: close"); private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive"); private static readonly byte[] _bytesTransferEncodingChunked = Encoding.ASCII.GetBytes("\r\nTransfer-Encoding: chunked"); - private static readonly byte[] _bytesHttpVersion11 = Encoding.ASCII.GetBytes("HTTP/1.1 "); - private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n"); private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: " + Constants.ServerName); - - private const string EmptyPath = "/"; - private const string Asterisk = "*"; + private static readonly Action> _writeChunk = WriteChunk; private readonly object _onStartingSync = new Object(); private readonly object _onCompletedSync = new Object(); - private Streams _frameStreams; + protected Streams _streams; protected Stack, object>> _onStarting; protected Stack, object>> _onCompleted; protected volatile int _requestAborted; - private volatile bool _requestTimedOut; - private CancellationTokenSource _abortedCts; + protected CancellationTokenSource _abortedCts; private CancellationToken? _manuallySetRequestAbortToken; protected RequestProcessingStatus _requestProcessingStatus; protected volatile bool _keepAlive = true; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx protected bool _upgradeAvailable; - private volatile bool _wasUpgraded; private bool _canHaveBody; private bool _autoChunk; protected Exception _applicationException; @@ -68,55 +54,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http protected HttpVersion _httpVersion; private string _requestId; - private int _remainingRequestHeadersBytesAllowed; - private int _requestHeadersParsed; - private uint _requestCount; - - protected readonly long _keepAliveTicks; - private readonly long _requestHeadersTimeoutTicks; + protected int _requestHeadersParsed; protected long _responseBytesWritten; - private readonly FrameContext _frameContext; - private readonly IHttpParser _parser; + private readonly IHttpProtocolContext _context; - private HttpRequestTarget _requestTargetForm = HttpRequestTarget.Unknown; - private Uri _absoluteRequestTarget; private string _scheme = null; - public Frame(FrameContext frameContext) + public HttpProtocol(IHttpProtocolContext context) { - _frameContext = frameContext; + _context = context; ServerOptions = ServiceContext.ServerOptions; - - _parser = ServiceContext.HttpParserFactory(new FrameAdapter(this)); - - FrameControl = this; - _keepAliveTicks = ServerOptions.Limits.KeepAliveTimeout.Ticks; - _requestHeadersTimeoutTicks = ServerOptions.Limits.RequestHeadersTimeout.Ticks; - - Output = new OutputProducer(frameContext.Application.Input, frameContext.Transport.Output, frameContext.ConnectionId, frameContext.ServiceContext.Log, TimeoutControl); + HttpResponseControl = this; RequestBodyPipe = CreateRequestBodyPipe(); } + public IHttpResponseControl HttpResponseControl { get; set; } + public IPipe RequestBodyPipe { get; } - public ServiceContext ServiceContext => _frameContext.ServiceContext; - private IPEndPoint LocalEndPoint => _frameContext.LocalEndPoint; - private IPEndPoint RemoteEndPoint => _frameContext.RemoteEndPoint; + public ServiceContext ServiceContext => _context.ServiceContext; + private IPEndPoint LocalEndPoint => _context.LocalEndPoint; + private IPEndPoint RemoteEndPoint => _context.RemoteEndPoint; - public IFeatureCollection ConnectionFeatures => _frameContext.ConnectionFeatures; - public IPipeReader Input => _frameContext.Transport.Input; - public OutputProducer Output { get; } - public ITimeoutControl TimeoutControl => _frameContext.TimeoutControl; - public bool RequestTimedOut => _requestTimedOut; + public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures; + public IHttpOutputProducer Output { get; protected set; } protected IKestrelTrace Log => ServiceContext.Log; private DateHeaderValueManager DateHeaderValueManager => ServiceContext.DateHeaderValueManager; // Hold direct reference to ServerOptions since this is used very often in the request processing path - private KestrelServerOptions ServerOptions { get; } - protected string ConnectionId => _frameContext.ConnectionId; + protected KestrelServerOptions ServerOptions { get; } + protected string ConnectionId => _context.ConnectionId; public string ConnectionIdFeature { get; set; } public bool HasStartedConsumingRequestBody { get; set; } @@ -134,13 +104,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http // don't generate an ID until it is requested if (_requestId == null) { - _requestId = StringUtilities.ConcatAsHexSuffix(ConnectionId, ':', _requestCount); + _requestId = CreateRequestId(); } return _requestId; } } - public bool WasUpgraded => _wasUpgraded; + public abstract bool IsUpgradableRequest { get; } + public bool IsUpgraded { get; set; } public IPAddress RemoteIpAddress { get; set; } public int RemotePort { get; set; } public IPAddress LocalIpAddress { get; set; } @@ -164,6 +135,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return HttpUtilities.Http10Version; } + if (_httpVersion == Http.HttpVersion.Http2) + { + return HttpUtilities.Http2Version; + } return string.Empty; } @@ -181,6 +156,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { _httpVersion = Http.HttpVersion.Http10; } + else if (ReferenceEquals(value, HttpUtilities.Http2Version)) + { + _httpVersion = Http.HttpVersion.Http2; + } else { HttpVersionSetSlow(value); @@ -199,6 +178,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { _httpVersion = Http.HttpVersion.Http10; } + else if (value == HttpUtilities.Http2Version) + { + _httpVersion = Http.HttpVersion.Http2; + } else { _httpVersion = Http.HttpVersion.Unknown; @@ -295,9 +278,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public bool HasResponseStarted => _requestProcessingStatus == RequestProcessingStatus.ResponseStarted; - protected FrameRequestHeaders FrameRequestHeaders { get; } = new FrameRequestHeaders(); + protected HttpRequestHeaders HttpRequestHeaders { get; } = new HttpRequestHeaders(); - protected FrameResponseHeaders FrameResponseHeaders { get; } = new FrameResponseHeaders(); + protected HttpResponseHeaders HttpResponseHeaders { get; } = new HttpResponseHeaders(); public MinDataRate MinRequestBodyDataRate { get; set; } @@ -305,17 +288,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public void InitializeStreams(MessageBody messageBody) { - if (_frameStreams == null) + if (_streams == null) { - _frameStreams = new Streams(bodyControl: this, frameControl: this); + _streams = new Streams(bodyControl: this, httpResponseControl: this); } - (RequestBody, ResponseBody) = _frameStreams.Start(messageBody); + (RequestBody, ResponseBody) = _streams.Start(messageBody); } - public void PauseStreams() => _frameStreams.Pause(); + public void PauseStreams() => _streams.Pause(); - public void StopStreams() => _frameStreams.Stop(); + public void StopStreams() => _streams.Stop(); // For testing internal void ResetState() @@ -331,7 +314,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _requestProcessingStatus = RequestProcessingStatus.RequestPending; _autoChunk = false; _applicationException = null; - _requestTimedOut = false; _requestRejectedException = null; ResetFeatureCollection(); @@ -344,8 +326,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http PathBase = null; Path = null; RawTarget = null; - _requestTargetForm = HttpRequestTarget.Unknown; - _absoluteRequestTarget = null; QueryString = null; _httpVersion = Http.HttpVersion.Unknown; _statusCode = StatusCodes.Status200OK; @@ -358,10 +338,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http LocalPort = LocalEndPoint?.Port ?? 0; ConnectionIdFeature = ConnectionId; - FrameRequestHeaders.Reset(); - FrameResponseHeaders.Reset(); - RequestHeaders = FrameRequestHeaders; - ResponseHeaders = FrameResponseHeaders; + HttpRequestHeaders.Reset(); + HttpResponseHeaders.Reset(); + RequestHeaders = HttpRequestHeaders; + ResponseHeaders = HttpResponseHeaders; if (_scheme == null) { @@ -375,33 +355,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _abortedCts = null; // Allow two bytes for \r\n after headers - _remainingRequestHeadersBytesAllowed = ServerOptions.Limits.MaxRequestHeadersTotalSize + 2; _requestHeadersParsed = 0; _responseBytesWritten = 0; - _requestCount++; MinRequestBodyDataRate = ServerOptions.Limits.MinRequestBodyDataRate; MinResponseDataRate = ServerOptions.Limits.MinResponseDataRate; + + OnReset(); } - /// - /// Stops the request processing loop between requests. - /// Called on all active connections when the server wants to initiate a shutdown - /// and after a keep-alive timeout. - /// - public void StopProcessingNextRequest() + protected abstract void OnReset(); + + protected virtual void OnRequestProcessingEnding() { - _keepAlive = false; - Input.CancelPendingRead(); } - public void SendTimeoutResponse() + protected virtual void OnRequestProcessingEnded() { - _requestTimedOut = true; - Input.CancelPendingRead(); } + protected abstract string CreateRequestId(); + + protected abstract MessageBody CreateMessageBody(); + + protected abstract Task ParseRequestAsync(); + + protected abstract void CreateHttpContext(); + + protected abstract void DisposeHttpContext(); + + protected abstract Task InvokeApplicationAsync(); + private void CancelRequestAbortedToken() { try @@ -424,22 +409,185 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { _keepAlive = false; - _frameStreams?.Abort(error); + _streams?.Abort(error); Output.Abort(error); // Potentially calling user code. CancelRequestAbortedToken logs any exceptions. - ServiceContext.ThreadPool.UnsafeRun(state => ((Frame)state).CancelRequestAbortedToken(), this); + ServiceContext.ThreadPool.UnsafeRun(state => ((HttpProtocol)state).CancelRequestAbortedToken(), this); } } - /// - /// Primary loop which consumes socket input, parses it for protocol framing, and invokes the - /// application delegate for as long as the socket is intended to remain open. - /// The resulting Task from this loop is preserved in a field which is used when the server needs - /// to drain and close all currently active connections. - /// - public abstract Task ProcessRequestsAsync(); + public async Task ProcessRequestsAsync() + { + try + { + while (_keepAlive) + { + if (!await ParseRequestAsync()) + { + return; + } + + var messageBody = CreateMessageBody(); + + if (!messageBody.RequestKeepAlive) + { + _keepAlive = false; + } + + _upgradeAvailable = messageBody.RequestUpgrade; + + InitializeStreams(messageBody); + + CreateHttpContext(); + try + { + try + { + KestrelEventSource.Log.RequestStart(this); + + await InvokeApplicationAsync(); + + if (_requestAborted == 0) + { + VerifyResponseContentLength(); + } + } + catch (Exception ex) + { + ReportApplicationError(ex); + + if (ex is BadHttpRequestException) + { + throw; + } + } + finally + { + KestrelEventSource.Log.RequestStop(this); + + // Trigger OnStarting if it hasn't been called yet and the app hasn't + // already failed. If an OnStarting callback throws we can go through + // our normal error handling in ProduceEnd. + // https://github.com/aspnet/KestrelHttpServer/issues/43 + if (!HasResponseStarted && _applicationException == null && _onStarting != null) + { + await FireOnStarting(); + } + + PauseStreams(); + + if (_onCompleted != null) + { + await FireOnCompleted(); + } + } + + // If _requestAbort is set, the connection has already been closed. + if (_requestAborted == 0) + { + if (HasResponseStarted) + { + // If the response has already started, call ProduceEnd() before + // consuming the rest of the request body to prevent + // delaying clients waiting for the chunk terminator: + // + // https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663 + // + // ProduceEnd() must be called before _application.DisposeContext(), to ensure + // HttpContext.Response.StatusCode is correctly set when + // IHttpContextFactory.Dispose(HttpContext) is called. + await ProduceEnd(); + } + + // ForZeroContentLength does not complete the reader nor the writer + if (!messageBody.IsEmpty && _keepAlive) + { + // Finish reading the request body in case the app did not. + await messageBody.ConsumeAsync(); + } + + if (!HasResponseStarted) + { + await ProduceEnd(); + } + } + else if (!HasResponseStarted) + { + // If the request was aborted and no response was sent, there's no + // meaningful status code to log. + StatusCode = 0; + } + } + catch (BadHttpRequestException ex) + { + // Handle BadHttpRequestException thrown during app execution or remaining message body consumption. + // This has to be caught here so StatusCode is set properly before disposing the HttpContext + // (DisposeContext logs StatusCode). + SetBadRequestState(ex); + } + finally + { + DisposeHttpContext(); + + // StopStreams should be called before the end of the "if (!_requestProcessingStopping)" block + // to ensure InitializeStreams has been called. + StopStreams(); + + if (HasStartedConsumingRequestBody) + { + RequestBodyPipe.Reader.Complete(); + + // Wait for MessageBody.PumpAsync() to call RequestBodyPipe.Writer.Complete(). + await messageBody.StopAsync(); + + // At this point both the request body pipe reader and writer should be completed. + RequestBodyPipe.Reset(); + } + } + } + } + catch (BadHttpRequestException ex) + { + // Handle BadHttpRequestException thrown during request line or header parsing. + // SetBadRequestState logs the error. + SetBadRequestState(ex); + } + catch (ConnectionResetException ex) + { + // Don't log ECONNRESET errors made between requests. Browsers like IE will reset connections regularly. + if (_requestProcessingStatus != RequestProcessingStatus.RequestPending) + { + Log.RequestProcessingError(ConnectionId, ex); + } + } + catch (IOException ex) + { + Log.RequestProcessingError(ConnectionId, ex); + } + catch (Exception ex) + { + Log.LogWarning(0, ex, CoreStrings.RequestProcessingEndError); + } + finally + { + try + { + OnRequestProcessingEnding(); + await TryProduceInvalidRequestResponse(); + Output.Dispose(); + } + catch (Exception ex) + { + Log.LogWarning(0, ex, CoreStrings.ConnectionShutdownError); + } + finally + { + OnRequestProcessingEnded(); + } + } + } public void OnStarting(Func callback, object state) { @@ -590,7 +738,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public Task WriteAsync(ArraySegment data, CancellationToken cancellationToken = default(CancellationToken)) { - // For the first write, ensure headers are flushed if Write(Chunked)Async isn't called. + // For the first write, ensure headers are flushed if WriteDataAsync isn't called. var firstWrite = !HasResponseStarted; if (firstWrite) @@ -620,7 +768,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http else { CheckLastWrite(); - return Output.WriteAsync(data, cancellationToken: cancellationToken); + return Output.WriteDataAsync(data, cancellationToken: cancellationToken); } } else @@ -651,7 +799,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http else { CheckLastWrite(); - await Output.WriteAsync(data, cancellationToken: cancellationToken); + await Output.WriteDataAsync(data, cancellationToken: cancellationToken); } } else @@ -663,7 +811,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private void VerifyAndUpdateWrite(int count) { - var responseHeaders = FrameResponseHeaders; + var responseHeaders = HttpResponseHeaders; if (responseHeaders != null && !responseHeaders.HasTransferEncoding && @@ -680,7 +828,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private void CheckLastWrite() { - var responseHeaders = FrameResponseHeaders; + var responseHeaders = HttpResponseHeaders; // Prevent firing request aborted token if this is the last write, to avoid // aborting the request if the app is still running when the client receives @@ -698,7 +846,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http protected void VerifyResponseContentLength() { - var responseHeaders = FrameResponseHeaders; + var responseHeaders = HttpResponseHeaders; if (!HttpMethods.IsHead(Method) && !responseHeaders.HasTransferEncoding && @@ -719,12 +867,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private Task WriteChunkedAsync(ArraySegment data, CancellationToken cancellationToken) { - return Output.WriteAsync(data, chunk: true, cancellationToken: cancellationToken); + return Output.WriteAsync(_writeChunk, data); } - private Task WriteChunkedResponseSuffix() + private static void WriteChunk(WritableBuffer writableBuffer, ArraySegment buffer) { - return Output.WriteAsync(_endChunkedResponseBytes); + var writer = new WritableBufferWriter(writableBuffer); + if (buffer.Count > 0) + { + ChunkWriter.WriteBeginChunkBytes(ref writer, buffer.Count); + writer.Write(buffer.Array, buffer.Offset, buffer.Count); + ChunkWriter.WriteEndChunkBytes(ref writer); + } } private static ArraySegment CreateAsciiByteArraySegment(string text) @@ -740,11 +894,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return; } - if (_httpVersion == Http.HttpVersion.Http11 && + if (_httpVersion != Http.HttpVersion.Http10 && RequestHeaders.TryGetValue("Expect", out var expect) && (expect.FirstOrDefault() ?? "").Equals("100-continue", StringComparison.OrdinalIgnoreCase)) { - Output.WriteAsync(_continueBytes).GetAwaiter().GetResult(); + Output.Write100ContinueAsync(default(CancellationToken)).GetAwaiter().GetResult(); } } @@ -843,7 +997,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http ProduceStart(appCompleted: true); // Force flush - await Output.FlushAsync(); + await Output.FlushAsync(default(CancellationToken)); await WriteSuffix(); } @@ -852,9 +1006,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { // _autoChunk should be checked after we are sure ProduceStart() has been called // since ProduceStart() may set _autoChunk to true. - if (_autoChunk) + if (_autoChunk || _httpVersion == Http.HttpVersion.Http2) { - return WriteAutoChunkSuffixAwaited(); + return WriteSuffixAwaited(); } if (_keepAlive) @@ -870,26 +1024,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return Task.CompletedTask; } - private async Task WriteAutoChunkSuffixAwaited() + private async Task WriteSuffixAwaited() { // For the same reason we call CheckLastWrite() in Content-Length responses. _abortedCts = null; - await WriteChunkedResponseSuffix(); + await Output.WriteStreamSuffixAsync(default(CancellationToken)); if (_keepAlive) { Log.ConnectionKeepAlive(ConnectionId); } + + if (HttpMethods.IsHead(Method) && _responseBytesWritten > 0) + { + Log.ConnectionHeadResponseBodyWrite(ConnectionId, _responseBytesWritten); + } } private void CreateResponseHeader(bool appCompleted) { - var responseHeaders = FrameResponseHeaders; + var responseHeaders = HttpResponseHeaders; var hasConnection = responseHeaders.HasConnection; - var connectionOptions = FrameHeaders.ParseConnection(responseHeaders.HeaderConnection); + var connectionOptions = HttpHeaders.ParseConnection(responseHeaders.HeaderConnection); var hasTransferEncoding = responseHeaders.HasTransferEncoding; - var transferCoding = FrameHeaders.GetFinalTransferCoding(responseHeaders.HeaderTransferEncoding); + var transferCoding = HttpHeaders.GetFinalTransferCoding(responseHeaders.HeaderTransferEncoding); if (_keepAlive && hasConnection && (connectionOptions & ConnectionOptions.KeepAlive) != ConnectionOptions.KeepAlive) { @@ -930,6 +1089,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http // // A server MUST NOT send a response containing Transfer-Encoding unless the corresponding // request indicates HTTP/1.1 (or later). + // + // This also covers HTTP/2, which forbids chunked encoding in RFC 7540 (section 8.1: + // + // The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in HTTP/2. if (_httpVersion == Http.HttpVersion.Http11 && StatusCode != StatusCodes.Status101SwitchingProtocols) { _autoChunk = true; @@ -949,7 +1112,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http responseHeaders.SetReadOnly(); - if (!hasConnection) + if (!hasConnection && _httpVersion != Http.HttpVersion.Http2) { if (!_keepAlive) { @@ -972,103 +1135,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http responseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes); } - Output.Write(_writeHeaders, new FrameAdapter(this)); - } - - private static void WriteResponseHeaders(WritableBuffer writableBuffer, FrameAdapter frameAdapter) - { - var frame = frameAdapter.Frame; - var writer = new WritableBufferWriter(writableBuffer); - - var responseHeaders = frame.FrameResponseHeaders; - writer.Write(_bytesHttpVersion11); - var statusBytes = ReasonPhrases.ToStatusBytes(frame.StatusCode, frame.ReasonPhrase); - writer.Write(statusBytes); - responseHeaders.CopyTo(ref writer); - writer.Write(_bytesEndHeaders); - } - - public void ParseRequest(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) - { - consumed = buffer.Start; - examined = buffer.End; - - switch (_requestProcessingStatus) - { - case RequestProcessingStatus.RequestPending: - if (buffer.IsEmpty) - { - break; - } - - TimeoutControl.ResetTimeout(_requestHeadersTimeoutTicks, TimeoutAction.SendTimeoutResponse); - - _requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine; - goto case RequestProcessingStatus.ParsingRequestLine; - case RequestProcessingStatus.ParsingRequestLine: - if (TakeStartLine(buffer, out consumed, out examined)) - { - buffer = buffer.Slice(consumed, buffer.End); - - _requestProcessingStatus = RequestProcessingStatus.ParsingHeaders; - goto case RequestProcessingStatus.ParsingHeaders; - } - else - { - break; - } - case RequestProcessingStatus.ParsingHeaders: - if (TakeMessageHeaders(buffer, out consumed, out examined)) - { - _requestProcessingStatus = RequestProcessingStatus.AppStarted; - } - break; - } - } - - public bool TakeStartLine(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) - { - var overLength = false; - if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize) - { - buffer = buffer.Slice(buffer.Start, ServerOptions.Limits.MaxRequestLineSize); - overLength = true; - } - - var result = _parser.ParseRequestLine(new FrameAdapter(this), buffer, out consumed, out examined); - if (!result && overLength) - { - ThrowRequestRejected(RequestRejectionReason.RequestLineTooLong); - } - - return result; - } - - public bool TakeMessageHeaders(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) - { - // Make sure the buffer is limited - bool overLength = false; - if (buffer.Length >= _remainingRequestHeadersBytesAllowed) - { - buffer = buffer.Slice(buffer.Start, _remainingRequestHeadersBytesAllowed); - - // If we sliced it means the current buffer bigger than what we're - // allowed to look at - overLength = true; - } - - var result = _parser.ParseHeaders(new FrameAdapter(this), buffer, out consumed, out examined, out var consumedBytes); - _remainingRequestHeadersBytesAllowed -= consumedBytes; - - if (!result && overLength) - { - ThrowRequestRejected(RequestRejectionReason.HeadersExceedMaxTotalSize); - } - if (result) - { - TimeoutControl.CancelTimeout(); - } - return result; + Output.WriteResponseHeaders(StatusCode, ReasonPhrase, responseHeaders); } public bool StatusCanHaveBody(int statusCode) @@ -1108,7 +1175,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (!StringValues.IsNullOrEmpty(ex.AllowedHeader)) { - FrameResponseHeaders.HeaderAllow = ex.AllowedHeader; + HttpResponseHeaders.HeaderAllow = ex.AllowedHeader; } } @@ -1119,7 +1186,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http StatusCode = statusCode; ReasonPhrase = null; - var responseHeaders = FrameResponseHeaders; + var responseHeaders = HttpResponseHeaders; responseHeaders.Reset(); var dateHeaderValues = DateHeaderValueManager.GetDateHeaderValues(); @@ -1154,7 +1221,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public void ThrowRequestRejected(RequestRejectionReason reason, string detail) => throw BadHttpRequestException.GetException(reason, detail); - private void ThrowRequestTargetRejected(Span target) + public void ThrowRequestTargetRejected(Span target) => throw GetInvalidRequestTargetException(target); private BadHttpRequestException GetInvalidRequestTargetException(Span target) @@ -1200,277 +1267,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http Log.ApplicationError(ConnectionId, TraceIdentifier, ex); } - public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) - { - Debug.Assert(target.Length != 0, "Request target must be non-zero length"); - - var ch = target[0]; - if (ch == ByteForwardSlash) - { - // origin-form. - // The most common form of request-target. - // https://tools.ietf.org/html/rfc7230#section-5.3.1 - OnOriginFormTarget(method, version, target, path, query, customMethod, pathEncoded); - } - else if (ch == ByteAsterisk && target.Length == 1) - { - OnAsteriskFormTarget(method); - } - else if (target.GetKnownHttpScheme(out var scheme)) - { - OnAbsoluteFormTarget(target, query); - } - else - { - // Assume anything else is considered authority form. - // FYI: this should be an edge case. This should only happen when - // a client mistakenly thinks this server is a proxy server. - OnAuthorityFormTarget(method, target); - } - - Method = method != HttpMethod.Custom - ? HttpUtilities.MethodToString(method) ?? string.Empty - : customMethod.GetAsciiStringNonNullCharacters(); - _httpVersion = version; - - Debug.Assert(RawTarget != null, "RawTarget was not set"); - Debug.Assert(Method != null, "Method was not set"); - Debug.Assert(Path != null, "Path was not set"); - Debug.Assert(QueryString != null, "QueryString was not set"); - Debug.Assert(HttpVersion != null, "HttpVersion was not set"); - } - - private void OnOriginFormTarget(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) - { - Debug.Assert(target[0] == ByteForwardSlash, "Should only be called when path starts with /"); - - _requestTargetForm = HttpRequestTarget.OriginForm; - - // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11 - // Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8; - // then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs" - string requestUrlPath = null; - string rawTarget = null; - - try - { - // Read raw target before mutating memory. - rawTarget = target.GetAsciiStringNonNullCharacters(); - - if (pathEncoded) - { - // URI was encoded, unescape and then parse as UTF-8 - var pathLength = UrlEncoder.Decode(path, path); - - // Removing dot segments must be done after unescaping. From RFC 3986: - // - // URI producing applications should percent-encode data octets that - // correspond to characters in the reserved set unless these characters - // are specifically allowed by the URI scheme to represent data in that - // component. If a reserved character is found in a URI component and - // no delimiting role is known for that character, then it must be - // interpreted as representing the data octet corresponding to that - // character's encoding in US-ASCII. - // - // https://tools.ietf.org/html/rfc3986#section-2.2 - pathLength = PathNormalizer.RemoveDotSegments(path.Slice(0, pathLength)); - - requestUrlPath = GetUtf8String(path.Slice(0, pathLength)); - } - else - { - var pathLength = PathNormalizer.RemoveDotSegments(path); - - if (path.Length == pathLength && query.Length == 0) - { - // If no decoding was required, no dot segments were removed and - // there is no query, the request path is the same as the raw target - requestUrlPath = rawTarget; - } - else - { - requestUrlPath = path.Slice(0, pathLength).GetAsciiStringNonNullCharacters(); - } - } - } - catch (InvalidOperationException) - { - ThrowRequestTargetRejected(target); - } - - QueryString = query.GetAsciiStringNonNullCharacters(); - RawTarget = rawTarget; - Path = requestUrlPath; - } - - private void OnAuthorityFormTarget(HttpMethod method, Span target) - { - _requestTargetForm = HttpRequestTarget.AuthorityForm; - - // This is not complete validation. It is just a quick scan for invalid characters - // but doesn't check that the target fully matches the URI spec. - for (var i = 0; i < target.Length; i++) - { - var ch = target[i]; - if (!UriUtilities.IsValidAuthorityCharacter(ch)) - { - ThrowRequestTargetRejected(target); - } - } - - // The authority-form of request-target is only used for CONNECT - // requests (https://tools.ietf.org/html/rfc7231#section-4.3.6). - if (method != HttpMethod.Connect) - { - ThrowRequestRejected(RequestRejectionReason.ConnectMethodRequired); - } - - // When making a CONNECT request to establish a tunnel through one or - // more proxies, a client MUST send only the target URI's authority - // component (excluding any userinfo and its "@" delimiter) as the - // request-target.For example, - // - // CONNECT www.example.com:80 HTTP/1.1 - // - // Allowed characters in the 'host + port' section of authority. - // See https://tools.ietf.org/html/rfc3986#section-3.2 - RawTarget = target.GetAsciiStringNonNullCharacters(); - Path = string.Empty; - QueryString = string.Empty; - } - - private void OnAsteriskFormTarget(HttpMethod method) - { - _requestTargetForm = HttpRequestTarget.AsteriskForm; - - // The asterisk-form of request-target is only used for a server-wide - // OPTIONS request (https://tools.ietf.org/html/rfc7231#section-4.3.7). - if (method != HttpMethod.Options) - { - ThrowRequestRejected(RequestRejectionReason.OptionsMethodRequired); - } - - RawTarget = Asterisk; - Path = string.Empty; - QueryString = string.Empty; - } - - private void OnAbsoluteFormTarget(Span target, Span query) - { - _requestTargetForm = HttpRequestTarget.AbsoluteForm; - - // absolute-form - // https://tools.ietf.org/html/rfc7230#section-5.3.2 - - // This code should be the edge-case. - - // From the spec: - // a server MUST accept the absolute-form in requests, even though - // HTTP/1.1 clients will only send them in requests to proxies. - - RawTarget = target.GetAsciiStringNonNullCharacters(); - - // Validation of absolute URIs is slow, but clients - // should not be sending this form anyways, so perf optimization - // not high priority - - if (!Uri.TryCreate(RawTarget, UriKind.Absolute, out var uri)) - { - ThrowRequestTargetRejected(target); - } - - _absoluteRequestTarget = uri; - Path = uri.LocalPath; - // don't use uri.Query because we need the unescaped version - QueryString = query.GetAsciiStringNonNullCharacters(); - } - - private unsafe static string GetUtf8String(Span path) - { - // .NET 451 doesn't have pointer overloads for Encoding.GetString so we - // copy to an array - fixed (byte* pointer = &path.DangerousGetPinnableReference()) - { - return Encoding.UTF8.GetString(pointer, path.Length); - } - } - - public void OnHeader(Span name, Span value) - { - _requestHeadersParsed++; - if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount) - { - ThrowRequestRejected(RequestRejectionReason.TooManyHeaders); - } - var valueString = value.GetAsciiStringNonNullCharacters(); - - FrameRequestHeaders.Append(name, valueString); - } - - protected void EnsureHostHeaderExists() - { - if (_httpVersion == Http.HttpVersion.Http10) - { - return; - } - - // https://tools.ietf.org/html/rfc7230#section-5.4 - // A server MUST respond with a 400 (Bad Request) status code to any - // HTTP/1.1 request message that lacks a Host header field and to any - // request message that contains more than one Host header field or a - // Host header field with an invalid field-value. - - var host = FrameRequestHeaders.HeaderHost; - if (host.Count <= 0) - { - ThrowRequestRejected(RequestRejectionReason.MissingHostHeader); - } - else if (host.Count > 1) - { - ThrowRequestRejected(RequestRejectionReason.MultipleHostHeaders); - } - else if (_requestTargetForm == HttpRequestTarget.AuthorityForm) - { - if (!host.Equals(RawTarget)) - { - ThrowRequestRejected(RequestRejectionReason.InvalidHostHeader, host.ToString()); - } - } - else if (_requestTargetForm == HttpRequestTarget.AbsoluteForm) - { - // If the target URI includes an authority component, then a - // client MUST send a field - value for Host that is identical to that - // authority component, excluding any userinfo subcomponent and its "@" - // delimiter. - - // System.Uri doesn't not tell us if the port was in the original string or not. - // When IsDefaultPort = true, we will allow Host: with or without the default port - var authorityAndPort = _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port; - if ((host != _absoluteRequestTarget.Authority || !_absoluteRequestTarget.IsDefaultPort) - && host != authorityAndPort) - { - ThrowRequestRejected(RequestRejectionReason.InvalidHostHeader, host.ToString()); - } - } - } - private IPipe CreateRequestBodyPipe() - => _frameContext.PipeFactory.Create(new PipeOptions + => _context.PipeFactory.Create(new PipeOptions { ReaderScheduler = ServiceContext.ThreadPool, WriterScheduler = InlineScheduler.Default, MaximumSizeHigh = 1, MaximumSizeLow = 1 }); - - private enum HttpRequestTarget - { - Unknown = -1, - // origin-form is the most common - OriginForm, - AbsoluteForm, - AuthorityForm, - AsteriskForm - } } } diff --git a/src/Kestrel.Core/Internal/Http/FrameRequestHeaders.cs b/src/Kestrel.Core/Internal/Http/HttpRequestHeaders.cs similarity index 95% rename from src/Kestrel.Core/Internal/Http/FrameRequestHeaders.cs rename to src/Kestrel.Core/Internal/Http/HttpRequestHeaders.cs index 241a086ea8..fd0008e52b 100644 --- a/src/Kestrel.Core/Internal/Http/FrameRequestHeaders.cs +++ b/src/Kestrel.Core/Internal/Http/HttpRequestHeaders.cs @@ -11,7 +11,7 @@ using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public partial class FrameRequestHeaders : FrameHeaders + public partial class HttpRequestHeaders : HttpHeaders { private static long ParseContentLength(string value) { @@ -77,14 +77,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public partial struct Enumerator : IEnumerator> { - private readonly FrameRequestHeaders _collection; + private readonly HttpRequestHeaders _collection; private readonly long _bits; private int _state; private KeyValuePair _current; private readonly bool _hasUnknown; private Dictionary.Enumerator _unknownEnumerator; - internal Enumerator(FrameRequestHeaders collection) + internal Enumerator(HttpRequestHeaders collection) { _collection = collection; _bits = collection._bits; diff --git a/src/Kestrel.Core/Internal/Http/FrameRequestStream.cs b/src/Kestrel.Core/Internal/Http/HttpRequestStream.cs similarity index 88% rename from src/Kestrel.Core/Internal/Http/FrameRequestStream.cs rename to src/Kestrel.Core/Internal/Http/HttpRequestStream.cs index e04966ba94..ca81b5ae56 100644 --- a/src/Kestrel.Core/Internal/Http/FrameRequestStream.cs +++ b/src/Kestrel.Core/Internal/Http/HttpRequestStream.cs @@ -12,17 +12,17 @@ using Microsoft.AspNetCore.Protocols; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - internal class FrameRequestStream : ReadOnlyStream + internal class HttpRequestStream : ReadOnlyStream { private readonly IHttpBodyControlFeature _bodyControl; - private IMessageBody _body; - private FrameStreamState _state; + private MessageBody _body; + private HttpStreamState _state; private Exception _error; - public FrameRequestStream(IHttpBodyControlFeature bodyControl) + public HttpRequestStream(IHttpBodyControlFeature bodyControl) { _bodyControl = bodyControl; - _state = FrameStreamState.Closed; + _state = HttpStreamState.Closed; } public override bool CanSeek => false; @@ -159,26 +159,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - public void StartAcceptingReads(IMessageBody body) + public void StartAcceptingReads(MessageBody body) { // Only start if not aborted - if (_state == FrameStreamState.Closed) + if (_state == HttpStreamState.Closed) { - _state = FrameStreamState.Open; + _state = HttpStreamState.Open; _body = body; } } public void PauseAcceptingReads() { - _state = FrameStreamState.Closed; + _state = HttpStreamState.Closed; } public void StopAcceptingReads() { // Can't use dispose (or close) as can be disposed too early by user code // As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes - _state = FrameStreamState.Closed; + _state = HttpStreamState.Closed; _body = null; } @@ -187,9 +187,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http // We don't want to throw an ODE until the app func actually completes. // If the request is aborted, we throw a TaskCanceledException instead, // unless error is not null, in which case we throw it. - if (_state != FrameStreamState.Closed) + if (_state != HttpStreamState.Closed) { - _state = FrameStreamState.Aborted; + _state = HttpStreamState.Aborted; _error = error; } } @@ -198,15 +198,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { switch (_state) { - case FrameStreamState.Open: + case HttpStreamState.Open: if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } break; - case FrameStreamState.Closed: - throw new ObjectDisposedException(nameof(FrameRequestStream)); - case FrameStreamState.Aborted: + case HttpStreamState.Closed: + throw new ObjectDisposedException(nameof(HttpRequestStream)); + case HttpStreamState.Aborted: return _error != null ? Task.FromException(_error) : Task.FromCanceled(new CancellationToken(true)); diff --git a/src/Kestrel.Core/Internal/Http/HttpRequestTargetForm.cs b/src/Kestrel.Core/Internal/Http/HttpRequestTargetForm.cs new file mode 100644 index 0000000000..0e43670fa3 --- /dev/null +++ b/src/Kestrel.Core/Internal/Http/HttpRequestTargetForm.cs @@ -0,0 +1,15 @@ +// 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. + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http +{ + public enum HttpRequestTarget + { + Unknown = -1, + // origin-form is the most common + OriginForm, + AbsoluteForm, + AuthorityForm, + AsteriskForm + } +} diff --git a/src/Kestrel.Core/Internal/Http/FrameResponseHeaders.cs b/src/Kestrel.Core/Internal/Http/HttpResponseHeaders.cs similarity index 95% rename from src/Kestrel.Core/Internal/Http/FrameResponseHeaders.cs rename to src/Kestrel.Core/Internal/Http/HttpResponseHeaders.cs index e7dedeb7dd..41bc6ea29b 100644 --- a/src/Kestrel.Core/Internal/Http/FrameResponseHeaders.cs +++ b/src/Kestrel.Core/Internal/Http/HttpResponseHeaders.cs @@ -11,7 +11,7 @@ using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public partial class FrameResponseHeaders : FrameHeaders + public partial class HttpResponseHeaders : HttpHeaders { private static readonly byte[] _CrLf = new[] { (byte)'\r', (byte)'\n' }; private static readonly byte[] _colonSpace = new[] { (byte)':', (byte)' ' }; @@ -80,14 +80,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public partial struct Enumerator : IEnumerator> { - private readonly FrameResponseHeaders _collection; + private readonly HttpResponseHeaders _collection; private readonly long _bits; private int _state; private KeyValuePair _current; private readonly bool _hasUnknown; private Dictionary.Enumerator _unknownEnumerator; - internal Enumerator(FrameResponseHeaders collection) + internal Enumerator(HttpResponseHeaders collection) { _collection = collection; _bits = collection._bits; diff --git a/src/Kestrel.Core/Internal/Http/FrameResponseStream.cs b/src/Kestrel.Core/Internal/Http/HttpResponseStream.cs similarity index 81% rename from src/Kestrel.Core/Internal/Http/FrameResponseStream.cs rename to src/Kestrel.Core/Internal/Http/HttpResponseStream.cs index d4c6784b4c..90c8cae840 100644 --- a/src/Kestrel.Core/Internal/Http/FrameResponseStream.cs +++ b/src/Kestrel.Core/Internal/Http/HttpResponseStream.cs @@ -10,17 +10,17 @@ using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - internal class FrameResponseStream : WriteOnlyStream + internal class HttpResponseStream : WriteOnlyStream { private readonly IHttpBodyControlFeature _bodyControl; - private readonly IFrameControl _frameControl; - private FrameStreamState _state; + private readonly IHttpResponseControl _httpResponseControl; + private HttpStreamState _state; - public FrameResponseStream(IHttpBodyControlFeature bodyControl, IFrameControl frameControl) + public HttpResponseStream(IHttpBodyControlFeature bodyControl, IHttpResponseControl httpResponseControl) { _bodyControl = bodyControl; - _frameControl = frameControl; - _state = FrameStreamState.Closed; + _httpResponseControl = httpResponseControl; + _state = HttpStreamState.Closed; } public override bool CanSeek => false; @@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var task = ValidateState(cancellationToken); if (task == null) { - return _frameControl.FlushAsync(cancellationToken); + return _httpResponseControl.FlushAsync(cancellationToken); } return task; } @@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var task = ValidateState(cancellationToken); if (task == null) { - return _frameControl.WriteAsync(new ArraySegment(buffer, offset, count), cancellationToken); + return _httpResponseControl.WriteAsync(new ArraySegment(buffer, offset, count), cancellationToken); } return task; } @@ -120,30 +120,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public void StartAcceptingWrites() { // Only start if not aborted - if (_state == FrameStreamState.Closed) + if (_state == HttpStreamState.Closed) { - _state = FrameStreamState.Open; + _state = HttpStreamState.Open; } } public void PauseAcceptingWrites() { - _state = FrameStreamState.Closed; + _state = HttpStreamState.Closed; } public void StopAcceptingWrites() { // Can't use dispose (or close) as can be disposed too early by user code // As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes - _state = FrameStreamState.Closed; + _state = HttpStreamState.Closed; } public void Abort() { // We don't want to throw an ODE until the app func actually completes. - if (_state != FrameStreamState.Closed) + if (_state != HttpStreamState.Closed) { - _state = FrameStreamState.Aborted; + _state = HttpStreamState.Aborted; } } @@ -151,15 +151,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { switch (_state) { - case FrameStreamState.Open: + case HttpStreamState.Open: if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } break; - case FrameStreamState.Closed: - throw new ObjectDisposedException(nameof(FrameResponseStream)); - case FrameStreamState.Aborted: + case HttpStreamState.Closed: + throw new ObjectDisposedException(nameof(HttpResponseStream)); + case HttpStreamState.Aborted: if (cancellationToken.IsCancellationRequested) { // Aborted state only throws on write if cancellationToken requests it diff --git a/src/Kestrel.Core/Internal/Http/FrameStreamState.cs b/src/Kestrel.Core/Internal/Http/HttpStreamState.cs similarity index 91% rename from src/Kestrel.Core/Internal/Http/FrameStreamState.cs rename to src/Kestrel.Core/Internal/Http/HttpStreamState.cs index a46cb733eb..34d5e904f5 100644 --- a/src/Kestrel.Core/Internal/Http/FrameStreamState.cs +++ b/src/Kestrel.Core/Internal/Http/HttpStreamState.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - enum FrameStreamState + enum HttpStreamState { Open, Closed, diff --git a/src/Kestrel.Core/Internal/Http/FrameDuplexStream.cs b/src/Kestrel.Core/Internal/Http/HttpUpgradeStream.cs similarity index 97% rename from src/Kestrel.Core/Internal/Http/FrameDuplexStream.cs rename to src/Kestrel.Core/Internal/Http/HttpUpgradeStream.cs index 13bce2447c..e7163b7ece 100644 --- a/src/Kestrel.Core/Internal/Http/FrameDuplexStream.cs +++ b/src/Kestrel.Core/Internal/Http/HttpUpgradeStream.cs @@ -8,12 +8,12 @@ using System.Threading.Tasks; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - internal class FrameDuplexStream : Stream + internal class HttpUpgradeStream : Stream { private readonly Stream _requestStream; private readonly Stream _responseStream; - public FrameDuplexStream(Stream requestStream, Stream responseStream) + public HttpUpgradeStream(Stream requestStream, Stream responseStream) { _requestStream = requestStream; _responseStream = responseStream; diff --git a/src/Kestrel.Core/Internal/Http/HttpVersion.cs b/src/Kestrel.Core/Internal/Http/HttpVersion.cs index 5ca3e8bdba..832a1c5616 100644 --- a/src/Kestrel.Core/Internal/Http/HttpVersion.cs +++ b/src/Kestrel.Core/Internal/Http/HttpVersion.cs @@ -7,6 +7,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { Unknown = -1, Http10 = 0, - Http11 = 1 + Http11 = 1, + Http2 } } diff --git a/src/Kestrel.Core/Internal/Http/IHttpOutputProducer.cs b/src/Kestrel.Core/Internal/Http/IHttpOutputProducer.cs new file mode 100644 index 0000000000..adab288186 --- /dev/null +++ b/src/Kestrel.Core/Internal/Http/IHttpOutputProducer.cs @@ -0,0 +1,21 @@ +// 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.Core.Internal.Http +{ + public interface IHttpOutputProducer : IDisposable + { + void Abort(Exception error); + Task WriteAsync(Action callback, T state); + Task FlushAsync(CancellationToken cancellationToken); + Task Write100ContinueAsync(CancellationToken cancellationToken); + void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpResponseHeaders responseHeaders); + Task WriteDataAsync(ArraySegment data, CancellationToken cancellationToken); + Task WriteStreamSuffixAsync(CancellationToken cancellationToken); + } +} diff --git a/src/Kestrel.Core/Internal/Http/IHttpProtocolContext.cs b/src/Kestrel.Core/Internal/Http/IHttpProtocolContext.cs new file mode 100644 index 0000000000..fe1d5fa363 --- /dev/null +++ b/src/Kestrel.Core/Internal/Http/IHttpProtocolContext.cs @@ -0,0 +1,19 @@ +// 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.IO.Pipelines; +using System.Net; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http +{ + public interface IHttpProtocolContext + { + string ConnectionId { get; set; } + ServiceContext ServiceContext { get; set; } + IFeatureCollection ConnectionFeatures { get; set; } + PipeFactory PipeFactory { get; set; } + IPEndPoint RemoteEndPoint { get; set; } + IPEndPoint LocalEndPoint { get; set; } + } +} diff --git a/src/Kestrel.Core/Internal/Http/IFrameControl.cs b/src/Kestrel.Core/Internal/Http/IHttpResponseControl.cs similarity index 92% rename from src/Kestrel.Core/Internal/Http/IFrameControl.cs rename to src/Kestrel.Core/Internal/Http/IHttpResponseControl.cs index eed8069276..fac6b11c0b 100644 --- a/src/Kestrel.Core/Internal/Http/IFrameControl.cs +++ b/src/Kestrel.Core/Internal/Http/IHttpResponseControl.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public interface IFrameControl + public interface IHttpResponseControl { void ProduceContinue(); Task WriteAsync(ArraySegment data, CancellationToken cancellationToken); diff --git a/src/Kestrel.Core/Internal/Http/IMessageBody.cs b/src/Kestrel.Core/Internal/Http/IMessageBody.cs deleted file mode 100644 index 1f21fb47d1..0000000000 --- a/src/Kestrel.Core/Internal/Http/IMessageBody.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http -{ - public interface IMessageBody - { - Task ReadAsync(ArraySegment buffer, CancellationToken cancellationToken = default(CancellationToken)); - - Task CopyToAsync(Stream destination, CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/src/Kestrel.Core/Internal/Http/MessageBody.cs b/src/Kestrel.Core/Internal/Http/MessageBody.cs index 22c378b3ce..8ae912435a 100644 --- a/src/Kestrel.Core/Internal/Http/MessageBody.cs +++ b/src/Kestrel.Core/Internal/Http/MessageBody.cs @@ -11,129 +11,29 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public abstract class MessageBody : IMessageBody + public abstract class MessageBody { private static readonly MessageBody _zeroContentLengthClose = new ForZeroContentLength(keepAlive: false); private static readonly MessageBody _zeroContentLengthKeepAlive = new ForZeroContentLength(keepAlive: true); - // For testing - internal static MessageBody ZeroContentLengthKeepAlive => _zeroContentLengthKeepAlive; + private readonly HttpProtocol _context; - private readonly Frame _context; - - private bool _send100Continue = true; - private volatile bool _canceled; - private Task _pumpTask; - - protected MessageBody(Frame context) + protected MessageBody(HttpProtocol context) { _context = context; } public static MessageBody ZeroContentLengthClose => _zeroContentLengthClose; + public static MessageBody ZeroContentLengthKeepAlive => _zeroContentLengthKeepAlive; + public bool RequestKeepAlive { get; protected set; } public bool RequestUpgrade { get; protected set; } public virtual bool IsEmpty => false; - private IKestrelTrace Log => _context.ServiceContext.Log; - - private async Task PumpAsync() - { - Exception error = null; - - try - { - var awaitable = _context.Input.ReadAsync(); - - if (!awaitable.IsCompleted) - { - TryProduceContinue(); - } - - TryStartTimingReads(); - - while (true) - { - var result = await awaitable; - - if (_context.RequestTimedOut) - { - _context.ThrowRequestRejected(RequestRejectionReason.RequestTimeout); - } - - var readableBuffer = result.Buffer; - var consumed = readableBuffer.Start; - var examined = readableBuffer.End; - - try - { - if (_canceled) - { - break; - } - - if (!readableBuffer.IsEmpty) - { - var writableBuffer = _context.RequestBodyPipe.Writer.Alloc(1); - bool done; - - try - { - done = Read(readableBuffer, writableBuffer, out consumed, out examined); - } - finally - { - writableBuffer.Commit(); - } - - var writeAwaitable = writableBuffer.FlushAsync(); - var backpressure = false; - - if (!writeAwaitable.IsCompleted) - { - // Backpressure, stop controlling incoming data rate until data is read. - backpressure = true; - TryPauseTimingReads(); - } - - await writeAwaitable; - - if (backpressure) - { - TryResumeTimingReads(); - } - - if (done) - { - break; - } - } - else if (result.IsCompleted) - { - _context.ThrowRequestRejected(RequestRejectionReason.UnexpectedEndOfRequestContent); - } - - awaitable = _context.Input.ReadAsync(); - } - finally - { - _context.Input.Advance(consumed, examined); - } - } - } - catch (Exception ex) - { - error = ex; - } - finally - { - _context.RequestBodyPipe.Writer.Complete(error); - TryStopTimingReads(); - } - } + protected IKestrelTrace Log => _context.ServiceContext.Log; public virtual async Task ReadAsync(ArraySegment buffer, CancellationToken cancellationToken = default(CancellationToken)) { @@ -200,202 +100,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - public virtual async Task ConsumeAsync(CancellationToken cancellationToken = default(CancellationToken)) + public virtual Task ConsumeAsync() { TryInit(); - ReadResult result; - do - { - result = await _context.RequestBodyPipe.Reader.ReadAsync(); - _context.RequestBodyPipe.Reader.Advance(result.Buffer.End); - } while (!result.IsCompleted); + return OnConsumeAsync(); } - public virtual Task StopAsync() - { - if (!_context.HasStartedConsumingRequestBody) - { - return Task.CompletedTask; - } + protected abstract Task OnConsumeAsync(); - _canceled = true; - _context.Input.CancelPendingRead(); - return _pumpTask; - } - - protected void Copy(ReadableBuffer readableBuffer, WritableBuffer writableBuffer) - { - _context.TimeoutControl.BytesRead(readableBuffer.Length); - - if (readableBuffer.IsSingleSpan) - { - writableBuffer.Write(readableBuffer.First.Span); - } - else - { - foreach (var memory in readableBuffer) - { - writableBuffer.Write(memory.Span); - } - } - } - - private void TryProduceContinue() - { - if (_send100Continue) - { - _context.FrameControl.ProduceContinue(); - _send100Continue = false; - } - } + public abstract Task StopAsync(); private void TryInit() { if (!_context.HasStartedConsumingRequestBody) { - OnReadStart(); + OnReadStarting(); _context.HasStartedConsumingRequestBody = true; - _pumpTask = PumpAsync(); + OnReadStarted(); } } - protected virtual bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined) - { - throw new NotImplementedException(); - } - - protected virtual void OnReadStart() + protected virtual void OnReadStarting() { } - private void TryStartTimingReads() + protected virtual void OnReadStarted() { - if (!RequestUpgrade) - { - Log.RequestBodyStart(_context.ConnectionIdFeature, _context.TraceIdentifier); - _context.TimeoutControl.StartTimingReads(); - } - } - - private void TryPauseTimingReads() - { - if (!RequestUpgrade) - { - _context.TimeoutControl.PauseTimingReads(); - } - } - - private void TryResumeTimingReads() - { - if (!RequestUpgrade) - { - _context.TimeoutControl.ResumeTimingReads(); - } - } - - private void TryStopTimingReads() - { - if (!RequestUpgrade) - { - Log.RequestBodyDone(_context.ConnectionIdFeature, _context.TraceIdentifier); - _context.TimeoutControl.StopTimingReads(); - } - } - - public static MessageBody For( - HttpVersion httpVersion, - FrameRequestHeaders headers, - Frame context) - { - // see also http://tools.ietf.org/html/rfc2616#section-4.4 - var keepAlive = httpVersion != HttpVersion.Http10; - - var connection = headers.HeaderConnection; - var upgrade = false; - if (connection.Count > 0) - { - var connectionOptions = FrameHeaders.ParseConnection(connection); - - upgrade = (connectionOptions & ConnectionOptions.Upgrade) == ConnectionOptions.Upgrade; - keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive; - } - - var transferEncoding = headers.HeaderTransferEncoding; - if (transferEncoding.Count > 0) - { - var transferCoding = FrameHeaders.GetFinalTransferCoding(headers.HeaderTransferEncoding); - - // https://tools.ietf.org/html/rfc7230#section-3.3.3 - // If a Transfer-Encoding header field - // is present in a request and the chunked transfer coding is not - // the final encoding, the message body length cannot be determined - // reliably; the server MUST respond with the 400 (Bad Request) - // status code and then close the connection. - if (transferCoding != TransferCoding.Chunked) - { - context.ThrowRequestRejected(RequestRejectionReason.FinalTransferCodingNotChunked, transferEncoding.ToString()); - } - - if (upgrade) - { - context.ThrowRequestRejected(RequestRejectionReason.UpgradeRequestCannotHavePayload); - } - - return new ForChunkedEncoding(keepAlive, context); - } - - if (headers.ContentLength.HasValue) - { - var contentLength = headers.ContentLength.Value; - - if (contentLength == 0) - { - return keepAlive ? _zeroContentLengthKeepAlive : _zeroContentLengthClose; - } - else if (upgrade) - { - context.ThrowRequestRejected(RequestRejectionReason.UpgradeRequestCannotHavePayload); - } - - return new ForContentLength(keepAlive, contentLength, context); - } - - // Avoid slowing down most common case - if (!object.ReferenceEquals(context.Method, HttpMethods.Get)) - { - // If we got here, request contains no Content-Length or Transfer-Encoding header. - // Reject with 411 Length Required. - if (HttpMethods.IsPost(context.Method) || HttpMethods.IsPut(context.Method)) - { - var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10; - context.ThrowRequestRejected(requestRejectionReason, context.Method); - } - } - - if (upgrade) - { - return new ForUpgrade(context); - } - - return keepAlive ? _zeroContentLengthKeepAlive : _zeroContentLengthClose; - } - - private class ForUpgrade : MessageBody - { - public ForUpgrade(Frame context) - : base(context) - { - RequestUpgrade = true; - } - - protected override bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined) - { - Copy(readableBuffer, writableBuffer); - consumed = readableBuffer.End; - examined = readableBuffer.End; - return false; - } } private class ForZeroContentLength : MessageBody @@ -408,396 +139,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public override bool IsEmpty => true; - public override Task ReadAsync(ArraySegment buffer, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(0); - } + public override Task ReadAsync(ArraySegment buffer, CancellationToken cancellationToken = default(CancellationToken)) => Task.FromResult(0); - public override Task CopyToAsync(Stream destination, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.CompletedTask; - } + public override Task CopyToAsync(Stream destination, CancellationToken cancellationToken = default(CancellationToken)) => Task.CompletedTask; - public override Task ConsumeAsync(CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.CompletedTask; - } + public override Task ConsumeAsync() => Task.CompletedTask; - public override Task StopAsync() - { - return Task.CompletedTask; - } - } + public override Task StopAsync() => Task.CompletedTask; - private class ForContentLength : MessageBody - { - private readonly long _contentLength; - private long _inputLength; - - public ForContentLength(bool keepAlive, long contentLength, Frame context) - : base(context) - { - RequestKeepAlive = keepAlive; - _contentLength = contentLength; - _inputLength = _contentLength; - } - - protected override bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined) - { - if (_inputLength == 0) - { - throw new InvalidOperationException("Attempted to read from completed Content-Length request body."); - } - - var actual = (int)Math.Min(readableBuffer.Length, _inputLength); - _inputLength -= actual; - - consumed = readableBuffer.Move(readableBuffer.Start, actual); - examined = consumed; - - Copy(readableBuffer.Slice(0, actual), writableBuffer); - - return _inputLength == 0; - } - - protected override void OnReadStart() - { - if (_contentLength > _context.MaxRequestBodySize) - { - _context.ThrowRequestRejected(RequestRejectionReason.RequestBodyTooLarge); - } - } - } - - /// - /// http://tools.ietf.org/html/rfc2616#section-3.6.1 - /// - private class ForChunkedEncoding : MessageBody - { - // byte consts don't have a data type annotation so we pre-cast it - private const byte ByteCR = (byte)'\r'; - // "7FFFFFFF\r\n" is the largest chunk size that could be returned as an int. - private const int MaxChunkPrefixBytes = 10; - - private long _inputLength; - private long _consumedBytes; - - private Mode _mode = Mode.Prefix; - - public ForChunkedEncoding(bool keepAlive, Frame context) - : base(context) - { - RequestKeepAlive = keepAlive; - } - - protected override bool Read(ReadableBuffer readableBuffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined) - { - consumed = default(ReadCursor); - examined = default(ReadCursor); - - while (_mode < Mode.Trailer) - { - if (_mode == Mode.Prefix) - { - ParseChunkedPrefix(readableBuffer, out consumed, out examined); - - if (_mode == Mode.Prefix) - { - return false; - } - - readableBuffer = readableBuffer.Slice(consumed); - } - - if (_mode == Mode.Extension) - { - ParseExtension(readableBuffer, out consumed, out examined); - - if (_mode == Mode.Extension) - { - return false; - } - - readableBuffer = readableBuffer.Slice(consumed); - } - - if (_mode == Mode.Data) - { - ReadChunkedData(readableBuffer, writableBuffer, out consumed, out examined); - - if (_mode == Mode.Data) - { - return false; - } - - readableBuffer = readableBuffer.Slice(consumed); - } - - if (_mode == Mode.Suffix) - { - ParseChunkedSuffix(readableBuffer, out consumed, out examined); - - if (_mode == Mode.Suffix) - { - return false; - } - - readableBuffer = readableBuffer.Slice(consumed); - } - } - - // Chunks finished, parse trailers - if (_mode == Mode.Trailer) - { - ParseChunkedTrailer(readableBuffer, out consumed, out examined); - - if (_mode == Mode.Trailer) - { - return false; - } - - readableBuffer = readableBuffer.Slice(consumed); - } - - // _consumedBytes aren't tracked for trailer headers, since headers have seperate limits. - if (_mode == Mode.TrailerHeaders) - { - if (_context.TakeMessageHeaders(readableBuffer, out consumed, out examined)) - { - _mode = Mode.Complete; - } - } - - return _mode == Mode.Complete; - } - - private void AddAndCheckConsumedBytes(long consumedBytes) - { - _consumedBytes += consumedBytes; - - if (_consumedBytes > _context.MaxRequestBodySize) - { - _context.ThrowRequestRejected(RequestRejectionReason.RequestBodyTooLarge); - } - } - - private void ParseChunkedPrefix(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) - { - consumed = buffer.Start; - examined = buffer.Start; - var reader = new ReadableBufferReader(buffer); - var ch1 = reader.Take(); - var ch2 = reader.Take(); - - if (ch1 == -1 || ch2 == -1) - { - examined = reader.Cursor; - return; - } - - var chunkSize = CalculateChunkSize(ch1, 0); - ch1 = ch2; - - while (reader.ConsumedBytes < MaxChunkPrefixBytes) - { - if (ch1 == ';') - { - consumed = reader.Cursor; - examined = reader.Cursor; - - AddAndCheckConsumedBytes(reader.ConsumedBytes); - _inputLength = chunkSize; - _mode = Mode.Extension; - return; - } - - ch2 = reader.Take(); - if (ch2 == -1) - { - examined = reader.Cursor; - return; - } - - if (ch1 == '\r' && ch2 == '\n') - { - consumed = reader.Cursor; - examined = reader.Cursor; - - AddAndCheckConsumedBytes(reader.ConsumedBytes); - _inputLength = chunkSize; - _mode = chunkSize > 0 ? Mode.Data : Mode.Trailer; - return; - } - - chunkSize = CalculateChunkSize(ch1, chunkSize); - ch1 = ch2; - } - - // At this point, 10 bytes have been consumed which is enough to parse the max value "7FFFFFFF\r\n". - _context.ThrowRequestRejected(RequestRejectionReason.BadChunkSizeData); - } - - private void ParseExtension(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) - { - // Chunk-extensions not currently parsed - // Just drain the data - consumed = buffer.Start; - examined = buffer.Start; - - do - { - ReadCursor extensionCursor; - if (ReadCursorOperations.Seek(buffer.Start, buffer.End, out extensionCursor, ByteCR) == -1) - { - // End marker not found yet - consumed = buffer.End; - examined = buffer.End; - AddAndCheckConsumedBytes(buffer.Length); - return; - }; - - var charsToByteCRExclusive = buffer.Slice(0, extensionCursor).Length; - - var sufixBuffer = buffer.Slice(extensionCursor); - if (sufixBuffer.Length < 2) - { - consumed = extensionCursor; - examined = buffer.End; - AddAndCheckConsumedBytes(charsToByteCRExclusive); - return; - } - - sufixBuffer = sufixBuffer.Slice(0, 2); - var sufixSpan = sufixBuffer.ToSpan(); - - if (sufixSpan[1] == '\n') - { - // We consumed the \r\n at the end of the extension, so switch modes. - _mode = _inputLength > 0 ? Mode.Data : Mode.Trailer; - - consumed = sufixBuffer.End; - examined = sufixBuffer.End; - AddAndCheckConsumedBytes(charsToByteCRExclusive + 2); - } - else - { - // Don't consume suffixSpan[1] in case it is also a \r. - buffer = buffer.Slice(charsToByteCRExclusive + 1); - consumed = extensionCursor; - AddAndCheckConsumedBytes(charsToByteCRExclusive + 1); - } - } while (_mode == Mode.Extension); - } - - private void ReadChunkedData(ReadableBuffer buffer, WritableBuffer writableBuffer, out ReadCursor consumed, out ReadCursor examined) - { - var actual = Math.Min(buffer.Length, _inputLength); - consumed = buffer.Move(buffer.Start, actual); - examined = consumed; - - Copy(buffer.Slice(0, actual), writableBuffer); - - _inputLength -= actual; - AddAndCheckConsumedBytes(actual); - - if (_inputLength == 0) - { - _mode = Mode.Suffix; - } - } - - private void ParseChunkedSuffix(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) - { - consumed = buffer.Start; - examined = buffer.Start; - - if (buffer.Length < 2) - { - examined = buffer.End; - return; - } - - var suffixBuffer = buffer.Slice(0, 2); - var suffixSpan = suffixBuffer.ToSpan(); - if (suffixSpan[0] == '\r' && suffixSpan[1] == '\n') - { - consumed = suffixBuffer.End; - examined = suffixBuffer.End; - AddAndCheckConsumedBytes(2); - _mode = Mode.Prefix; - } - else - { - _context.ThrowRequestRejected(RequestRejectionReason.BadChunkSuffix); - } - } - - private void ParseChunkedTrailer(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined) - { - consumed = buffer.Start; - examined = buffer.Start; - - if (buffer.Length < 2) - { - examined = buffer.End; - return; - } - - var trailerBuffer = buffer.Slice(0, 2); - var trailerSpan = trailerBuffer.ToSpan(); - - if (trailerSpan[0] == '\r' && trailerSpan[1] == '\n') - { - consumed = trailerBuffer.End; - examined = trailerBuffer.End; - AddAndCheckConsumedBytes(2); - _mode = Mode.Complete; - } - else - { - _mode = Mode.TrailerHeaders; - } - } - - private int CalculateChunkSize(int extraHexDigit, int currentParsedSize) - { - try - { - checked - { - if (extraHexDigit >= '0' && extraHexDigit <= '9') - { - return currentParsedSize * 0x10 + (extraHexDigit - '0'); - } - else if (extraHexDigit >= 'A' && extraHexDigit <= 'F') - { - return currentParsedSize * 0x10 + (extraHexDigit - ('A' - 10)); - } - else if (extraHexDigit >= 'a' && extraHexDigit <= 'f') - { - return currentParsedSize * 0x10 + (extraHexDigit - ('a' - 10)); - } - } - } - catch (OverflowException ex) - { - throw new IOException(CoreStrings.BadRequest_BadChunkSizeData, ex); - } - - _context.ThrowRequestRejected(RequestRejectionReason.BadChunkSizeData); - return -1; // can't happen, but compiler complains - } - - private enum Mode - { - Prefix, - Extension, - Data, - Suffix, - Trailer, - TrailerHeaders, - Complete - }; + protected override Task OnConsumeAsync() => Task.CompletedTask; } } } diff --git a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs index 57e5a812af..494379a518 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs @@ -235,9 +235,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); } - if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream) && !stream.MessageBody.IsCompleted) + if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream) && !stream.HasReceivedEndStream) { - return stream.MessageBody.OnDataAsync(_incomingFrame.DataPayload, + return stream.OnDataAsync(_incomingFrame.DataPayload, endStream: (_incomingFrame.DataFlags & Http2DataFrameFlags.END_STREAM) == Http2DataFrameFlags.END_STREAM); } @@ -272,7 +272,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 StreamLifetimeHandler = this, FrameWriter = _frameWriter }); - _currentHeadersStream.ExpectBody = (_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == 0; + _currentHeadersStream.ExpectData = (_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == 0; _currentHeadersStream.Reset(); _streams[_incomingFrame.StreamId] = _currentHeadersStream; @@ -282,7 +282,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 if ((_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS) { _lastStreamId = _incomingFrame.StreamId; - _ = _currentHeadersStream.ProcessRequestAsync(); + _ = _currentHeadersStream.ProcessRequestsAsync(); _currentHeadersStream = null; } @@ -441,7 +441,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 if ((_incomingFrame.ContinuationFlags & Http2ContinuationFrameFlags.END_HEADERS) == Http2ContinuationFrameFlags.END_HEADERS) { _lastStreamId = _currentHeadersStream.StreamId; - _ = _currentHeadersStream.ProcessRequestAsync(); + _ = _currentHeadersStream.ProcessRequestsAsync(); _currentHeadersStream = null; } diff --git a/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs b/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs index 6623f489b8..569d1e298e 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs @@ -7,6 +7,7 @@ using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.Extensions.Logging; @@ -50,10 +51,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 return Task.CompletedTask; } - public Task WriteHeadersAsync(int streamId, int statusCode, IHeaderDictionary headers) + public void WriteResponseHeaders(int streamId, int statusCode, IHeaderDictionary headers) { - var tasks = new List(); - lock (_writeLock) { _outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); @@ -66,7 +65,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 _outgoingFrame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS; } - tasks.Add(WriteAsync(_outgoingFrame.Raw)); + Append(_outgoingFrame.Raw); while (!done) { @@ -80,10 +79,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 _outgoingFrame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS; } - tasks.Add(WriteAsync(_outgoingFrame.Raw)); + Append(_outgoingFrame.Raw); } - - return Task.WhenAll(tasks); } } @@ -168,6 +165,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } } + // Must be called with _writeLock + private void Append(ArraySegment data) + { + if (_completed) + { + return; + } + + var writeableBuffer = _outputWriter.Alloc(1); + writeableBuffer.Write(data); + writeableBuffer.Commit(); + } + + // Must be called with _writeLock private async Task WriteAsync(ArraySegment data, CancellationToken cancellationToken = default(CancellationToken)) { if (_completed) diff --git a/src/Kestrel.Core/Internal/Http2/Http2MessageBody.cs b/src/Kestrel.Core/Internal/Http2/Http2MessageBody.cs index 81ab3a5595..26013948dc 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2MessageBody.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2MessageBody.cs @@ -1,262 +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; -using System.IO; -using System.IO.Pipelines; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { - public abstract class Http2MessageBody : IMessageBody + public abstract class Http2MessageBody : MessageBody { - private static readonly Http2MessageBody _emptyMessageBody = new ForEmpty(); - private readonly Http2Stream _context; - private bool _send100Continue = true; - protected Http2MessageBody(Http2Stream context) + : base(context) { _context = context; } - public bool IsCompleted { get; protected set; } + protected override Task OnConsumeAsync() => Task.CompletedTask; - public virtual async Task OnDataAsync(ArraySegment data, bool endStream) - { - try - { - if (data.Count > 0) - { - var writableBuffer = _context.RequestBodyPipe.Writer.Alloc(1); - bool done; - - try - { - done = Read(data, writableBuffer); - } - finally - { - writableBuffer.Commit(); - } - - await writableBuffer.FlushAsync(); - } - - if (endStream) - { - IsCompleted = true; - _context.RequestBodyPipe.Writer.Complete(); - } - } - catch (Exception ex) - { - _context.RequestBodyPipe.Writer.Complete(ex); - } - } - - public virtual async Task ReadAsync(ArraySegment buffer, CancellationToken cancellationToken = default(CancellationToken)) - { - TryInit(); - - while (true) - { - var result = await _context.RequestBodyPipe.Reader.ReadAsync(); - var readableBuffer = result.Buffer; - var consumed = readableBuffer.End; - - try - { - if (!readableBuffer.IsEmpty) - { - // buffer.Count is int - var actual = (int)Math.Min(readableBuffer.Length, buffer.Count); - var slice = readableBuffer.Slice(0, actual); - consumed = readableBuffer.Move(readableBuffer.Start, actual); - slice.CopyTo(buffer); - return actual; - } - else if (result.IsCompleted) - { - return 0; - } - } - finally - { - _context.RequestBodyPipe.Reader.Advance(consumed); - } - } - } - - public virtual async Task CopyToAsync(Stream destination, CancellationToken cancellationToken = default(CancellationToken)) - { - TryInit(); - - while (true) - { - var result = await _context.RequestBodyPipe.Reader.ReadAsync(); - var readableBuffer = result.Buffer; - var consumed = readableBuffer.End; - - try - { - if (!readableBuffer.IsEmpty) - { - foreach (var memory in readableBuffer) - { - var array = memory.GetArray(); - await destination.WriteAsync(array.Array, array.Offset, array.Count, cancellationToken); - } - } - else if (result.IsCompleted) - { - return; - } - } - finally - { - _context.RequestBodyPipe.Reader.Advance(consumed); - } - } - } - - public virtual Task StopAsync() + public override Task StopAsync() { _context.RequestBodyPipe.Reader.Complete(); _context.RequestBodyPipe.Writer.Complete(); return Task.CompletedTask; } - protected void Copy(Span data, WritableBuffer writableBuffer) - { - writableBuffer.Write(data); - } - - private void TryProduceContinue() - { - if (_send100Continue) - { - _context.HttpStreamControl.ProduceContinue(); - _send100Continue = false; - } - } - - private void TryInit() - { - if (!_context.HasStartedConsumingRequestBody) - { - OnReadStart(); - _context.HasStartedConsumingRequestBody = true; - } - } - - protected virtual bool Read(Span readableBuffer, WritableBuffer writableBuffer) - { - throw new NotImplementedException(); - } - - protected virtual void OnReadStart() - { - } - - public static Http2MessageBody For( - FrameRequestHeaders headers, + public static MessageBody For( + HttpRequestHeaders headers, Http2Stream context) { - if (!context.ExpectBody) + if (!context.ExpectData) { - return _emptyMessageBody; + return MessageBody.ZeroContentLengthClose; } - if (headers.ContentLength.HasValue) - { - var contentLength = headers.ContentLength.Value; - - return new ForContentLength(contentLength, context); - } - - return new ForRemainingData(context); + return new ForHttp2(context); } - private class ForEmpty : Http2MessageBody + private class ForHttp2 : Http2MessageBody { - public ForEmpty() - : base(context: null) - { - IsCompleted = true; - } - - public override Task OnDataAsync(ArraySegment data, bool endStream) - { - throw new NotImplementedException(); - } - - public override Task ReadAsync(ArraySegment buffer, CancellationToken cancellationToken) - { - return Task.FromResult(0); - } - - public override Task CopyToAsync(Stream destination, CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - } - - private class ForRemainingData : Http2MessageBody - { - public ForRemainingData(Http2Stream context) + public ForHttp2(Http2Stream context) : base(context) { } - - protected override bool Read(Span data, WritableBuffer writableBuffer) - { - Copy(data, writableBuffer); - return false; - } - } - - private class ForContentLength : Http2MessageBody - { - private readonly long _contentLength; - private long _inputLength; - - public ForContentLength(long contentLength, Http2Stream context) - : base(context) - { - _contentLength = contentLength; - _inputLength = _contentLength; - } - - protected override bool Read(Span data, WritableBuffer writableBuffer) - { - if (_inputLength == 0) - { - throw new InvalidOperationException("Attempted to read from completed Content-Length request body."); - } - - if (data.Length > _inputLength) - { - _context.ThrowRequestRejected(RequestRejectionReason.RequestBodyExceedsContentLength); - } - - _inputLength -= data.Length; - - Copy(data, writableBuffer); - - return _inputLength == 0; - } - - protected override void OnReadStart() - { - if (_contentLength > _context.MaxRequestBodySize) - { - _context.ThrowRequestRejected(RequestRejectionReason.RequestBodyTooLarge); - } - } } } } diff --git a/src/Kestrel.Core/Internal/Http2/Http2OutputProducer.cs b/src/Kestrel.Core/Internal/Http2/Http2OutputProducer.cs new file mode 100644 index 0000000000..1b7d43b284 --- /dev/null +++ b/src/Kestrel.Core/Internal/Http2/Http2OutputProducer.cs @@ -0,0 +1,58 @@ +// 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; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 +{ + public class Http2OutputProducer : IHttpOutputProducer + { + private static readonly ArraySegment _emptyData = new ArraySegment(new byte[0]); + + private readonly int _streamId; + private readonly IHttp2FrameWriter _frameWriter; + + public Http2OutputProducer(int streamId, IHttp2FrameWriter frameWriter) + { + _streamId = streamId; + _frameWriter = frameWriter; + } + + public void Dispose() + { + } + + public void Abort(Exception error) + { + // TODO: RST_STREAM? + } + + public Task WriteAsync(Action callback, T state) + { + throw new NotImplementedException(); + } + + public Task FlushAsync(CancellationToken cancellationToken) => _frameWriter.FlushAsync(cancellationToken); + + public Task Write100ContinueAsync(CancellationToken cancellationToken) => _frameWriter.Write100ContinueAsync(_streamId); + + public Task WriteDataAsync(ArraySegment data, CancellationToken cancellationToken) + { + return _frameWriter.WriteDataAsync(_streamId, data, cancellationToken); + } + + public Task WriteStreamSuffixAsync(CancellationToken cancellationToken) + { + return _frameWriter.WriteDataAsync(_streamId, _emptyData, endStream: true, cancellationToken: cancellationToken); + } + + public void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpResponseHeaders responseHeaders) + { + _frameWriter.WriteResponseHeaders(_streamId, statusCode, responseHeaders); + } + } +} diff --git a/src/Kestrel.Core/Internal/Http2/Http2Stream.FeatureCollection.cs b/src/Kestrel.Core/Internal/Http2/Http2Stream.FeatureCollection.cs index c5df791ce8..782a8ddf51 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Stream.FeatureCollection.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Stream.FeatureCollection.cs @@ -1,289 +1,12 @@ // 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.IO; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; -using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { - public partial class Http2Stream : IFeatureCollection, - IHttpRequestFeature, - IHttpResponseFeature, - IHttpUpgradeFeature, - IHttpConnectionFeature, - IHttpRequestLifetimeFeature, - IHttpRequestIdentifierFeature, - IHttpBodyControlFeature, - IHttpMaxRequestBodySizeFeature, - IHttpMinRequestBodyDataRateFeature, - IHttpMinResponseDataRateFeature, - IHttp2StreamIdFeature + public partial class Http2Stream : IHttp2StreamIdFeature { - // NOTE: When feature interfaces are added to or removed from this Frame class implementation, - // then the list of `implementedFeatures` in the generated code project MUST also be updated. - // See also: tools/Microsoft.AspNetCore.Server.Kestrel.GeneratedCode/FrameFeatureCollection.cs - - private int _featureRevision; - - private List> MaybeExtra; - - public void ResetFeatureCollection() - { - FastReset(); - MaybeExtra?.Clear(); - _featureRevision++; - } - - private object ExtraFeatureGet(Type key) - { - if (MaybeExtra == null) - { - return null; - } - for (var i = 0; i < MaybeExtra.Count; i++) - { - var kv = MaybeExtra[i]; - if (kv.Key == key) - { - return kv.Value; - } - } - return null; - } - - private void ExtraFeatureSet(Type key, object value) - { - if (MaybeExtra == null) - { - MaybeExtra = new List>(2); - } - - for (var i = 0; i < MaybeExtra.Count; i++) - { - if (MaybeExtra[i].Key == key) - { - MaybeExtra[i] = new KeyValuePair(key, value); - return; - } - } - MaybeExtra.Add(new KeyValuePair(key, value)); - } - - string IHttpRequestFeature.Protocol - { - get => HttpVersion; - set => throw new InvalidOperationException(); - } - - string IHttpRequestFeature.Scheme - { - get => Scheme ?? "http"; - set => Scheme = value; - } - - string IHttpRequestFeature.Method - { - get => Method; - set => Method = value; - } - - string IHttpRequestFeature.PathBase - { - get => PathBase ?? ""; - set => PathBase = value; - } - - string IHttpRequestFeature.Path - { - get => Path; - set => Path = value; - } - - string IHttpRequestFeature.QueryString - { - get => QueryString; - set => QueryString = value; - } - - string IHttpRequestFeature.RawTarget - { - get => RawTarget; - set => RawTarget = value; - } - - IHeaderDictionary IHttpRequestFeature.Headers - { - get => RequestHeaders; - set => RequestHeaders = value; - } - - Stream IHttpRequestFeature.Body - { - get => RequestBody; - set => RequestBody = value; - } - - int IHttpResponseFeature.StatusCode - { - get => StatusCode; - set => StatusCode = value; - } - - string IHttpResponseFeature.ReasonPhrase - { - get => ReasonPhrase; - set => ReasonPhrase = value; - } - - IHeaderDictionary IHttpResponseFeature.Headers - { - get => ResponseHeaders; - set => ResponseHeaders = value; - } - - Stream IHttpResponseFeature.Body - { - get => ResponseBody; - set => ResponseBody = value; - } - - CancellationToken IHttpRequestLifetimeFeature.RequestAborted - { - get => RequestAborted; - set => RequestAborted = value; - } - - bool IHttpResponseFeature.HasStarted => HasResponseStarted; - - bool IHttpUpgradeFeature.IsUpgradableRequest => false; - - bool IFeatureCollection.IsReadOnly => false; - - int IFeatureCollection.Revision => _featureRevision; - - IPAddress IHttpConnectionFeature.RemoteIpAddress - { - get => RemoteIpAddress; - set => RemoteIpAddress = value; - } - - IPAddress IHttpConnectionFeature.LocalIpAddress - { - get => LocalIpAddress; - set => LocalIpAddress = value; - } - - int IHttpConnectionFeature.RemotePort - { - get => RemotePort; - set => RemotePort = value; - } - - int IHttpConnectionFeature.LocalPort - { - get => LocalPort; - set => LocalPort = value; - } - - string IHttpConnectionFeature.ConnectionId - { - get => ConnectionIdFeature; - set => ConnectionIdFeature = value; - } - - string IHttpRequestIdentifierFeature.TraceIdentifier - { - get => TraceIdentifier; - set => TraceIdentifier = value; - } - - bool IHttpBodyControlFeature.AllowSynchronousIO - { - get => AllowSynchronousIO; - set => AllowSynchronousIO = value; - } - - bool IHttpMaxRequestBodySizeFeature.IsReadOnly => HasStartedConsumingRequestBody; - - long? IHttpMaxRequestBodySizeFeature.MaxRequestBodySize - { - get => MaxRequestBodySize; - set - { - if (HasStartedConsumingRequestBody) - { - throw new InvalidOperationException(CoreStrings.MaxRequestBodySizeCannotBeModifiedAfterRead); - } - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.NonNegativeNumberOrNullRequired); - } - - MaxRequestBodySize = value; - } - } - - MinDataRate IHttpMinRequestBodyDataRateFeature.MinDataRate - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - MinDataRate IHttpMinResponseDataRateFeature.MinDataRate - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - object IFeatureCollection.this[Type key] - { - get => FastFeatureGet(key) ?? ConnectionFeatures?[key]; - set => FastFeatureSet(key, value); - } - - TFeature IFeatureCollection.Get() - { - return (TFeature)(FastFeatureGet(typeof(TFeature)) ?? ConnectionFeatures?[typeof(TFeature)]); - } - - void IFeatureCollection.Set(TFeature instance) - { - FastFeatureSet(typeof(TFeature), instance); - } - - void IHttpResponseFeature.OnStarting(Func callback, object state) - { - OnStarting(callback, state); - } - - void IHttpResponseFeature.OnCompleted(Func callback, object state) - { - OnCompleted(callback, state); - } - - Task IHttpUpgradeFeature.UpgradeAsync() - { - throw new NotImplementedException(); - } - - IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); - - void IHttpRequestLifetimeFeature.Abort() - { - Abort(error: null); - } - - int IHttp2StreamIdFeature.StreamId => StreamId; + int IHttp2StreamIdFeature.StreamId => _context.StreamId; } } diff --git a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs index ea3fc33144..71457fae4c 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs @@ -2,1024 +2,103 @@ // 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.Diagnostics; -using System.IO; using System.IO.Pipelines; -using System.Linq; -using System.Net; -using System.Text; -using System.Text.Encodings.Web.Utf8; -using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Primitives; - -// ReSharper disable AccessToModifiedClosure namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { - public abstract partial class Http2Stream : IFrameControl + public abstract partial class Http2Stream : HttpProtocol { - private const byte ByteAsterisk = (byte)'*'; - private const byte ByteForwardSlash = (byte)'/'; - private const byte BytePercentage = (byte)'%'; - - private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: " + Constants.ServerName); - - private const string EmptyPath = "/"; - private const string Asterisk = "*"; - - private readonly object _onStartingSync = new Object(); - private readonly object _onCompletedSync = new Object(); - - private Http2StreamContext _context; - private Http2Streams _streams; - - protected Stack, object>> _onStarting; - protected Stack, object>> _onCompleted; - - protected int _requestAborted; - private CancellationTokenSource _abortedCts; - private CancellationToken? _manuallySetRequestAbortToken; - - protected RequestProcessingStatus _requestProcessingStatus; - private bool _canHaveBody; - protected Exception _applicationException; - private BadHttpRequestException _requestRejectedException; - - private string _requestId; - - protected long _responseBytesWritten; - - private HttpRequestTarget _requestTargetForm = HttpRequestTarget.Unknown; - private Uri _absoluteRequestTarget; - private string _scheme = null; + private readonly Http2StreamContext _context; public Http2Stream(Http2StreamContext context) + : base(context) { _context = context; - HttpStreamControl = this; - ServerOptions = context.ServiceContext.ServerOptions; - RequestBodyPipe = CreateRequestBodyPipe(); + + Output = new Http2OutputProducer(StreamId, _context.FrameWriter); } - public IFrameControl HttpStreamControl { get; set; } - - public Http2MessageBody MessageBody { get; protected set; } - public IPipe RequestBodyPipe { get; } - - protected string ConnectionId => _context.ConnectionId; public int StreamId => _context.StreamId; - public ServiceContext ServiceContext => _context.ServiceContext; - // Hold direct reference to ServerOptions since this is used very often in the request processing path - private KestrelServerOptions ServerOptions { get; } + public bool HasReceivedEndStream { get; private set; } - public IFeatureCollection ConnectionFeatures { get; set; } protected IHttp2StreamLifetimeHandler StreamLifetimeHandler => _context.StreamLifetimeHandler; - public IHttp2FrameWriter Output => _context.FrameWriter; - protected IKestrelTrace Log => ServiceContext.Log; - private DateHeaderValueManager DateHeaderValueManager => ServiceContext.DateHeaderValueManager; + public bool ExpectData { get; set; } - private IPEndPoint LocalEndPoint => _context.LocalEndPoint; - private IPEndPoint RemoteEndPoint => _context.RemoteEndPoint; + public override bool IsUpgradableRequest => false; - public string ConnectionIdFeature { get; set; } - public bool HasStartedConsumingRequestBody { get; set; } - public long? MaxRequestBodySize { get; set; } - public bool AllowSynchronousIO { get; set; } - - public bool ExpectBody { get; set; } - - /// - /// The request id. - /// - public string TraceIdentifier + protected override void OnReset() { - set => _requestId = value; - get - { - // don't generate an ID until it is requested - if (_requestId == null) - { - _requestId = StringUtilities.ConcatAsHexSuffix(ConnectionId, ':', (uint)StreamId); - } - return _requestId; - } + FastFeatureSet(typeof(IHttp2StreamIdFeature), this); } - public IPAddress RemoteIpAddress { get; set; } - public int RemotePort { get; set; } - public IPAddress LocalIpAddress { get; set; } - public int LocalPort { get; set; } - public string Scheme { get; set; } - public string Method { get; set; } - public string PathBase { get; set; } - public string Path { get; set; } - public string QueryString { get; set; } - public string RawTarget { get; set; } - - public string HttpVersion => "HTTP/2"; - - public IHeaderDictionary RequestHeaders { get; set; } - public Stream RequestBody { get; set; } - - private int _statusCode; - public int StatusCode + protected override void OnRequestProcessingEnded() { - get => _statusCode; - set - { - if (HasResponseStarted) - { - ThrowResponseAlreadyStartedException(nameof(StatusCode)); - } - - _statusCode = value; - } + StreamLifetimeHandler.OnStreamCompleted(StreamId); } - private string _reasonPhrase; + protected override string CreateRequestId() + => StringUtilities.ConcatAsHexSuffix(ConnectionId, ':', (uint)StreamId); - public string ReasonPhrase + protected override MessageBody CreateMessageBody() + => Http2MessageBody.For(HttpRequestHeaders, this); + + protected override Task ParseRequestAsync() { - get => _reasonPhrase; + Method = RequestHeaders[":method"]; + Scheme = RequestHeaders[":scheme"]; + _httpVersion = Http.HttpVersion.Http2; - set - { - if (HasResponseStarted) - { - ThrowResponseAlreadyStartedException(nameof(ReasonPhrase)); - } + var path = RequestHeaders[":path"].ToString(); + var queryIndex = path.IndexOf('?'); - _reasonPhrase = value; - } + Path = queryIndex == -1 ? path : path.Substring(0, queryIndex); + QueryString = queryIndex == -1 ? string.Empty : path.Substring(queryIndex); + RawTarget = path; + + RequestHeaders["Host"] = RequestHeaders[":authority"]; + + return Task.FromResult(true); } - public IHeaderDictionary ResponseHeaders { get; set; } - public Stream ResponseBody { get; set; } - - public CancellationToken RequestAborted + public async Task OnDataAsync(ArraySegment data, bool endStream) { - get - { - // If a request abort token was previously explicitly set, return it. - if (_manuallySetRequestAbortToken.HasValue) - { - return _manuallySetRequestAbortToken.Value; - } - // Otherwise, get the abort CTS. If we have one, which would mean that someone previously - // asked for the RequestAborted token, simply return its token. If we don't, - // check to see whether we've already aborted, in which case just return an - // already canceled token. Finally, force a source into existence if we still - // don't have one, and return its token. - var cts = _abortedCts; - return - cts != null ? cts.Token : - (Volatile.Read(ref _requestAborted) == 1) ? new CancellationToken(true) : - RequestAbortedSource.Token; - } - set - { - // Set an abort token, overriding one we create internally. This setter and associated - // field exist purely to support IHttpRequestLifetimeFeature.set_RequestAborted. - _manuallySetRequestAbortToken = value; - } - } + // TODO: content-length accounting + // TODO: flow-control - private CancellationTokenSource RequestAbortedSource - { - get - { - // Get the abort token, lazily-initializing it if necessary. - // Make sure it's canceled if an abort request already came in. - - // EnsureInitialized can return null since _abortedCts is reset to null - // after it's already been initialized to a non-null value. - // If EnsureInitialized does return null, this property was accessed between - // requests so it's safe to return an ephemeral CancellationTokenSource. - var cts = LazyInitializer.EnsureInitialized(ref _abortedCts, () => new CancellationTokenSource()) - ?? new CancellationTokenSource(); - - if (Volatile.Read(ref _requestAborted) == 1) - { - cts.Cancel(); - } - return cts; - } - } - - public bool HasResponseStarted => _requestProcessingStatus == RequestProcessingStatus.ResponseStarted; - - protected FrameRequestHeaders FrameRequestHeaders { get; } = new FrameRequestHeaders(); - - protected FrameResponseHeaders FrameResponseHeaders { get; } = new FrameResponseHeaders(); - - public void InitializeStreams(Http2MessageBody messageBody) - { - if (_streams == null) - { - _streams = new Http2Streams(bodyControl: this, httpStreamControl: this); - } - - (RequestBody, ResponseBody) = _streams.Start(messageBody); - } - - public void PauseStreams() => _streams.Pause(); - - public void StopStreams() => _streams.Stop(); - - public void Reset() - { - _onStarting = null; - _onCompleted = null; - - _requestProcessingStatus = RequestProcessingStatus.RequestPending; - _applicationException = null; - - ResetFeatureCollection(); - - HasStartedConsumingRequestBody = false; - MaxRequestBodySize = ServerOptions.Limits.MaxRequestBodySize; - AllowSynchronousIO = ServerOptions.AllowSynchronousIO; - TraceIdentifier = null; - Method = null; - PathBase = null; - Path = null; - RawTarget = null; - _requestTargetForm = HttpRequestTarget.Unknown; - _absoluteRequestTarget = null; - QueryString = null; - _statusCode = StatusCodes.Status200OK; - _reasonPhrase = null; - - RemoteIpAddress = RemoteEndPoint?.Address; - RemotePort = RemoteEndPoint?.Port ?? 0; - - LocalIpAddress = LocalEndPoint?.Address; - LocalPort = LocalEndPoint?.Port ?? 0; - ConnectionIdFeature = ConnectionId; - - FrameRequestHeaders.Reset(); - FrameResponseHeaders.Reset(); - RequestHeaders = FrameRequestHeaders; - ResponseHeaders = FrameResponseHeaders; - - if (_scheme == null) - { - var tlsFeature = ConnectionFeatures?[typeof(ITlsConnectionFeature)]; - _scheme = tlsFeature != null ? "https" : "http"; - } - - Scheme = _scheme; - - _manuallySetRequestAbortToken = null; - _abortedCts = null; - - _responseBytesWritten = 0; - } - - private void CancelRequestAbortedToken() - { try { - RequestAbortedSource.Cancel(); - _abortedCts = null; + if (data.Count > 0) + { + var writableBuffer = RequestBodyPipe.Writer.Alloc(1); + + try + { + writableBuffer.Write(data); + } + finally + { + writableBuffer.Commit(); + } + + await writableBuffer.FlushAsync(); + } + + if (endStream) + { + HasReceivedEndStream = true; + RequestBodyPipe.Writer.Complete(); + } } catch (Exception ex) { - Log.ApplicationError(ConnectionId, TraceIdentifier, ex); + RequestBodyPipe.Writer.Complete(ex); } } - - public void Abort(Exception error) - { - if (Interlocked.Exchange(ref _requestAborted, 1) == 0) - { - _streams?.Abort(error); - - // Potentially calling user code. CancelRequestAbortedToken logs any exceptions. - ServiceContext.ThreadPool.UnsafeRun(state => ((Http2Stream)state).CancelRequestAbortedToken(), this); - } - } - - public abstract Task ProcessRequestAsync(); - - public void OnStarting(Func callback, object state) - { - lock (_onStartingSync) - { - if (HasResponseStarted) - { - ThrowResponseAlreadyStartedException(nameof(OnStarting)); - } - - if (_onStarting == null) - { - _onStarting = new Stack, object>>(); - } - _onStarting.Push(new KeyValuePair, object>(callback, state)); - } - } - - public void OnCompleted(Func callback, object state) - { - lock (_onCompletedSync) - { - if (_onCompleted == null) - { - _onCompleted = new Stack, object>>(); - } - _onCompleted.Push(new KeyValuePair, object>(callback, state)); - } - } - - protected async Task FireOnStarting() - { - Stack, object>> onStarting = null; - lock (_onStartingSync) - { - onStarting = _onStarting; - _onStarting = null; - } - if (onStarting != null) - { - try - { - foreach (var entry in onStarting) - { - await entry.Key.Invoke(entry.Value); - } - } - catch (Exception ex) - { - ReportApplicationError(ex); - } - } - } - - protected async Task FireOnCompleted() - { - Stack, object>> onCompleted = null; - lock (_onCompletedSync) - { - onCompleted = _onCompleted; - _onCompleted = null; - } - if (onCompleted != null) - { - foreach (var entry in onCompleted) - { - try - { - await entry.Key.Invoke(entry.Value); - } - catch (Exception ex) - { - ReportApplicationError(ex); - } - } - } - } - - public async Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken)) - { - await InitializeResponse(0); - await Output.FlushAsync(cancellationToken); - } - - public Task WriteAsync(ArraySegment data, CancellationToken cancellationToken = default(CancellationToken)) - { - if (!HasResponseStarted) - { - return WriteAsyncAwaited(data, cancellationToken); - } - - VerifyAndUpdateWrite(data.Count); - - if (_canHaveBody) - { - CheckLastWrite(); - return Output.WriteDataAsync(StreamId, data, cancellationToken: cancellationToken); - } - else - { - HandleNonBodyResponseWrite(); - return Task.CompletedTask; - } - } - - public async Task WriteAsyncAwaited(ArraySegment data, CancellationToken cancellationToken) - { - await InitializeResponseAwaited(data.Count); - - // WriteAsyncAwaited is only called for the first write to the body. - // Ensure headers are flushed if Write(Chunked)Async isn't called. - if (_canHaveBody) - { - CheckLastWrite(); - await Output.WriteDataAsync(StreamId, data, cancellationToken: cancellationToken); - } - else - { - HandleNonBodyResponseWrite(); - await FlushAsync(cancellationToken); - } - } - - private void VerifyAndUpdateWrite(int count) - { - var responseHeaders = FrameResponseHeaders; - - if (responseHeaders != null && - !responseHeaders.HasTransferEncoding && - responseHeaders.ContentLength.HasValue && - _responseBytesWritten + count > responseHeaders.ContentLength.Value) - { - throw new InvalidOperationException( - CoreStrings.FormatTooManyBytesWritten(_responseBytesWritten + count, responseHeaders.ContentLength.Value)); - } - - _responseBytesWritten += count; - } - - private void CheckLastWrite() - { - var responseHeaders = FrameResponseHeaders; - - // Prevent firing request aborted token if this is the last write, to avoid - // aborting the request if the app is still running when the client receives - // the final bytes of the response and gracefully closes the connection. - // - // Called after VerifyAndUpdateWrite(), so _responseBytesWritten has already been updated. - if (responseHeaders != null && - !responseHeaders.HasTransferEncoding && - responseHeaders.ContentLength.HasValue && - _responseBytesWritten == responseHeaders.ContentLength.Value) - { - _abortedCts = null; - } - } - - protected void VerifyResponseContentLength() - { - var responseHeaders = FrameResponseHeaders; - - if (!HttpMethods.IsHead(Method) && - !responseHeaders.HasTransferEncoding && - responseHeaders.ContentLength.HasValue && - _responseBytesWritten < responseHeaders.ContentLength.Value) - { - // We need to close the connection if any bytes were written since the client - // cannot be certain of how many bytes it will receive. - if (_responseBytesWritten > 0) - { - // TODO: HTTP/2 - } - - ReportApplicationError(new InvalidOperationException( - CoreStrings.FormatTooFewBytesWritten(_responseBytesWritten, responseHeaders.ContentLength.Value))); - } - } - - private static ArraySegment CreateAsciiByteArraySegment(string text) - { - var bytes = Encoding.ASCII.GetBytes(text); - return new ArraySegment(bytes); - } - - public void ProduceContinue() - { - if (HasResponseStarted) - { - return; - } - - if (RequestHeaders.TryGetValue("Expect", out var expect) && - (expect.FirstOrDefault() ?? "").Equals("100-continue", StringComparison.OrdinalIgnoreCase)) - { - Output.Write100ContinueAsync(StreamId).GetAwaiter().GetResult(); - } - } - - public Task InitializeResponse(int firstWriteByteCount) - { - if (HasResponseStarted) - { - return Task.CompletedTask; - } - - if (_onStarting != null) - { - return InitializeResponseAwaited(firstWriteByteCount); - } - - if (_applicationException != null) - { - ThrowResponseAbortedException(); - } - - VerifyAndUpdateWrite(firstWriteByteCount); - - return ProduceStart(appCompleted: false); - } - - private async Task InitializeResponseAwaited(int firstWriteByteCount) - { - await FireOnStarting(); - - if (_applicationException != null) - { - ThrowResponseAbortedException(); - } - - VerifyAndUpdateWrite(firstWriteByteCount); - - await ProduceStart(appCompleted: false); - } - - private Task ProduceStart(bool appCompleted) - { - if (HasResponseStarted) - { - return Task.CompletedTask; - } - - _requestProcessingStatus = RequestProcessingStatus.ResponseStarted; - - return CreateResponseHeader(appCompleted); - } - - protected Task TryProduceInvalidRequestResponse() - { - if (_requestRejectedException != null) - { - return ProduceEnd(); - } - - return Task.CompletedTask; - } - - protected Task ProduceEnd() - { - if (_requestRejectedException != null || _applicationException != null) - { - if (HasResponseStarted) - { - // We can no longer change the response, so we simply close the connection. - return Task.CompletedTask; - } - - // If the request was rejected, the error state has already been set by SetBadRequestState and - // that should take precedence. - if (_requestRejectedException != null) - { - SetErrorResponseException(_requestRejectedException); - } - else - { - // 500 Internal Server Error - SetErrorResponseHeaders(statusCode: StatusCodes.Status500InternalServerError); - } - } - - if (!HasResponseStarted) - { - return ProduceEndAwaited(); - } - - return WriteSuffix(); - } - - private async Task ProduceEndAwaited() - { - await ProduceStart(appCompleted: true); - - // Force flush - await Output.FlushAsync(default(CancellationToken)); - - await WriteSuffix(); - } - - private Task WriteSuffix() - { - if (HttpMethods.IsHead(Method) && _responseBytesWritten > 0) - { - Log.ConnectionHeadResponseBodyWrite(ConnectionId, _responseBytesWritten); - } - - return Output.WriteDataAsync(StreamId, Span.Empty, endStream: true, cancellationToken: default(CancellationToken)); - } - - private Task CreateResponseHeader(bool appCompleted) - { - var responseHeaders = FrameResponseHeaders; - var hasConnection = responseHeaders.HasConnection; - var connectionOptions = FrameHeaders.ParseConnection(responseHeaders.HeaderConnection); - var hasTransferEncoding = responseHeaders.HasTransferEncoding; - var transferCoding = FrameHeaders.GetFinalTransferCoding(responseHeaders.HeaderTransferEncoding); - - // https://tools.ietf.org/html/rfc7230#section-3.3.1 - // If any transfer coding other than - // chunked is applied to a response payload body, the sender MUST either - // apply chunked as the final transfer coding or terminate the message - // by closing the connection. - if (hasTransferEncoding && transferCoding == TransferCoding.Chunked) - { - // TODO: this is an error in HTTP/2 - } - - // Set whether response can have body - _canHaveBody = StatusCanHaveBody(StatusCode) && Method != "HEAD"; - - // Don't set the Content-Length or Transfer-Encoding headers - // automatically for HEAD requests or 204, 205, 304 responses. - if (_canHaveBody) - { - if (appCompleted) - { - // Since the app has completed and we are only now generating - // the headers we can safely set the Content-Length to 0. - responseHeaders.ContentLength = 0; - } - } - else if (hasTransferEncoding) - { - RejectNonBodyTransferEncodingResponse(appCompleted); - } - - responseHeaders.SetReadOnly(); - - if (ServerOptions.AddServerHeader && !responseHeaders.HasServer) - { - responseHeaders.SetRawServer(Constants.ServerName, _bytesServer); - } - - if (!responseHeaders.HasDate) - { - var dateHeaderValues = DateHeaderValueManager.GetDateHeaderValues(); - responseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes); - } - - return Output.WriteHeadersAsync(StreamId, StatusCode, responseHeaders); - } - - public bool StatusCanHaveBody(int statusCode) - { - // List of status codes taken from Microsoft.Net.Http.Server.Response - return statusCode != StatusCodes.Status204NoContent && - statusCode != StatusCodes.Status205ResetContent && - statusCode != StatusCodes.Status304NotModified; - } - - private void ThrowResponseAlreadyStartedException(string value) - { - throw new InvalidOperationException(CoreStrings.FormatParameterReadOnlyAfterResponseStarted(value)); - } - - private void RejectNonBodyTransferEncodingResponse(bool appCompleted) - { - var ex = new InvalidOperationException(CoreStrings.FormatHeaderNotAllowedOnResponse("Transfer-Encoding", StatusCode)); - if (!appCompleted) - { - // Back out of header creation surface exeception in user code - _requestProcessingStatus = RequestProcessingStatus.AppStarted; - throw ex; - } - else - { - ReportApplicationError(ex); - - // 500 Internal Server Error - SetErrorResponseHeaders(statusCode: StatusCodes.Status500InternalServerError); - } - } - - private void SetErrorResponseException(BadHttpRequestException ex) - { - SetErrorResponseHeaders(ex.StatusCode); - - if (!StringValues.IsNullOrEmpty(ex.AllowedHeader)) - { - FrameResponseHeaders.HeaderAllow = ex.AllowedHeader; - } - } - - private void SetErrorResponseHeaders(int statusCode) - { - Debug.Assert(!HasResponseStarted, $"{nameof(SetErrorResponseHeaders)} called after response had already started."); - - StatusCode = statusCode; - ReasonPhrase = null; - - var responseHeaders = FrameResponseHeaders; - responseHeaders.Reset(); - var dateHeaderValues = DateHeaderValueManager.GetDateHeaderValues(); - - responseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes); - - responseHeaders.ContentLength = 0; - - if (ServerOptions.AddServerHeader) - { - responseHeaders.SetRawServer(Constants.ServerName, _bytesServer); - } - } - - public void HandleNonBodyResponseWrite() - { - // Writes to HEAD response are ignored and logged at the end of the request - if (Method != "HEAD") - { - // Throw Exception for 204, 205, 304 responses. - throw new InvalidOperationException(CoreStrings.FormatWritingToResponseBodyNotSupported(StatusCode)); - } - } - - private void ThrowResponseAbortedException() - { - throw new ObjectDisposedException(CoreStrings.UnhandledApplicationException, _applicationException); - } - - public void ThrowRequestRejected(RequestRejectionReason reason) - => throw BadHttpRequestException.GetException(reason); - - public void ThrowRequestRejected(RequestRejectionReason reason, string detail) - => throw BadHttpRequestException.GetException(reason, detail); - - private void ThrowRequestTargetRejected(Span target) - => throw GetInvalidRequestTargetException(target); - - private BadHttpRequestException GetInvalidRequestTargetException(Span target) - => BadHttpRequestException.GetException( - RequestRejectionReason.InvalidRequestTarget, - Log.IsEnabled(LogLevel.Information) - ? target.GetAsciiStringEscaped(Constants.MaxExceptionDetailSize) - : string.Empty); - - public void SetBadRequestState(RequestRejectionReason reason) - { - SetBadRequestState(BadHttpRequestException.GetException(reason)); - } - - public void SetBadRequestState(BadHttpRequestException ex) - { - Log.ConnectionBadRequest(ConnectionId, ex); - - if (!HasResponseStarted) - { - SetErrorResponseException(ex); - } - - _requestRejectedException = ex; - } - - protected void ReportApplicationError(Exception ex) - { - if (_applicationException == null) - { - _applicationException = ex; - } - else if (_applicationException is AggregateException) - { - _applicationException = new AggregateException(_applicationException, ex).Flatten(); - } - else - { - _applicationException = new AggregateException(_applicationException, ex); - } - - Log.ApplicationError(ConnectionId, TraceIdentifier, ex); - } - - private void OnOriginFormTarget(HttpMethod method, Http.HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) - { - Debug.Assert(target[0] == ByteForwardSlash, "Should only be called when path starts with /"); - - _requestTargetForm = HttpRequestTarget.OriginForm; - - // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11 - // Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8; - // then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs" - string requestUrlPath = null; - string rawTarget = null; - - try - { - // Read raw target before mutating memory. - rawTarget = target.GetAsciiStringNonNullCharacters(); - - if (pathEncoded) - { - // URI was encoded, unescape and then parse as UTF-8 - var pathLength = UrlEncoder.Decode(path, path); - - // Removing dot segments must be done after unescaping. From RFC 3986: - // - // URI producing applications should percent-encode data octets that - // correspond to characters in the reserved set unless these characters - // are specifically allowed by the URI scheme to represent data in that - // component. If a reserved character is found in a URI component and - // no delimiting role is known for that character, then it must be - // interpreted as representing the data octet corresponding to that - // character's encoding in US-ASCII. - // - // https://tools.ietf.org/html/rfc3986#section-2.2 - pathLength = PathNormalizer.RemoveDotSegments(path.Slice(0, pathLength)); - - requestUrlPath = GetUtf8String(path.Slice(0, pathLength)); - } - else - { - var pathLength = PathNormalizer.RemoveDotSegments(path); - - if (path.Length == pathLength && query.Length == 0) - { - // If no decoding was required, no dot segments were removed and - // there is no query, the request path is the same as the raw target - requestUrlPath = rawTarget; - } - else - { - requestUrlPath = path.Slice(0, pathLength).GetAsciiStringNonNullCharacters(); - } - } - } - catch (InvalidOperationException) - { - ThrowRequestTargetRejected(target); - } - - QueryString = query.GetAsciiStringNonNullCharacters(); - RawTarget = rawTarget; - Path = requestUrlPath; - } - - private void OnAuthorityFormTarget(HttpMethod method, Span target) - { - _requestTargetForm = HttpRequestTarget.AuthorityForm; - - // This is not complete validation. It is just a quick scan for invalid characters - // but doesn't check that the target fully matches the URI spec. - for (var i = 0; i < target.Length; i++) - { - var ch = target[i]; - if (!UriUtilities.IsValidAuthorityCharacter(ch)) - { - ThrowRequestTargetRejected(target); - } - } - - // The authority-form of request-target is only used for CONNECT - // requests (https://tools.ietf.org/html/rfc7231#section-4.3.6). - if (method != HttpMethod.Connect) - { - ThrowRequestRejected(RequestRejectionReason.ConnectMethodRequired); - } - - // When making a CONNECT request to establish a tunnel through one or - // more proxies, a client MUST send only the target URI's authority - // component (excluding any userinfo and its "@" delimiter) as the - // request-target.For example, - // - // CONNECT www.example.com:80 HTTP/1.1 - // - // Allowed characters in the 'host + port' section of authority. - // See https://tools.ietf.org/html/rfc3986#section-3.2 - RawTarget = target.GetAsciiStringNonNullCharacters(); - Path = string.Empty; - QueryString = string.Empty; - } - - private void OnAsteriskFormTarget(HttpMethod method) - { - _requestTargetForm = HttpRequestTarget.AsteriskForm; - - // The asterisk-form of request-target is only used for a server-wide - // OPTIONS request (https://tools.ietf.org/html/rfc7231#section-4.3.7). - if (method != HttpMethod.Options) - { - ThrowRequestRejected(RequestRejectionReason.OptionsMethodRequired); - } - - RawTarget = Asterisk; - Path = string.Empty; - QueryString = string.Empty; - } - - private void OnAbsoluteFormTarget(Span target, Span query) - { - _requestTargetForm = HttpRequestTarget.AbsoluteForm; - - // absolute-form - // https://tools.ietf.org/html/rfc7230#section-5.3.2 - - // This code should be the edge-case. - - // From the spec: - // a server MUST accept the absolute-form in requests, even though - // HTTP/1.1 clients will only send them in requests to proxies. - - RawTarget = target.GetAsciiStringNonNullCharacters(); - - // Validation of absolute URIs is slow, but clients - // should not be sending this form anyways, so perf optimization - // not high priority - - if (!Uri.TryCreate(RawTarget, UriKind.Absolute, out var uri)) - { - ThrowRequestTargetRejected(target); - } - - _absoluteRequestTarget = uri; - Path = uri.LocalPath; - // don't use uri.Query because we need the unescaped version - QueryString = query.GetAsciiStringNonNullCharacters(); - } - - private unsafe static string GetUtf8String(Span path) - { - // .NET 451 doesn't have pointer overloads for Encoding.GetString so we - // copy to an array - fixed (byte* pointer = &path.DangerousGetPinnableReference()) - { - return Encoding.UTF8.GetString(pointer, path.Length); - } - } - - public void OnHeader(Span name, Span value) - { - // TODO: move validation of header count and size to HPACK decoding - var valueString = value.GetAsciiStringNonNullCharacters(); - - FrameRequestHeaders.Append(name, valueString); - } - - protected void EnsureHostHeaderExists() - { - // https://tools.ietf.org/html/rfc7230#section-5.4 - // A server MUST respond with a 400 (Bad Request) status code to any - // HTTP/1.1 request message that lacks a Host header field and to any - // request message that contains more than one Host header field or a - // Host header field with an invalid field-value. - - var host = FrameRequestHeaders.HeaderHost; - if (host.Count <= 0) - { - ThrowRequestRejected(RequestRejectionReason.MissingHostHeader); - } - else if (host.Count > 1) - { - ThrowRequestRejected(RequestRejectionReason.MultipleHostHeaders); - } - else if (_requestTargetForm == HttpRequestTarget.AuthorityForm) - { - if (!host.Equals(RawTarget)) - { - ThrowRequestRejected(RequestRejectionReason.InvalidHostHeader, host.ToString()); - } - } - else if (_requestTargetForm == HttpRequestTarget.AbsoluteForm) - { - // If the target URI includes an authority component, then a - // client MUST send a field - value for Host that is identical to that - // authority component, excluding any userinfo subcomponent and its "@" - // delimiter. - - // System.Uri doesn't not tell us if the port was in the original string or not. - // When IsDefaultPort = true, we will allow Host: with or without the default port - var authorityAndPort = _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port; - if ((host != _absoluteRequestTarget.Authority || !_absoluteRequestTarget.IsDefaultPort) - && host != authorityAndPort) - { - ThrowRequestRejected(RequestRejectionReason.InvalidHostHeader, host.ToString()); - } - } - } - - private IPipe CreateRequestBodyPipe() - => _context.PipeFactory.Create(new PipeOptions - { - ReaderScheduler = ServiceContext.ThreadPool, - WriterScheduler = InlineScheduler.Default, - MaximumSizeHigh = 1, - MaximumSizeLow = 1 - }); - - private enum HttpRequestTarget - { - Unknown = -1, - // origin-form is the most common - OriginForm, - AbsoluteForm, - AuthorityForm, - AsteriskForm - } } } diff --git a/src/Kestrel.Core/Internal/Http2/Http2StreamContext.cs b/src/Kestrel.Core/Internal/Http2/Http2StreamContext.cs index af7174c754..68ea22533e 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2StreamContext.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2StreamContext.cs @@ -3,14 +3,17 @@ using System.IO.Pipelines; using System.Net; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { - public class Http2StreamContext + public class Http2StreamContext : IHttpProtocolContext { public string ConnectionId { get; set; } public int StreamId { get; set; } public ServiceContext ServiceContext { get; set; } + public IFeatureCollection ConnectionFeatures { get; set; } public PipeFactory PipeFactory { get; set; } public IPEndPoint RemoteEndPoint { get; set; } public IPEndPoint LocalEndPoint { get; set; } diff --git a/src/Kestrel.Core/Internal/Http2/Http2StreamOfT.cs b/src/Kestrel.Core/Internal/Http2/Http2StreamOfT.cs index 79b7493411..5f6c1e7d95 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2StreamOfT.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2StreamOfT.cs @@ -1,14 +1,8 @@ // 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; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Protocols; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; -using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { @@ -16,157 +10,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { private readonly IHttpApplication _application; + private TContext _httpContext; + public Http2Stream(IHttpApplication application, Http2StreamContext context) : base(context) { _application = application; } - public override async Task ProcessRequestAsync() - { - try - { - Method = RequestHeaders[":method"]; - Scheme = RequestHeaders[":scheme"]; + protected override void CreateHttpContext() => _httpContext = _application.CreateContext(this); - var path = RequestHeaders[":path"].ToString(); - var queryIndex = path.IndexOf('?'); + protected override void DisposeHttpContext() => _application.DisposeContext(_httpContext, _applicationException); - Path = queryIndex == -1 ? path : path.Substring(0, queryIndex); - QueryString = queryIndex == -1 ? string.Empty : path.Substring(queryIndex); - - RequestHeaders["Host"] = RequestHeaders[":authority"]; - - // TODO: figure out what the equivalent for HTTP/2 is - // EnsureHostHeaderExists(); - - MessageBody = Http2MessageBody.For(FrameRequestHeaders, this); - - InitializeStreams(MessageBody); - - var context = _application.CreateContext(this); - try - { - try - { - //KestrelEventSource.Log.RequestStart(this); - - await _application.ProcessRequestAsync(context); - - if (Volatile.Read(ref _requestAborted) == 0) - { - VerifyResponseContentLength(); - } - } - catch (Exception ex) - { - ReportApplicationError(ex); - - if (ex is BadHttpRequestException) - { - throw; - } - } - finally - { - //KestrelEventSource.Log.RequestStop(this); - - // Trigger OnStarting if it hasn't been called yet and the app hasn't - // already failed. If an OnStarting callback throws we can go through - // our normal error handling in ProduceEnd. - // https://github.com/aspnet/KestrelHttpServer/issues/43 - if (!HasResponseStarted && _applicationException == null && _onStarting != null) - { - await FireOnStarting(); - } - - PauseStreams(); - - if (_onCompleted != null) - { - await FireOnCompleted(); - } - } - - // If _requestAbort is set, the connection has already been closed. - if (Volatile.Read(ref _requestAborted) == 0) - { - await ProduceEnd(); - } - else if (!HasResponseStarted) - { - // If the request was aborted and no response was sent, there's no - // meaningful status code to log. - StatusCode = 0; - } - } - catch (BadHttpRequestException ex) - { - // Handle BadHttpRequestException thrown during app execution or remaining message body consumption. - // This has to be caught here so StatusCode is set properly before disposing the HttpContext - // (DisposeContext logs StatusCode). - SetBadRequestState(ex); - } - finally - { - _application.DisposeContext(context, _applicationException); - - // StopStreams should be called before the end of the "if (!_requestProcessingStopping)" block - // to ensure InitializeStreams has been called. - StopStreams(); - - if (HasStartedConsumingRequestBody) - { - RequestBodyPipe.Reader.Complete(); - - // Wait for MessageBody.PumpAsync() to call RequestBodyPipe.Writer.Complete(). - await MessageBody.StopAsync(); - - // At this point both the request body pipe reader and writer should be completed. - RequestBodyPipe.Reset(); - } - } - } - catch (BadHttpRequestException ex) - { - // Handle BadHttpRequestException thrown during request line or header parsing. - // SetBadRequestState logs the error. - SetBadRequestState(ex); - } - catch (ConnectionResetException ex) - { - // Don't log ECONNRESET errors made between requests. Browsers like IE will reset connections regularly. - if (_requestProcessingStatus != RequestProcessingStatus.RequestPending) - { - Log.RequestProcessingError(ConnectionId, ex); - } - } - catch (IOException ex) - { - Log.RequestProcessingError(ConnectionId, ex); - } - catch (Exception ex) - { - Log.LogWarning(0, ex, CoreStrings.RequestProcessingEndError); - } - finally - { - try - { - if (Volatile.Read(ref _requestAborted) == 0) - { - await TryProduceInvalidRequestResponse(); - } - } - catch (Exception ex) - { - Log.LogWarning(0, ex, CoreStrings.ConnectionShutdownError); - } - finally - { - StreamLifetimeHandler.OnStreamCompleted(StreamId); - } - } - } + protected override Task InvokeApplicationAsync() => _application.ProcessRequestAsync(_httpContext); } } diff --git a/src/Kestrel.Core/Internal/Http2/Http2Streams.cs b/src/Kestrel.Core/Internal/Http2/Http2Streams.cs deleted file mode 100644 index b433f7c22c..0000000000 --- a/src/Kestrel.Core/Internal/Http2/Http2Streams.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 -{ - internal class Http2Streams - { - private readonly FrameResponseStream _response; - private readonly FrameRequestStream _request; - - public Http2Streams(IHttpBodyControlFeature bodyControl, IFrameControl httpStreamControl) - { - _request = new FrameRequestStream(bodyControl); - _response = new FrameResponseStream(bodyControl, httpStreamControl); - } - - public (Stream request, Stream response) Start(Http2MessageBody body) - { - _request.StartAcceptingReads(body); - _response.StartAcceptingWrites(); - - return (_request, _response); - } - - public void Pause() - { - _request.PauseAcceptingReads(); - _response.PauseAcceptingWrites(); - } - - public void Stop() - { - _request.StopAcceptingReads(); - _response.StopAcceptingWrites(); - } - - public void Abort(Exception error) - { - _request.Abort(error); - _response.Abort(); - } - } -} diff --git a/src/Kestrel.Core/Internal/Http2/IHttp2FrameWriter.cs b/src/Kestrel.Core/Internal/Http2/IHttp2FrameWriter.cs index c4615dac51..21a8192a44 100644 --- a/src/Kestrel.Core/Internal/Http2/IHttp2FrameWriter.cs +++ b/src/Kestrel.Core/Internal/Http2/IHttp2FrameWriter.cs @@ -11,9 +11,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 public interface IHttp2FrameWriter { void Abort(Exception error); - Task FlushAsync(CancellationToken cancellationToken); + Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken)); Task Write100ContinueAsync(int streamId); - Task WriteHeadersAsync(int streamId, int statusCode, IHeaderDictionary headers); + void WriteResponseHeaders(int streamId, int statusCode, IHeaderDictionary headers); Task WriteDataAsync(int streamId, Span data, CancellationToken cancellationToken); Task WriteDataAsync(int streamId, Span data, bool endStream, CancellationToken cancellationToken); Task WriteRstStreamAsync(int streamId, Http2ErrorCode errorCode); diff --git a/src/Kestrel.Core/Internal/FrameConnection.cs b/src/Kestrel.Core/Internal/HttpConnection.cs similarity index 86% rename from src/Kestrel.Core/Internal/FrameConnection.cs rename to src/Kestrel.Core/Internal/HttpConnection.cs index 85c58e7dd7..e74b46323b 100644 --- a/src/Kestrel.Core/Internal/FrameConnection.cs +++ b/src/Kestrel.Core/Internal/HttpConnection.cs @@ -19,16 +19,16 @@ using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { - public class FrameConnection : ITimeoutControl, IConnectionTimeoutFeature + public class HttpConnection : ITimeoutControl, IConnectionTimeoutFeature { private const int Http2ConnectionNotStarted = 0; private const int Http2ConnectionStarted = 1; private const int Http2ConnectionClosed = 2; - private readonly FrameConnectionContext _context; + private readonly HttpConnectionContext _context; private IList _adaptedConnections; private readonly TaskCompletionSource _socketClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - private Frame _frame; + private Http1Connection _http1Connection; private Http2Connection _http2Connection; private volatile int _http2ConnectionState; @@ -48,13 +48,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal private Task _lifetimeTask; - public FrameConnection(FrameConnectionContext context) + public HttpConnection(HttpConnectionContext context) { _context = context; } // For testing - internal Frame Frame => _frame; + internal HttpProtocol Http1Connection => _http1Connection; internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton; // For testing @@ -112,8 +112,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal transport = adaptedPipeline; } - // _frame must be initialized before adding the connection to the connection manager - CreateFrame(httpApplication, transport, application); + // _http1Connection must be initialized before adding the connection to the connection manager + CreateHttp1Connection(httpApplication, transport, application); // _http2Connection must be initialized before yield control to the transport thread, // to prevent a race condition where _http2Connection.Abort() is called just as @@ -131,10 +131,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal // Do this before the first await so we don't yield control to the transport until we've // added the connection to the connection manager - _context.ServiceContext.ConnectionManager.AddConnection(_context.FrameConnectionId, this); + _context.ServiceContext.ConnectionManager.AddConnection(_context.HttpConnectionId, this); _lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks; - _frame.ConnectionFeatures.Set(this); + _http1Connection.ConnectionFeatures.Set(this); if (adaptedPipeline != null) { @@ -143,14 +143,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal adaptedPipelineTask = adaptedPipeline.RunAsync(stream); } - if (_frame.ConnectionFeatures.Get()?.ApplicationProtocol == "h2" && + if (_http1Connection.ConnectionFeatures.Get()?.ApplicationProtocol == "h2" && Interlocked.CompareExchange(ref _http2ConnectionState, Http2ConnectionStarted, Http2ConnectionNotStarted) == Http2ConnectionNotStarted) { await _http2Connection.ProcessAsync(httpApplication); } else { - await _frame.ProcessRequestsAsync(); + await _http1Connection.ProcessRequestsAsync(); } await adaptedPipelineTask; @@ -158,14 +158,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal } catch (Exception ex) { - Log.LogError(0, ex, $"Unexpected exception in {nameof(FrameConnection)}.{nameof(ProcessRequestsAsync)}."); + Log.LogError(0, ex, $"Unexpected exception in {nameof(HttpConnection)}.{nameof(ProcessRequestsAsync)}."); } finally { - _context.ServiceContext.ConnectionManager.RemoveConnection(_context.FrameConnectionId); + _context.ServiceContext.ConnectionManager.RemoveConnection(_context.HttpConnectionId); DisposeAdaptedConnections(); - if (_frame.WasUpgraded) + if (_http1Connection.IsUpgraded) { _context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne(); } @@ -174,9 +174,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal } } - internal void CreateFrame(IHttpApplication httpApplication, IPipeConnection transport, IPipeConnection application) + internal void CreateHttp1Connection(IHttpApplication httpApplication, IPipeConnection transport, IPipeConnection application) { - _frame = new Frame(httpApplication, new FrameContext + _http1Connection = new Http1Connection(httpApplication, new Http1ConnectionContext { ConnectionId = _context.ConnectionId, ConnectionFeatures = _context.ConnectionFeatures, @@ -199,7 +199,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public Task StopProcessingNextRequestAsync() { - Debug.Assert(_frame != null, $"{nameof(_frame)} is null"); + Debug.Assert(_http1Connection != null, $"{nameof(_http1Connection)} is null"); if (Interlocked.Exchange(ref _http2ConnectionState, Http2ConnectionClosed) == Http2ConnectionStarted) { @@ -207,7 +207,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal } else { - _frame.StopProcessingNextRequest(); + _http1Connection.StopProcessingNextRequest(); } return _lifetimeTask; @@ -215,7 +215,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public void Abort(Exception ex) { - Debug.Assert(_frame != null, $"{nameof(_frame)} is null"); + Debug.Assert(_http1Connection != null, $"{nameof(_http1Connection)} is null"); // Abort the connection (if not already aborted) if (Interlocked.Exchange(ref _http2ConnectionState, Http2ConnectionClosed) == Http2ConnectionStarted) @@ -224,7 +224,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal } else { - _frame.Abort(ex); + _http1Connection.Abort(ex); } } @@ -237,26 +237,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public void SendTimeoutResponse() { - Debug.Assert(_frame != null, $"{nameof(_frame)} is null"); + Debug.Assert(_http1Connection != null, $"{nameof(_http1Connection)} is null"); RequestTimedOut = true; - _frame.SendTimeoutResponse(); + _http1Connection.SendTimeoutResponse(); } public void StopProcessingNextRequest() { - Debug.Assert(_frame != null, $"{nameof(_frame)} is null"); + Debug.Assert(_http1Connection != null, $"{nameof(_http1Connection)} is null"); - _frame.StopProcessingNextRequest(); + _http1Connection.StopProcessingNextRequest(); } private async Task ApplyConnectionAdaptersAsync() { - Debug.Assert(_frame != null, $"{nameof(_frame)} is null"); + Debug.Assert(_http1Connection != null, $"{nameof(_http1Connection)} is null"); var connectionAdapters = _context.ConnectionAdapters; var stream = new RawStream(_context.Transport.Input, _context.Transport.Output); - var adapterContext = new ConnectionAdapterContext(_frame.ConnectionFeatures, stream); + var adapterContext = new ConnectionAdapterContext(_http1Connection.ConnectionFeatures, stream); _adaptedConnections = new List(connectionAdapters.Count); try @@ -265,7 +265,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { var adaptedConnection = await connectionAdapters[i].OnConnectionAsync(adapterContext); _adaptedConnections.Add(adaptedConnection); - adapterContext = new ConnectionAdapterContext(_frame.ConnectionFeatures, adaptedConnection.ConnectionStream); + adapterContext = new ConnectionAdapterContext(_http1Connection.ConnectionFeatures, adaptedConnection.ConnectionStream); } } catch (Exception ex) @@ -292,7 +292,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public void Tick(DateTimeOffset now) { - Debug.Assert(_frame != null, $"{nameof(_frame)} is null"); + Debug.Assert(_http1Connection != null, $"{nameof(_http1Connection)} is null"); var timestamp = now.Ticks; @@ -343,7 +343,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal if (_readTimingEnabled) { // Reference in local var to avoid torn reads in case the min rate is changed via IHttpMinRequestBodyDataRateFeature - var minRequestBodyDataRate = _frame.MinRequestBodyDataRate; + var minRequestBodyDataRate = _http1Connection.MinRequestBodyDataRate; _readTimingElapsedTicks += timestamp - _lastTimestamp; @@ -354,7 +354,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal if (rate < minRequestBodyDataRate.BytesPerSecond && !Debugger.IsAttached) { - Log.RequestBodyMininumDataRateNotSatisfied(_context.ConnectionId, _frame.TraceIdentifier, minRequestBodyDataRate.BytesPerSecond); + Log.RequestBodyMininumDataRateNotSatisfied(_context.ConnectionId, _http1Connection.TraceIdentifier, minRequestBodyDataRate.BytesPerSecond); SendTimeoutResponse(); } } @@ -378,7 +378,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal if (_writeTimingWrites > 0 && timestamp > _writeTimingTimeoutTimestamp && !Debugger.IsAttached) { RequestTimedOut = true; - Log.ResponseMininumDataRateNotSatisfied(_frame.ConnectionIdFeature, _frame.TraceIdentifier); + Log.ResponseMininumDataRateNotSatisfied(_http1Connection.ConnectionIdFeature, _http1Connection.TraceIdentifier); Abort(new TimeoutException()); } } @@ -455,7 +455,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { lock (_writeTimingLock) { - var minResponseDataRate = _frame.MinResponseDataRate; + var minResponseDataRate = _http1Connection.MinResponseDataRate; if (minResponseDataRate != null) { diff --git a/src/Kestrel.Core/Internal/FrameConnectionContext.cs b/src/Kestrel.Core/Internal/HttpConnectionContext.cs similarity index 91% rename from src/Kestrel.Core/Internal/FrameConnectionContext.cs rename to src/Kestrel.Core/Internal/HttpConnectionContext.cs index c8beb6ab45..9993070689 100644 --- a/src/Kestrel.Core/Internal/FrameConnectionContext.cs +++ b/src/Kestrel.Core/Internal/HttpConnectionContext.cs @@ -9,10 +9,10 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { - public class FrameConnectionContext + public class HttpConnectionContext { public string ConnectionId { get; set; } - public long FrameConnectionId { get; set; } + public long HttpConnectionId { get; set; } public ServiceContext ServiceContext { get; set; } public IFeatureCollection ConnectionFeatures { get; set; } public IList ConnectionAdapters { get; set; } diff --git a/src/Kestrel.Core/Internal/HttpConnectionMiddleware.cs b/src/Kestrel.Core/Internal/HttpConnectionMiddleware.cs index 62c51fb0f5..4b342f20c1 100644 --- a/src/Kestrel.Core/Internal/HttpConnectionMiddleware.cs +++ b/src/Kestrel.Core/Internal/HttpConnectionMiddleware.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { private static Action _completeTcs = CompleteTcs; - private static long _lastFrameConnectionId = long.MinValue; + private static long _lastHttpConnectionId = long.MinValue; private readonly IList _connectionAdapters; private readonly ServiceContext _serviceContext; @@ -36,12 +36,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal // This is a bit of a hack but it preserves the existing semantics var transportFeature = connectionContext.Features.Get(); - var frameConnectionId = Interlocked.Increment(ref _lastFrameConnectionId); + var httpConnectionId = Interlocked.Increment(ref _lastHttpConnectionId); - var frameConnectionContext = new FrameConnectionContext + var httpConnectionContext = new HttpConnectionContext { ConnectionId = connectionContext.ConnectionId, - FrameConnectionId = frameConnectionId, + HttpConnectionId = httpConnectionId, ServiceContext = _serviceContext, ConnectionFeatures = connectionContext.Features, PipeFactory = connectionContext.PipeFactory, @@ -56,16 +56,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { if (connectionFeature.LocalIpAddress != null) { - frameConnectionContext.LocalEndPoint = new IPEndPoint(connectionFeature.LocalIpAddress, connectionFeature.LocalPort); + httpConnectionContext.LocalEndPoint = new IPEndPoint(connectionFeature.LocalIpAddress, connectionFeature.LocalPort); } if (connectionFeature.RemoteIpAddress != null) { - frameConnectionContext.RemoteEndPoint = new IPEndPoint(connectionFeature.RemoteIpAddress, connectionFeature.RemotePort); + httpConnectionContext.RemoteEndPoint = new IPEndPoint(connectionFeature.RemoteIpAddress, connectionFeature.RemotePort); } } - var connection = new FrameConnection(frameConnectionContext); + var connection = new HttpConnection(httpConnectionContext); var processingTask = connection.StartRequestProcessing(_application); @@ -79,13 +79,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal inputTcs.Task.ContinueWith((task, state) => { - ((FrameConnection)state).Abort(task.Exception?.InnerException); + ((HttpConnection)state).Abort(task.Exception?.InnerException); }, connection, TaskContinuationOptions.ExecuteSynchronously); outputTcs.Task.ContinueWith((task, state) => { - ((FrameConnection)state).OnConnectionClosed(task.Exception?.InnerException); + ((HttpConnection)state).OnConnectionClosed(task.Exception?.InnerException); }, connection, TaskContinuationOptions.ExecuteSynchronously); diff --git a/src/Kestrel.Core/Internal/Infrastructure/FrameConnectionManager.cs b/src/Kestrel.Core/Internal/Infrastructure/HttpConnectionManager.cs similarity index 76% rename from src/Kestrel.Core/Internal/Infrastructure/FrameConnectionManager.cs rename to src/Kestrel.Core/Internal/Infrastructure/HttpConnectionManager.cs index dc4d969ad7..feb3a4cb29 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/FrameConnectionManager.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/HttpConnectionManager.cs @@ -6,17 +6,17 @@ using System.Collections.Concurrent; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { - public class FrameConnectionManager + public class HttpConnectionManager { - private readonly ConcurrentDictionary _connectionReferences = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _connectionReferences = new ConcurrentDictionary(); private readonly IKestrelTrace _trace; - public FrameConnectionManager(IKestrelTrace trace, long? upgradedConnectionLimit) + public HttpConnectionManager(IKestrelTrace trace, long? upgradedConnectionLimit) : this(trace, GetCounter(upgradedConnectionLimit)) { } - public FrameConnectionManager(IKestrelTrace trace, ResourceCounter upgradedConnections) + public HttpConnectionManager(IKestrelTrace trace, ResourceCounter upgradedConnections) { UpgradedConnectionCount = upgradedConnections; _trace = trace; @@ -27,9 +27,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure /// public ResourceCounter UpgradedConnectionCount { get; } - public void AddConnection(long id, FrameConnection connection) + public void AddConnection(long id, HttpConnection connection) { - if (!_connectionReferences.TryAdd(id, new FrameConnectionReference(connection))) + if (!_connectionReferences.TryAdd(id, new HttpConnectionReference(connection))) { throw new ArgumentException(nameof(id)); } @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } } - public void Walk(Action callback) + public void Walk(Action callback) { foreach (var kvp in _connectionReferences) { diff --git a/src/Kestrel.Core/Internal/Infrastructure/FrameConnectionManagerShutdownExtensions.cs b/src/Kestrel.Core/Internal/Infrastructure/HttpConnectionManagerShutdownExtensions.cs similarity index 91% rename from src/Kestrel.Core/Internal/Infrastructure/FrameConnectionManagerShutdownExtensions.cs rename to src/Kestrel.Core/Internal/Infrastructure/HttpConnectionManagerShutdownExtensions.cs index a0319b0db9..d8f6320ff9 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/FrameConnectionManagerShutdownExtensions.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/HttpConnectionManagerShutdownExtensions.cs @@ -9,9 +9,9 @@ using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { - public static class FrameConnectionManagerShutdownExtensions + public static class HttpConnectionManagerShutdownExtensions { - public static async Task CloseAllConnectionsAsync(this FrameConnectionManager connectionManager, CancellationToken token) + public static async Task CloseAllConnectionsAsync(this HttpConnectionManager connectionManager, CancellationToken token) { var closeTasks = new List(); @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure return await Task.WhenAny(allClosedTask, CancellationTokenAsTask(token)).ConfigureAwait(false) == allClosedTask; } - public static async Task AbortAllConnectionsAsync(this FrameConnectionManager connectionManager) + public static async Task AbortAllConnectionsAsync(this HttpConnectionManager connectionManager) { var abortTasks = new List(); var canceledException = new ConnectionAbortedException(); diff --git a/src/Kestrel.Core/Internal/Infrastructure/FrameConnectionReference.cs b/src/Kestrel.Core/Internal/Infrastructure/HttpConnectionReference.cs similarity index 59% rename from src/Kestrel.Core/Internal/Infrastructure/FrameConnectionReference.cs rename to src/Kestrel.Core/Internal/Infrastructure/HttpConnectionReference.cs index dfa3f8f43c..395b58a9c4 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/FrameConnectionReference.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/HttpConnectionReference.cs @@ -5,19 +5,19 @@ using System; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { - public class FrameConnectionReference + public class HttpConnectionReference { - private readonly WeakReference _weakReference; + private readonly WeakReference _weakReference; - public FrameConnectionReference(FrameConnection connection) + public HttpConnectionReference(HttpConnection connection) { - _weakReference = new WeakReference(connection); + _weakReference = new WeakReference(connection); ConnectionId = connection.ConnectionId; } public string ConnectionId { get; } - public bool TryGetConnection(out FrameConnection connection) + public bool TryGetConnection(out HttpConnection connection) { return _weakReference.TryGetTarget(out connection); } diff --git a/src/Kestrel.Core/Internal/Infrastructure/FrameHeartbeatManager.cs b/src/Kestrel.Core/Internal/Infrastructure/HttpHeartbeatManager.cs similarity index 65% rename from src/Kestrel.Core/Internal/Infrastructure/FrameHeartbeatManager.cs rename to src/Kestrel.Core/Internal/Infrastructure/HttpHeartbeatManager.cs index 2dff6a08ac..fba8e8e1a1 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/FrameHeartbeatManager.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/HttpHeartbeatManager.cs @@ -5,13 +5,13 @@ using System; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { - public class FrameHeartbeatManager : IHeartbeatHandler + public class HttpHeartbeatManager : IHeartbeatHandler { - private readonly FrameConnectionManager _connectionManager; - private readonly Action _walkCallback; + private readonly HttpConnectionManager _connectionManager; + private readonly Action _walkCallback; private DateTimeOffset _now; - public FrameHeartbeatManager(FrameConnectionManager connectionManager) + public HttpHeartbeatManager(HttpConnectionManager connectionManager) { _connectionManager = connectionManager; _walkCallback = WalkCallback; @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure _connectionManager.Walk(_walkCallback); } - private void WalkCallback(FrameConnection connection) + private void WalkCallback(HttpConnection connection) { connection.Tick(_now); } diff --git a/src/Kestrel.Core/Internal/Infrastructure/HttpUtilities.cs b/src/Kestrel.Core/Internal/Infrastructure/HttpUtilities.cs index 8d7e5518c5..7b08a068fd 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/HttpUtilities.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/HttpUtilities.cs @@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { public const string Http10Version = "HTTP/1.0"; public const string Http11Version = "HTTP/1.1"; + public const string Http2Version = "HTTP/2"; public const string HttpUriScheme = "http://"; public const string HttpsUriScheme = "https://"; diff --git a/src/Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs b/src/Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs index 4191141bbb..708dedaa89 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure // - Avoid renaming methods or parameters marked with EventAttribute. EventSource uses these to form the event object. [NonEvent] - public void ConnectionStart(FrameConnection connection) + public void ConnectionStart(HttpConnection connection) { // avoid allocating strings unless this event source is enabled if (IsEnabled()) @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } [NonEvent] - public void ConnectionStop(FrameConnection connection) + public void ConnectionStop(HttpConnection connection) { if (IsEnabled()) { @@ -82,12 +82,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } [NonEvent] - public void RequestStart(Frame frame) + public void RequestStart(HttpProtocol httpProtocol) { // avoid allocating the trace identifier unless logging is enabled if (IsEnabled()) { - RequestStart(frame.ConnectionIdFeature, frame.TraceIdentifier); + RequestStart(httpProtocol.ConnectionIdFeature, httpProtocol.TraceIdentifier); } } @@ -99,12 +99,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } [NonEvent] - public void RequestStop(Frame frame) + public void RequestStop(HttpProtocol httpProtocol) { // avoid allocating the trace identifier unless logging is enabled if (IsEnabled()) { - RequestStop(frame.ConnectionIdFeature, frame.TraceIdentifier); + RequestStop(httpProtocol.ConnectionIdFeature, httpProtocol.TraceIdentifier); } } diff --git a/src/Kestrel.Core/Internal/Infrastructure/Streams.cs b/src/Kestrel.Core/Internal/Infrastructure/Streams.cs index a7762c6990..21a82b4648 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/Streams.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/Streams.cs @@ -8,23 +8,23 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { - internal class Streams + public class Streams { private static readonly ThrowingWriteOnlyStream _throwingResponseStream = new ThrowingWriteOnlyStream(new InvalidOperationException(CoreStrings.ResponseStreamWasUpgraded)); - private readonly FrameResponseStream _response; - private readonly FrameRequestStream _request; + private readonly HttpResponseStream _response; + private readonly HttpRequestStream _request; private readonly WrappingStream _upgradeableResponse; - private readonly FrameRequestStream _emptyRequest; + private readonly HttpRequestStream _emptyRequest; private readonly Stream _upgradeStream; - public Streams(IHttpBodyControlFeature bodyControl, IFrameControl frameControl) + public Streams(IHttpBodyControlFeature bodyControl, IHttpResponseControl httpResponseControl) { - _request = new FrameRequestStream(bodyControl); - _emptyRequest = new FrameRequestStream(bodyControl); - _response = new FrameResponseStream(bodyControl, frameControl); + _request = new HttpRequestStream(bodyControl); + _emptyRequest = new HttpRequestStream(bodyControl); + _response = new HttpResponseStream(bodyControl, httpResponseControl); _upgradeableResponse = new WrappingStream(_response); - _upgradeStream = new FrameDuplexStream(_request, _response); + _upgradeStream = new HttpUpgradeStream(_request, _response); } public Stream Upgrade() diff --git a/src/Kestrel.Core/Internal/ServiceContext.cs b/src/Kestrel.Core/Internal/ServiceContext.cs index 72017e5d43..19d22a1272 100644 --- a/src/Kestrel.Core/Internal/ServiceContext.cs +++ b/src/Kestrel.Core/Internal/ServiceContext.cs @@ -13,13 +13,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public IThreadPool ThreadPool { get; set; } - public Func> HttpParserFactory { get; set; } + public Func> HttpParserFactory { get; set; } public ISystemClock SystemClock { get; set; } public DateHeaderValueManager DateHeaderValueManager { get; set; } - public FrameConnectionManager ConnectionManager { get; set; } + public HttpConnectionManager ConnectionManager { get; set; } public KestrelServerOptions ServerOptions { get; set; } } diff --git a/src/Kestrel.Core/KestrelServer.cs b/src/Kestrel.Core/KestrelServer.cs index 528ea80e7a..8182c1e6c9 100644 --- a/src/Kestrel.Core/KestrelServer.cs +++ b/src/Kestrel.Core/KestrelServer.cs @@ -44,9 +44,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core _transportFactory = transportFactory; ServiceContext = serviceContext; - var frameHeartbeatManager = new FrameHeartbeatManager(serviceContext.ConnectionManager); + var httpHeartbeatManager = new HttpHeartbeatManager(serviceContext.ConnectionManager); _heartbeat = new Heartbeat( - new IHeartbeatHandler[] { serviceContext.DateHeaderValueManager, frameHeartbeatManager }, + new IHeartbeatHandler[] { serviceContext.DateHeaderValueManager, httpHeartbeatManager }, serviceContext.SystemClock, Trace); Features = new FeatureCollection(); @@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core var serverOptions = options.Value ?? new KestrelServerOptions(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel"); var trace = new KestrelTrace(logger); - var connectionManager = new FrameConnectionManager( + var connectionManager = new HttpConnectionManager( trace, serverOptions.Limits.MaxConcurrentUpgradedConnections); @@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core return new ServiceContext { Log = trace, - HttpParserFactory = frameParser => new HttpParser(frameParser.Frame.ServiceContext.Log.IsEnabled(LogLevel.Information)), + HttpParserFactory = handler => new HttpParser(handler.Connection.ServiceContext.Log.IsEnabled(LogLevel.Information)), ThreadPool = threadPool, SystemClock = systemClock, DateHeaderValueManager = dateHeaderValueManager, @@ -111,7 +111,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core private IKestrelTrace Trace => ServiceContext.Log; - private FrameConnectionManager ConnectionManager => ServiceContext.ConnectionManager; + private HttpConnectionManager ConnectionManager => ServiceContext.ConnectionManager; public async Task StartAsync(IHttpApplication application, CancellationToken cancellationToken) { diff --git a/src/Kestrel.Https/Internal/HttpsConnectionAdapter.cs b/src/Kestrel.Https/Internal/HttpsConnectionAdapter.cs index e8dfc3ec68..290bb3beee 100644 --- a/src/Kestrel.Https/Internal/HttpsConnectionAdapter.cs +++ b/src/Kestrel.Https/Internal/HttpsConnectionAdapter.cs @@ -147,11 +147,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal { /* If the Extended Key Usage extension is included, then we check that the serverAuth usage is included. (http://oid-info.com/get/1.3.6.1.5.5.7.3.1) * If the Extended Key Usage extension is not included, then we assume the certificate is allowed for all usages. - * + * * See also https://blogs.msdn.microsoft.com/kaushal/2012/02/17/client-certificates-vs-server-certificates/ - * + * * From https://tools.ietf.org/html/rfc3280#section-4.2.1.13 "Certificate Extensions: Extended Key Usage" - * + * * If the (Extended Key Usage) extension is present, then the certificate MUST only be used * for one of the purposes indicated. If multiple purposes are * indicated the application need not recognize all purposes indicated, diff --git a/test/Kestrel.Core.Tests/FrameTests.cs b/test/Kestrel.Core.Tests/Http1ConnectionTests.cs similarity index 67% rename from test/Kestrel.Core.Tests/FrameTests.cs rename to test/Kestrel.Core.Tests/Http1ConnectionTests.cs index ed4db8e6bb..2cfd2ed2f8 100644 --- a/test/Kestrel.Core.Tests/FrameTests.cs +++ b/test/Kestrel.Core.Tests/Http1ConnectionTests.cs @@ -25,21 +25,21 @@ using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { - public class FrameTests : IDisposable + public class Http1ConnectionTests : IDisposable { private readonly IPipeConnection _transport; private readonly IPipeConnection _application; - private readonly TestFrame _frame; + private readonly TestHttp1Connection _http1Connection; private readonly ServiceContext _serviceContext; - private readonly FrameContext _frameContext; + private readonly Http1ConnectionContext _http1ConnectionContext; private readonly PipeFactory _pipelineFactory; private ReadCursor _consumed; private ReadCursor _examined; private Mock _timeoutControl; - private class TestFrame : Frame + private class TestHttp1Connection : Http1Connection { - public TestFrame(IHttpApplication application, FrameContext context) + public TestHttp1Connection(IHttpApplication application, Http1ConnectionContext context) : base(application, context) { } @@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } } - public FrameTests() + public Http1ConnectionTests() { _pipelineFactory = new PipeFactory(); var pair = _pipelineFactory.CreateConnectionPair(); @@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _serviceContext = new TestServiceContext(); _timeoutControl = new Mock(); - _frameContext = new FrameContext + _http1ConnectionContext = new Http1ConnectionContext { ServiceContext = _serviceContext, ConnectionFeatures = new FeatureCollection(), @@ -70,8 +70,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Transport = pair.Transport }; - _frame = new TestFrame(application: null, context: _frameContext); - _frame.Reset(); + _http1Connection = new TestHttp1Connection(application: null, context: _http1ConnectionContext); + _http1Connection.Reset(); } public void Dispose() @@ -90,12 +90,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { const string headerLine = "Header: value\r\n"; _serviceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize = headerLine.Length - 1; - _frame.Reset(); + _http1Connection.Reset(); await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n")); var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - var exception = Assert.Throws(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined)); + var exception = Assert.Throws(() => _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined)); _transport.Input.Advance(_consumed, _examined); Assert.Equal(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, exception.Message); @@ -111,7 +111,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n")); var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - var exception = Assert.Throws(() => _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined)); + var exception = Assert.Throws(() => _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined)); _transport.Input.Advance(_consumed, _examined); Assert.Equal(CoreStrings.BadRequest_TooManyHeaders, exception.Message); @@ -121,60 +121,60 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void ResetResetsScheme() { - _frame.Scheme = "https"; + _http1Connection.Scheme = "https"; // Act - _frame.Reset(); + _http1Connection.Reset(); // Assert - Assert.Equal("http", ((IFeatureCollection)_frame).Get().Scheme); + Assert.Equal("http", ((IFeatureCollection)_http1Connection).Get().Scheme); } [Fact] public void ResetResetsTraceIdentifier() { - _frame.TraceIdentifier = "xyz"; + _http1Connection.TraceIdentifier = "xyz"; - _frame.Reset(); + _http1Connection.Reset(); - var nextId = ((IFeatureCollection)_frame).Get().TraceIdentifier; + var nextId = ((IFeatureCollection)_http1Connection).Get().TraceIdentifier; Assert.NotEqual("xyz", nextId); - _frame.Reset(); - var secondId = ((IFeatureCollection)_frame).Get().TraceIdentifier; + _http1Connection.Reset(); + var secondId = ((IFeatureCollection)_http1Connection).Get().TraceIdentifier; Assert.NotEqual(nextId, secondId); } [Fact] public void ResetResetsMinRequestBodyDataRate() { - _frame.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: 1, gracePeriod: TimeSpan.MaxValue); + _http1Connection.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: 1, gracePeriod: TimeSpan.MaxValue); - _frame.Reset(); + _http1Connection.Reset(); - Assert.Same(_serviceContext.ServerOptions.Limits.MinRequestBodyDataRate, _frame.MinRequestBodyDataRate); + Assert.Same(_serviceContext.ServerOptions.Limits.MinRequestBodyDataRate, _http1Connection.MinRequestBodyDataRate); } [Fact] public void ResetResetsMinResponseDataRate() { - _frame.MinResponseDataRate = new MinDataRate(bytesPerSecond: 1, gracePeriod: TimeSpan.MaxValue); + _http1Connection.MinResponseDataRate = new MinDataRate(bytesPerSecond: 1, gracePeriod: TimeSpan.MaxValue); - _frame.Reset(); + _http1Connection.Reset(); - Assert.Same(_serviceContext.ServerOptions.Limits.MinResponseDataRate, _frame.MinResponseDataRate); + Assert.Same(_serviceContext.ServerOptions.Limits.MinResponseDataRate, _http1Connection.MinResponseDataRate); } [Fact] - public void TraceIdentifierCountsRequestsPerFrame() + public void TraceIdentifierCountsRequestsPerHttp1Connection() { - var connectionId = _frameContext.ConnectionId; - var feature = ((IFeatureCollection)_frame).Get(); + var connectionId = _http1ConnectionContext.ConnectionId; + var feature = ((IFeatureCollection)_http1Connection).Get(); // Reset() is called once in the test ctor var count = 1; void Reset() { - _frame.Reset(); + _http1Connection.Reset(); count++; } @@ -193,13 +193,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void TraceIdentifierGeneratesWhenNull() { - _frame.TraceIdentifier = null; - var id = _frame.TraceIdentifier; + _http1Connection.TraceIdentifier = null; + var id = _http1Connection.TraceIdentifier; Assert.NotNull(id); - Assert.Equal(id, _frame.TraceIdentifier); + Assert.Equal(id, _http1Connection.TraceIdentifier); - _frame.Reset(); - Assert.NotEqual(id, _frame.TraceIdentifier); + _http1Connection.Reset(); + Assert.NotEqual(id, _http1Connection.TraceIdentifier); } [Fact] @@ -216,127 +216,127 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n")); var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - var takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined); + var takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined); _transport.Input.Advance(_consumed, _examined); Assert.True(takeMessageHeaders); - Assert.Equal(1, _frame.RequestHeaders.Count); - Assert.Equal("value1", _frame.RequestHeaders["Header-1"]); + Assert.Equal(1, _http1Connection.RequestHeaders.Count); + Assert.Equal("value1", _http1Connection.RequestHeaders["Header-1"]); - _frame.Reset(); + _http1Connection.Reset(); await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n")); readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, out _consumed, out _examined); + takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined); _transport.Input.Advance(_consumed, _examined); Assert.True(takeMessageHeaders); - Assert.Equal(1, _frame.RequestHeaders.Count); - Assert.Equal("value2", _frame.RequestHeaders["Header-2"]); + Assert.Equal(1, _http1Connection.RequestHeaders.Count); + Assert.Equal("value2", _http1Connection.RequestHeaders["Header-2"]); } [Fact] public async Task ThrowsWhenStatusCodeIsSetAfterResponseStarted() { // Act - await _frame.WriteAsync(new ArraySegment(new byte[1])); + await _http1Connection.WriteAsync(new ArraySegment(new byte[1])); // Assert - Assert.True(_frame.HasResponseStarted); - Assert.Throws(() => ((IHttpResponseFeature)_frame).StatusCode = StatusCodes.Status404NotFound); + Assert.True(_http1Connection.HasResponseStarted); + Assert.Throws(() => ((IHttpResponseFeature)_http1Connection).StatusCode = StatusCodes.Status404NotFound); } [Fact] public async Task ThrowsWhenReasonPhraseIsSetAfterResponseStarted() { // Act - await _frame.WriteAsync(new ArraySegment(new byte[1])); + await _http1Connection.WriteAsync(new ArraySegment(new byte[1])); // Assert - Assert.True(_frame.HasResponseStarted); - Assert.Throws(() => ((IHttpResponseFeature)_frame).ReasonPhrase = "Reason phrase"); + Assert.True(_http1Connection.HasResponseStarted); + Assert.Throws(() => ((IHttpResponseFeature)_http1Connection).ReasonPhrase = "Reason phrase"); } [Fact] public async Task ThrowsWhenOnStartingIsSetAfterResponseStarted() { - await _frame.WriteAsync(new ArraySegment(new byte[1])); + await _http1Connection.WriteAsync(new ArraySegment(new byte[1])); // Act/Assert - Assert.True(_frame.HasResponseStarted); - Assert.Throws(() => ((IHttpResponseFeature)_frame).OnStarting(_ => Task.CompletedTask, null)); + Assert.True(_http1Connection.HasResponseStarted); + Assert.Throws(() => ((IHttpResponseFeature)_http1Connection).OnStarting(_ => Task.CompletedTask, null)); } [Theory] [MemberData(nameof(MinDataRateData))] public void ConfiguringIHttpMinRequestBodyDataRateFeatureSetsMinRequestBodyDataRate(MinDataRate minDataRate) { - ((IFeatureCollection)_frame).Get().MinDataRate = minDataRate; + ((IFeatureCollection)_http1Connection).Get().MinDataRate = minDataRate; - Assert.Same(minDataRate, _frame.MinRequestBodyDataRate); + Assert.Same(minDataRate, _http1Connection.MinRequestBodyDataRate); } [Theory] [MemberData(nameof(MinDataRateData))] public void ConfiguringIHttpMinResponseDataRateFeatureSetsMinResponseDataRate(MinDataRate minDataRate) { - ((IFeatureCollection)_frame).Get().MinDataRate = minDataRate; + ((IFeatureCollection)_http1Connection).Get().MinDataRate = minDataRate; - Assert.Same(minDataRate, _frame.MinResponseDataRate); + Assert.Same(minDataRate, _http1Connection.MinResponseDataRate); } [Fact] public void ResetResetsRequestHeaders() { // Arrange - var originalRequestHeaders = _frame.RequestHeaders; - _frame.RequestHeaders = new FrameRequestHeaders(); + var originalRequestHeaders = _http1Connection.RequestHeaders; + _http1Connection.RequestHeaders = new HttpRequestHeaders(); // Act - _frame.Reset(); + _http1Connection.Reset(); // Assert - Assert.Same(originalRequestHeaders, _frame.RequestHeaders); + Assert.Same(originalRequestHeaders, _http1Connection.RequestHeaders); } [Fact] public void ResetResetsResponseHeaders() { // Arrange - var originalResponseHeaders = _frame.ResponseHeaders; - _frame.ResponseHeaders = new FrameResponseHeaders(); + var originalResponseHeaders = _http1Connection.ResponseHeaders; + _http1Connection.ResponseHeaders = new HttpResponseHeaders(); // Act - _frame.Reset(); + _http1Connection.Reset(); // Assert - Assert.Same(originalResponseHeaders, _frame.ResponseHeaders); + Assert.Same(originalResponseHeaders, _http1Connection.ResponseHeaders); } [Fact] public void InitializeStreamsResetsStreams() { // Arrange - var messageBody = MessageBody.For(Kestrel.Core.Internal.Http.HttpVersion.Http11, (FrameRequestHeaders)_frame.RequestHeaders, _frame); - _frame.InitializeStreams(messageBody); + var messageBody = Http1MessageBody.For(Kestrel.Core.Internal.Http.HttpVersion.Http11, (HttpRequestHeaders)_http1Connection.RequestHeaders, _http1Connection); + _http1Connection.InitializeStreams(messageBody); - var originalRequestBody = _frame.RequestBody; - var originalResponseBody = _frame.ResponseBody; - _frame.RequestBody = new MemoryStream(); - _frame.ResponseBody = new MemoryStream(); + var originalRequestBody = _http1Connection.RequestBody; + var originalResponseBody = _http1Connection.ResponseBody; + _http1Connection.RequestBody = new MemoryStream(); + _http1Connection.ResponseBody = new MemoryStream(); // Act - _frame.InitializeStreams(messageBody); + _http1Connection.InitializeStreams(messageBody); // Assert - Assert.Same(originalRequestBody, _frame.RequestBody); - Assert.Same(originalResponseBody, _frame.ResponseBody); + Assert.Same(originalRequestBody, _http1Connection.RequestBody); + Assert.Same(originalResponseBody, _http1Connection.ResponseBody); } [Theory] [MemberData(nameof(RequestLineValidData))] - public async Task TakeStartLineSetsFrameProperties( + public async Task TakeStartLineSetsHttpProtocolProperties( string requestLine, string expectedMethod, string expectedRawTarget, @@ -353,15 +353,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await _application.Output.WriteAsync(requestLineBytes); var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - var returnValue = _frame.TakeStartLine(readableBuffer, out _consumed, out _examined); + var returnValue = _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined); _transport.Input.Advance(_consumed, _examined); Assert.True(returnValue); - Assert.Equal(expectedMethod, _frame.Method); - Assert.Equal(expectedRawTarget, _frame.RawTarget); - Assert.Equal(expectedDecodedPath, _frame.Path); - Assert.Equal(expectedQueryString, _frame.QueryString); - Assert.Equal(expectedHttpVersion, _frame.HttpVersion); + Assert.Equal(expectedMethod, _http1Connection.Method); + Assert.Equal(expectedRawTarget, _http1Connection.RawTarget); + Assert.Equal(expectedDecodedPath, _http1Connection.Path); + Assert.Equal(expectedQueryString, _http1Connection.QueryString); + Assert.Equal(expectedHttpVersion, _http1Connection.HttpVersion); } [Theory] @@ -376,13 +376,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await _application.Output.WriteAsync(requestLineBytes); var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - var returnValue = _frame.TakeStartLine(readableBuffer, out _consumed, out _examined); + var returnValue = _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined); _transport.Input.Advance(_consumed, _examined); Assert.True(returnValue); - Assert.Equal(expectedRawTarget, _frame.RawTarget); - Assert.Equal(expectedDecodedPath, _frame.Path); - Assert.Equal(expectedQueryString, _frame.QueryString); + Assert.Equal(expectedRawTarget, _http1Connection.RawTarget); + Assert.Equal(expectedDecodedPath, _http1Connection.Path); + Assert.Equal(expectedQueryString, _http1Connection.QueryString); } [Fact] @@ -390,7 +390,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("G")); - _frame.ParseRequest((await _transport.Input.ReadAsync()).Buffer, out _consumed, out _examined); + _http1Connection.ParseRequest((await _transport.Input.ReadAsync()).Buffer, out _consumed, out _examined); _transport.Input.Advance(_consumed, _examined); var expectedRequestHeadersTimeout = _serviceContext.ServerOptions.Limits.RequestHeadersTimeout.Ticks; @@ -406,7 +406,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await _application.Output.WriteAsync(requestLineBytes); var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - var exception = Assert.Throws(() => _frame.TakeStartLine(readableBuffer, out _consumed, out _examined)); + var exception = Assert.Throws(() => _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); _transport.Input.Advance(_consumed, _examined); Assert.Equal(CoreStrings.BadRequest_RequestLineTooLong, exception.Message); @@ -421,7 +421,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; var exception = Assert.Throws(() => - _frame.TakeStartLine(readableBuffer, out _consumed, out _examined)); + _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); _transport.Input.Advance(_consumed, _examined); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target), exception.Message); @@ -435,7 +435,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; var exception = Assert.Throws(() => - _frame.TakeStartLine(readableBuffer, out _consumed, out _examined)); + _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); _transport.Input.Advance(_consumed, _examined); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message); @@ -451,7 +451,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; var exception = Assert.Throws(() => - _frame.TakeStartLine(readableBuffer, out _consumed, out _examined)); + _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); _transport.Input.Advance(_consumed, _examined); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine.EscapeNonPrintable()), exception.Message); @@ -467,7 +467,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; var exception = Assert.Throws(() => - _frame.TakeStartLine(readableBuffer, out _consumed, out _examined)); + _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); _transport.Input.Advance(_consumed, _examined); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message); @@ -483,7 +483,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; var exception = Assert.Throws(() => - _frame.TakeStartLine(readableBuffer, out _consumed, out _examined)); + _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); _transport.Input.Advance(_consumed, _examined); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message); @@ -497,7 +497,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; var exception = Assert.Throws(() => - _frame.TakeStartLine(readableBuffer, out _consumed, out _examined)); + _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); _transport.Input.Advance(_consumed, _examined); Assert.Equal(405, exception.StatusCode); @@ -508,12 +508,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void ProcessRequestsAsyncEnablesKeepAliveTimeout() { - var requestProcessingTask = _frame.ProcessRequestsAsync(); + var requestProcessingTask = _http1Connection.ProcessRequestsAsync(); var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout.Ticks; _timeoutControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.StopProcessingNextRequest)); - _frame.StopProcessingNextRequest(); + _http1Connection.StopProcessingNextRequest(); _application.Output.Complete(); requestProcessingTask.Wait(); @@ -523,82 +523,82 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public async Task WriteThrowsForNonBodyResponse() { // Arrange - ((IHttpResponseFeature)_frame).StatusCode = StatusCodes.Status304NotModified; + ((IHttpResponseFeature)_http1Connection).StatusCode = StatusCodes.Status304NotModified; // Act/Assert - await Assert.ThrowsAsync(() => _frame.WriteAsync(new ArraySegment(new byte[1]))); + await Assert.ThrowsAsync(() => _http1Connection.WriteAsync(new ArraySegment(new byte[1]))); } [Fact] public async Task WriteAsyncThrowsForNonBodyResponse() { // Arrange - _frame.HttpVersion = "HTTP/1.1"; - ((IHttpResponseFeature)_frame).StatusCode = StatusCodes.Status304NotModified; + _http1Connection.HttpVersion = "HTTP/1.1"; + ((IHttpResponseFeature)_http1Connection).StatusCode = StatusCodes.Status304NotModified; // Act/Assert - await Assert.ThrowsAsync(() => _frame.WriteAsync(new ArraySegment(new byte[1]), default(CancellationToken))); + await Assert.ThrowsAsync(() => _http1Connection.WriteAsync(new ArraySegment(new byte[1]), default(CancellationToken))); } [Fact] public async Task WriteDoesNotThrowForHeadResponse() { // Arrange - _frame.HttpVersion = "HTTP/1.1"; - ((IHttpRequestFeature)_frame).Method = "HEAD"; + _http1Connection.HttpVersion = "HTTP/1.1"; + ((IHttpRequestFeature)_http1Connection).Method = "HEAD"; // Act/Assert - await _frame.WriteAsync(new ArraySegment(new byte[1])); + await _http1Connection.WriteAsync(new ArraySegment(new byte[1])); } [Fact] public async Task WriteAsyncDoesNotThrowForHeadResponse() { // Arrange - _frame.HttpVersion = "HTTP/1.1"; - ((IHttpRequestFeature)_frame).Method = "HEAD"; + _http1Connection.HttpVersion = "HTTP/1.1"; + ((IHttpRequestFeature)_http1Connection).Method = "HEAD"; // Act/Assert - await _frame.WriteAsync(new ArraySegment(new byte[1]), default(CancellationToken)); + await _http1Connection.WriteAsync(new ArraySegment(new byte[1]), default(CancellationToken)); } [Fact] public async Task ManuallySettingTransferEncodingThrowsForHeadResponse() { // Arrange - _frame.HttpVersion = "HTTP/1.1"; - ((IHttpRequestFeature)_frame).Method = "HEAD"; + _http1Connection.HttpVersion = "HTTP/1.1"; + ((IHttpRequestFeature)_http1Connection).Method = "HEAD"; // Act - _frame.ResponseHeaders.Add("Transfer-Encoding", "chunked"); + _http1Connection.ResponseHeaders.Add("Transfer-Encoding", "chunked"); // Assert - await Assert.ThrowsAsync(() => _frame.FlushAsync()); + await Assert.ThrowsAsync(() => _http1Connection.FlushAsync()); } [Fact] public async Task ManuallySettingTransferEncodingThrowsForNoBodyResponse() { // Arrange - _frame.HttpVersion = "HTTP/1.1"; - ((IHttpResponseFeature)_frame).StatusCode = StatusCodes.Status304NotModified; + _http1Connection.HttpVersion = "HTTP/1.1"; + ((IHttpResponseFeature)_http1Connection).StatusCode = StatusCodes.Status304NotModified; // Act - _frame.ResponseHeaders.Add("Transfer-Encoding", "chunked"); + _http1Connection.ResponseHeaders.Add("Transfer-Encoding", "chunked"); // Assert - await Assert.ThrowsAsync(() => _frame.FlushAsync()); + await Assert.ThrowsAsync(() => _http1Connection.FlushAsync()); } [Fact] public async Task RequestProcessingTaskIsUnwrapped() { - var requestProcessingTask = _frame.ProcessRequestsAsync(); + var requestProcessingTask = _http1Connection.ProcessRequestsAsync(); var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n"); await _application.Output.WriteAsync(data); - _frame.StopProcessingNextRequest(); + _http1Connection.StopProcessingNextRequest(); Assert.IsNotType>(requestProcessingTask); await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10)); @@ -608,74 +608,74 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public async Task RequestAbortedTokenIsResetBeforeLastWriteWithContentLength() { - _frame.ResponseHeaders["Content-Length"] = "12"; + _http1Connection.ResponseHeaders["Content-Length"] = "12"; // Need to compare WaitHandle ref since CancellationToken is struct - var original = _frame.RequestAborted.WaitHandle; + var original = _http1Connection.RequestAborted.WaitHandle; foreach (var ch in "hello, worl") { - await _frame.WriteAsync(new ArraySegment(new[] { (byte)ch })); - Assert.Same(original, _frame.RequestAborted.WaitHandle); + await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)ch })); + Assert.Same(original, _http1Connection.RequestAborted.WaitHandle); } - await _frame.WriteAsync(new ArraySegment(new[] { (byte)'d' })); - Assert.NotSame(original, _frame.RequestAborted.WaitHandle); + await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)'d' })); + Assert.NotSame(original, _http1Connection.RequestAborted.WaitHandle); } [Fact] public async Task RequestAbortedTokenIsResetBeforeLastWriteAsyncWithContentLength() { - _frame.ResponseHeaders["Content-Length"] = "12"; + _http1Connection.ResponseHeaders["Content-Length"] = "12"; // Need to compare WaitHandle ref since CancellationToken is struct - var original = _frame.RequestAborted.WaitHandle; + var original = _http1Connection.RequestAborted.WaitHandle; foreach (var ch in "hello, worl") { - await _frame.WriteAsync(new ArraySegment(new[] { (byte)ch }), default(CancellationToken)); - Assert.Same(original, _frame.RequestAborted.WaitHandle); + await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)ch }), default(CancellationToken)); + Assert.Same(original, _http1Connection.RequestAborted.WaitHandle); } - await _frame.WriteAsync(new ArraySegment(new[] { (byte)'d' }), default(CancellationToken)); - Assert.NotSame(original, _frame.RequestAborted.WaitHandle); + await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)'d' }), default(CancellationToken)); + Assert.NotSame(original, _http1Connection.RequestAborted.WaitHandle); } [Fact] public async Task RequestAbortedTokenIsResetBeforeLastWriteAsyncAwaitedWithContentLength() { - _frame.ResponseHeaders["Content-Length"] = "12"; + _http1Connection.ResponseHeaders["Content-Length"] = "12"; // Need to compare WaitHandle ref since CancellationToken is struct - var original = _frame.RequestAborted.WaitHandle; + var original = _http1Connection.RequestAborted.WaitHandle; // Only first write can be WriteAsyncAwaited - var startingTask = _frame.InitializeResponseAwaited(Task.CompletedTask, 1); - await _frame.WriteAsyncAwaited(startingTask, new ArraySegment(new[] { (byte)'h' }), default(CancellationToken)); - Assert.Same(original, _frame.RequestAborted.WaitHandle); + var startingTask = _http1Connection.InitializeResponseAwaited(Task.CompletedTask, 1); + await _http1Connection.WriteAsyncAwaited(startingTask, new ArraySegment(new[] { (byte)'h' }), default(CancellationToken)); + Assert.Same(original, _http1Connection.RequestAborted.WaitHandle); foreach (var ch in "ello, worl") { - await _frame.WriteAsync(new ArraySegment(new[] { (byte)ch }), default(CancellationToken)); - Assert.Same(original, _frame.RequestAborted.WaitHandle); + await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)ch }), default(CancellationToken)); + Assert.Same(original, _http1Connection.RequestAborted.WaitHandle); } - await _frame.WriteAsync(new ArraySegment(new[] { (byte)'d' }), default(CancellationToken)); - Assert.NotSame(original, _frame.RequestAborted.WaitHandle); + await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)'d' }), default(CancellationToken)); + Assert.NotSame(original, _http1Connection.RequestAborted.WaitHandle); } [Fact] public async Task RequestAbortedTokenIsResetBeforeLastWriteWithChunkedEncoding() { // Need to compare WaitHandle ref since CancellationToken is struct - var original = _frame.RequestAborted.WaitHandle; + var original = _http1Connection.RequestAborted.WaitHandle; - _frame.HttpVersion = "HTTP/1.1"; - await _frame.WriteAsync(new ArraySegment(Encoding.ASCII.GetBytes("hello, world")), default(CancellationToken)); - Assert.Same(original, _frame.RequestAborted.WaitHandle); + _http1Connection.HttpVersion = "HTTP/1.1"; + await _http1Connection.WriteAsync(new ArraySegment(Encoding.ASCII.GetBytes("hello, world")), default(CancellationToken)); + Assert.Same(original, _http1Connection.RequestAborted.WaitHandle); - await _frame.ProduceEndAsync(); - Assert.NotSame(original, _frame.RequestAborted.WaitHandle); + await _http1Connection.ProduceEndAsync(); + Assert.NotSame(original, _http1Connection.RequestAborted.WaitHandle); } [Fact] @@ -696,7 +696,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; var exception = Assert.Throws(() => - _frame.TakeStartLine(readableBuffer, out _consumed, out _examined)); + _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined)); _transport.Input.Advance(_consumed, _examined); Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(string.Empty), exception.Message); @@ -722,22 +722,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var headers0 = MakeHeaders(header0Count); var headers1 = MakeHeaders(header1Count, header0Count); - var requestProcessingTask = _frame.ProcessRequestsAsync(); + var requestProcessingTask = _http1Connection.ProcessRequestsAsync(); await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.0\r\n")); - await WaitForCondition(TimeSpan.FromSeconds(1), () => _frame.RequestHeaders != null); - Assert.Equal(0, _frame.RequestHeaders.Count); + await WaitForCondition(TimeSpan.FromSeconds(1), () => _http1Connection.RequestHeaders != null); + Assert.Equal(0, _http1Connection.RequestHeaders.Count); await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(headers0)); - await WaitForCondition(TimeSpan.FromSeconds(1), () => _frame.RequestHeaders.Count >= header0Count); - Assert.Equal(header0Count, _frame.RequestHeaders.Count); + await WaitForCondition(TimeSpan.FromSeconds(1), () => _http1Connection.RequestHeaders.Count >= header0Count); + Assert.Equal(header0Count, _http1Connection.RequestHeaders.Count); await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(headers1)); - await WaitForCondition(TimeSpan.FromSeconds(1), () => _frame.RequestHeaders.Count >= header0Count + header1Count); - Assert.Equal(header0Count + header1Count, _frame.RequestHeaders.Count); + await WaitForCondition(TimeSpan.FromSeconds(1), () => _http1Connection.RequestHeaders.Count >= header0Count + header1Count); + Assert.Equal(header0Count + header1Count, _http1Connection.RequestHeaders.Count); await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("\r\n")); - Assert.Equal(header0Count + header1Count, _frame.RequestHeaders.Count); + Assert.Equal(header0Count + header1Count, _http1Connection.RequestHeaders.Count); await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10)); } @@ -756,29 +756,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var headers0 = MakeHeaders(header0Count); var headers1 = MakeHeaders(header1Count, header0Count); - var requestProcessingTask = _frame.ProcessRequestsAsync(); + var requestProcessingTask = _http1Connection.ProcessRequestsAsync(); await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.0\r\n")); - await WaitForCondition(TimeSpan.FromSeconds(1), () => _frame.RequestHeaders != null); - Assert.Equal(0, _frame.RequestHeaders.Count); + await WaitForCondition(TimeSpan.FromSeconds(1), () => _http1Connection.RequestHeaders != null); + Assert.Equal(0, _http1Connection.RequestHeaders.Count); - var newRequestHeaders = new RequestHeadersWrapper(_frame.RequestHeaders); - _frame.RequestHeaders = newRequestHeaders; - Assert.Same(newRequestHeaders, _frame.RequestHeaders); + var newRequestHeaders = new RequestHeadersWrapper(_http1Connection.RequestHeaders); + _http1Connection.RequestHeaders = newRequestHeaders; + Assert.Same(newRequestHeaders, _http1Connection.RequestHeaders); await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(headers0)); - await WaitForCondition(TimeSpan.FromSeconds(1), () => _frame.RequestHeaders.Count >= header0Count); - Assert.Same(newRequestHeaders, _frame.RequestHeaders); - Assert.Equal(header0Count, _frame.RequestHeaders.Count); + await WaitForCondition(TimeSpan.FromSeconds(1), () => _http1Connection.RequestHeaders.Count >= header0Count); + Assert.Same(newRequestHeaders, _http1Connection.RequestHeaders); + Assert.Equal(header0Count, _http1Connection.RequestHeaders.Count); await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(headers1)); - await WaitForCondition(TimeSpan.FromSeconds(1), () => _frame.RequestHeaders.Count >= header0Count + header1Count); - Assert.Same(newRequestHeaders, _frame.RequestHeaders); - Assert.Equal(header0Count + header1Count, _frame.RequestHeaders.Count); + await WaitForCondition(TimeSpan.FromSeconds(1), () => _http1Connection.RequestHeaders.Count >= header0Count + header1Count); + Assert.Same(newRequestHeaders, _http1Connection.RequestHeaders); + Assert.Equal(header0Count + header1Count, _http1Connection.RequestHeaders.Count); await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("\r\n")); - Assert.Same(newRequestHeaders, _frame.RequestHeaders); - Assert.Equal(header0Count + header1Count, _frame.RequestHeaders.Count); + Assert.Same(newRequestHeaders, _http1Connection.RequestHeaders); + Assert.Equal(header0Count + header1Count, _http1Connection.RequestHeaders.Count); await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10)); } @@ -788,11 +788,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { // Act // This would normally be set by the MessageBody during the first read. - _frame.HasStartedConsumingRequestBody = true; + _http1Connection.HasStartedConsumingRequestBody = true; // Assert - Assert.True(((IHttpMaxRequestBodySizeFeature)_frame).IsReadOnly); - var ex = Assert.Throws(() => ((IHttpMaxRequestBodySizeFeature)_frame).MaxRequestBodySize = 1); + Assert.True(((IHttpMaxRequestBodySizeFeature)_http1Connection).IsReadOnly); + var ex = Assert.Throws(() => ((IHttpMaxRequestBodySizeFeature)_http1Connection).MaxRequestBodySize = 1); Assert.Equal(CoreStrings.MaxRequestBodySizeCannotBeModifiedAfterRead, ex.Message); } @@ -800,7 +800,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public void ThrowsWhenMaxRequestBodySizeIsSetToANegativeValue() { // Assert - var ex = Assert.Throws(() => ((IHttpMaxRequestBodySizeFeature)_frame).MaxRequestBodySize = -1); + var ex = Assert.Throws(() => ((IHttpMaxRequestBodySizeFeature)_http1Connection).MaxRequestBodySize = -1); Assert.StartsWith(CoreStrings.NonNegativeNumberOrNullRequired, ex.Message); } diff --git a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs index 67743cad1f..1a193e02b3 100644 --- a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs +++ b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs @@ -1011,7 +1011,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - var responseHeaders = new FrameResponseHeaders(); + var responseHeaders = new HttpResponseHeaders(); _hpackDecoder.Decode(headersFrame.HeadersPayload, responseHeaders); _hpackDecoder.Decode(continuationFrame1.HeadersPayload, responseHeaders); _hpackDecoder.Decode(continuationFrame2.HeadersPayload, responseHeaders); diff --git a/test/Kestrel.Core.Tests/FrameConnectionManagerTests.cs b/test/Kestrel.Core.Tests/HttpConnectionManagerTests.cs similarity index 66% rename from test/Kestrel.Core.Tests/FrameConnectionManagerTests.cs rename to test/Kestrel.Core.Tests/HttpConnectionManagerTests.cs index a476c32ad1..015aa75881 100644 --- a/test/Kestrel.Core.Tests/FrameConnectionManagerTests.cs +++ b/test/Kestrel.Core.Tests/HttpConnectionManagerTests.cs @@ -11,23 +11,23 @@ using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { - public class FrameConnectionManagerTests + public class HttpConnectionManagerTests { [Fact] public void UnrootedConnectionsGetRemovedFromHeartbeat() { var connectionId = "0"; var trace = new Mock(); - var frameConnectionManager = new FrameConnectionManager(trace.Object, ResourceCounter.Unlimited); + var httpConnectionManager = new HttpConnectionManager(trace.Object, ResourceCounter.Unlimited); - // Create FrameConnection in inner scope so it doesn't get rooted by the current frame. - UnrootedConnectionsGetRemovedFromHeartbeatInnerScope(connectionId, frameConnectionManager, trace); + // Create HttpConnection in inner scope so it doesn't get rooted by the current frame. + UnrootedConnectionsGetRemovedFromHeartbeatInnerScope(connectionId, httpConnectionManager, trace); GC.Collect(); GC.WaitForPendingFinalizers(); var connectionCount = 0; - frameConnectionManager.Walk(_ => connectionCount++); + httpConnectionManager.Walk(_ => connectionCount++); Assert.Equal(0, connectionCount); trace.Verify(t => t.ApplicationNeverCompleted(connectionId), Times.Once()); @@ -36,25 +36,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MethodImpl(MethodImplOptions.NoInlining)] private void UnrootedConnectionsGetRemovedFromHeartbeatInnerScope( string connectionId, - FrameConnectionManager frameConnectionManager, + HttpConnectionManager httpConnectionManager, Mock trace) { - var frameConnection = new FrameConnection(new FrameConnectionContext + var httpConnection = new HttpConnection(new HttpConnectionContext { ServiceContext = new TestServiceContext(), ConnectionId = connectionId }); - frameConnectionManager.AddConnection(0, frameConnection); + httpConnectionManager.AddConnection(0, httpConnection); var connectionCount = 0; - frameConnectionManager.Walk(_ => connectionCount++); + httpConnectionManager.Walk(_ => connectionCount++); Assert.Equal(1, connectionCount); trace.Verify(t => t.ApplicationNeverCompleted(connectionId), Times.Never()); - // Ensure frameConnection doesn't get GC'd before this point. - GC.KeepAlive(frameConnection); + // Ensure httpConnection doesn't get GC'd before this point. + GC.KeepAlive(httpConnection); } } } diff --git a/test/Kestrel.Core.Tests/FrameConnectionTests.cs b/test/Kestrel.Core.Tests/HttpConnectionTests.cs similarity index 60% rename from test/Kestrel.Core.Tests/FrameConnectionTests.cs rename to test/Kestrel.Core.Tests/HttpConnectionTests.cs index 7ddb0856b7..f1f887ddc2 100644 --- a/test/Kestrel.Core.Tests/FrameConnectionTests.cs +++ b/test/Kestrel.Core.Tests/HttpConnectionTests.cs @@ -15,24 +15,24 @@ using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { - public class FrameConnectionTests : IDisposable + public class HttpConnectionTests : IDisposable { private readonly PipeFactory _pipeFactory; - private readonly FrameConnectionContext _frameConnectionContext; - private readonly FrameConnection _frameConnection; + private readonly HttpConnectionContext _httpConnectionContext; + private readonly HttpConnection _httpConnection; - public FrameConnectionTests() + public HttpConnectionTests() { _pipeFactory = new PipeFactory(); var pair = _pipeFactory.CreateConnectionPair(); - _frameConnectionContext = new FrameConnectionContext + _httpConnectionContext = new HttpConnectionContext { ConnectionId = "0123456789", ConnectionAdapters = new List(), ConnectionFeatures = new FeatureCollection(), PipeFactory = _pipeFactory, - FrameConnectionId = long.MinValue, + HttpConnectionId = long.MinValue, Application = pair.Application, Transport = pair.Transport, ServiceContext = new TestServiceContext @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } }; - _frameConnection = new FrameConnection(_frameConnectionContext); + _httpConnection = new HttpConnection(_httpConnectionContext); } public void Dispose() @@ -54,15 +54,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var mockDebugger = new Mock(); mockDebugger.SetupGet(g => g.IsAttached).Returns(true); - _frameConnection.Debugger = mockDebugger.Object; - _frameConnection.CreateFrame(new DummyApplication(), _frameConnectionContext.Transport, _frameConnectionContext.Application); + _httpConnection.Debugger = mockDebugger.Object; + _httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application); var now = DateTimeOffset.Now; - _frameConnection.Tick(now); - _frameConnection.SetTimeout(1, TimeoutAction.SendTimeoutResponse); - _frameConnection.Tick(now.AddTicks(2).Add(Heartbeat.Interval)); + _httpConnection.Tick(now); + _httpConnection.SetTimeout(1, TimeoutAction.SendTimeoutResponse); + _httpConnection.Tick(now.AddTicks(2).Add(Heartbeat.Interval)); - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); } [Fact] @@ -70,14 +70,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var mockDebugger = new Mock(); mockDebugger.SetupGet(g => g.IsAttached).Returns(true); - _frameConnection.Debugger = mockDebugger.Object; + _httpConnection.Debugger = mockDebugger.Object; var bytesPerSecond = 100; var mockLogger = new Mock(); mockLogger.Setup(l => l.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), It.IsAny())).Throws(new InvalidOperationException("Should not log")); TickBodyWithMinimumDataRate(mockLogger.Object, bytesPerSecond); - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); } [Fact] @@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests TickBodyWithMinimumDataRate(mockLogger.Object, bytesPerSecond); // Timed out - Assert.True(_frameConnection.RequestTimedOut); + Assert.True(_httpConnection.RequestTimedOut); mockLogger.Verify(logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), bytesPerSecond), Times.Once); } @@ -97,24 +97,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var gracePeriod = TimeSpan.FromSeconds(5); - _frameConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate = + _httpConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: bytesPerSecond, gracePeriod: gracePeriod); - _frameConnectionContext.ServiceContext.Log = logger; + _httpConnectionContext.ServiceContext.Log = logger; - _frameConnection.CreateFrame(new DummyApplication(), _frameConnectionContext.Transport, _frameConnectionContext.Application); - _frameConnection.Frame.Reset(); + _httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application); + _httpConnection.Http1Connection.Reset(); // Initialize timestamp var now = DateTimeOffset.UtcNow; - _frameConnection.Tick(now); + _httpConnection.Tick(now); - _frameConnection.StartTimingReads(); + _httpConnection.StartTimingReads(); // Tick after grace period w/ low data rate now += gracePeriod + TimeSpan.FromSeconds(1); - _frameConnection.BytesRead(1); - _frameConnection.Tick(now); + _httpConnection.BytesRead(1); + _httpConnection.Tick(now); } [Fact] @@ -123,38 +123,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var bytesPerSecond = 100; var gracePeriod = TimeSpan.FromSeconds(2); - _frameConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate = + _httpConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: bytesPerSecond, gracePeriod: gracePeriod); var mockLogger = new Mock(); - _frameConnectionContext.ServiceContext.Log = mockLogger.Object; + _httpConnectionContext.ServiceContext.Log = mockLogger.Object; - _frameConnection.CreateFrame(new DummyApplication(), _frameConnectionContext.Transport, _frameConnectionContext.Application); - _frameConnection.Frame.Reset(); + _httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application); + _httpConnection.Http1Connection.Reset(); // Initialize timestamp var now = DateTimeOffset.UtcNow; - _frameConnection.Tick(now); + _httpConnection.Tick(now); - _frameConnection.StartTimingReads(); + _httpConnection.StartTimingReads(); // Tick during grace period w/ low data rate now += TimeSpan.FromSeconds(1); - _frameConnection.BytesRead(10); - _frameConnection.Tick(now); + _httpConnection.BytesRead(10); + _httpConnection.Tick(now); // Not timed out - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); mockLogger.Verify(logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), bytesPerSecond), Times.Never); // Tick after grace period w/ low data rate now += TimeSpan.FromSeconds(2); - _frameConnection.BytesRead(10); - _frameConnection.Tick(now); + _httpConnection.BytesRead(10); + _httpConnection.Tick(now); // Timed out - Assert.True(_frameConnection.RequestTimedOut); + Assert.True(_httpConnection.RequestTimedOut); mockLogger.Verify(logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), bytesPerSecond), Times.Once); } @@ -165,73 +165,73 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var bytesPerSecond = 100; var gracePeriod = TimeSpan.FromSeconds(2); - _frameConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate = + _httpConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: bytesPerSecond, gracePeriod: gracePeriod); var mockLogger = new Mock(); - _frameConnectionContext.ServiceContext.Log = mockLogger.Object; + _httpConnectionContext.ServiceContext.Log = mockLogger.Object; - _frameConnection.CreateFrame(new DummyApplication(), _frameConnectionContext.Transport, _frameConnectionContext.Application); - _frameConnection.Frame.Reset(); + _httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application); + _httpConnection.Http1Connection.Reset(); // Initialize timestamp var now = DateTimeOffset.UtcNow; - _frameConnection.Tick(now); + _httpConnection.Tick(now); - _frameConnection.StartTimingReads(); + _httpConnection.StartTimingReads(); // Set base data rate to 200 bytes/second now += gracePeriod; - _frameConnection.BytesRead(400); - _frameConnection.Tick(now); + _httpConnection.BytesRead(400); + _httpConnection.Tick(now); // Data rate: 200 bytes/second now += TimeSpan.FromSeconds(1); - _frameConnection.BytesRead(200); - _frameConnection.Tick(now); + _httpConnection.BytesRead(200); + _httpConnection.Tick(now); // Not timed out - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); mockLogger.Verify(logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), bytesPerSecond), Times.Never); // Data rate: 150 bytes/second now += TimeSpan.FromSeconds(1); - _frameConnection.BytesRead(0); - _frameConnection.Tick(now); + _httpConnection.BytesRead(0); + _httpConnection.Tick(now); // Not timed out - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); mockLogger.Verify(logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), bytesPerSecond), Times.Never); // Data rate: 120 bytes/second now += TimeSpan.FromSeconds(1); - _frameConnection.BytesRead(0); - _frameConnection.Tick(now); + _httpConnection.BytesRead(0); + _httpConnection.Tick(now); // Not timed out - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); mockLogger.Verify(logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), bytesPerSecond), Times.Never); // Data rate: 100 bytes/second now += TimeSpan.FromSeconds(1); - _frameConnection.BytesRead(0); - _frameConnection.Tick(now); + _httpConnection.BytesRead(0); + _httpConnection.Tick(now); // Not timed out - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); mockLogger.Verify(logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), bytesPerSecond), Times.Never); // Data rate: ~85 bytes/second now += TimeSpan.FromSeconds(1); - _frameConnection.BytesRead(0); - _frameConnection.Tick(now); + _httpConnection.BytesRead(0); + _httpConnection.Tick(now); // Timed out - Assert.True(_frameConnection.RequestTimedOut); + Assert.True(_httpConnection.RequestTimedOut); mockLogger.Verify(logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), bytesPerSecond), Times.Once); } @@ -241,64 +241,64 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var systemClock = new MockSystemClock(); - _frameConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate = + _httpConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); - _frameConnectionContext.ServiceContext.SystemClock = systemClock; + _httpConnectionContext.ServiceContext.SystemClock = systemClock; var mockLogger = new Mock(); - _frameConnectionContext.ServiceContext.Log = mockLogger.Object; + _httpConnectionContext.ServiceContext.Log = mockLogger.Object; - _frameConnection.CreateFrame(new DummyApplication(), _frameConnectionContext.Transport, _frameConnectionContext.Application); - _frameConnection.Frame.Reset(); + _httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application); + _httpConnection.Http1Connection.Reset(); // Initialize timestamp - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); - _frameConnection.StartTimingReads(); + _httpConnection.StartTimingReads(); // Tick at 3s, expected counted time is 3s, expected data rate is 200 bytes/second systemClock.UtcNow += TimeSpan.FromSeconds(3); - _frameConnection.BytesRead(600); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.BytesRead(600); + _httpConnection.Tick(systemClock.UtcNow); // Pause at 3.5s systemClock.UtcNow += TimeSpan.FromSeconds(0.5); - _frameConnection.PauseTimingReads(); + _httpConnection.PauseTimingReads(); // Tick at 4s, expected counted time is 4s (first tick after pause goes through), expected data rate is 150 bytes/second systemClock.UtcNow += TimeSpan.FromSeconds(0.5); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); // Tick at 6s, expected counted time is 4s, expected data rate is 150 bytes/second systemClock.UtcNow += TimeSpan.FromSeconds(2); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); // Not timed out - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); mockLogger.Verify( logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); // Resume at 6.5s systemClock.UtcNow += TimeSpan.FromSeconds(0.5); - _frameConnection.ResumeTimingReads(); + _httpConnection.ResumeTimingReads(); // Tick at 9s, expected counted time is 6s, expected data rate is 100 bytes/second systemClock.UtcNow += TimeSpan.FromSeconds(1.5); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); // Not timed out - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); mockLogger.Verify( logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); // Tick at 10s, expected counted time is 7s, expected data rate drops below 100 bytes/second systemClock.UtcNow += TimeSpan.FromSeconds(1); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); // Timed out - Assert.True(_frameConnection.RequestTimedOut); + Assert.True(_httpConnection.RequestTimedOut); mockLogger.Verify( logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); @@ -309,57 +309,57 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var systemClock = new MockSystemClock(); - _frameConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate = + _httpConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); - _frameConnectionContext.ServiceContext.SystemClock = systemClock; + _httpConnectionContext.ServiceContext.SystemClock = systemClock; var mockLogger = new Mock(); - _frameConnectionContext.ServiceContext.Log = mockLogger.Object; + _httpConnectionContext.ServiceContext.Log = mockLogger.Object; - _frameConnection.CreateFrame(new DummyApplication(), _frameConnectionContext.Transport, _frameConnectionContext.Application); - _frameConnection.Frame.Reset(); + _httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application); + _httpConnection.Http1Connection.Reset(); // Initialize timestamp - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); - _frameConnection.StartTimingReads(); + _httpConnection.StartTimingReads(); // Tick at 2s, expected counted time is 2s, expected data rate is 100 bytes/second systemClock.UtcNow += TimeSpan.FromSeconds(2); - _frameConnection.BytesRead(200); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.BytesRead(200); + _httpConnection.Tick(systemClock.UtcNow); // Not timed out - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); mockLogger.Verify( logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); // Pause at 2.25s systemClock.UtcNow += TimeSpan.FromSeconds(0.25); - _frameConnection.PauseTimingReads(); + _httpConnection.PauseTimingReads(); // Resume at 2.5s systemClock.UtcNow += TimeSpan.FromSeconds(0.25); - _frameConnection.ResumeTimingReads(); + _httpConnection.ResumeTimingReads(); // Tick at 3s, expected counted time is 3s, expected data rate is 100 bytes/second systemClock.UtcNow += TimeSpan.FromSeconds(0.5); - _frameConnection.BytesRead(100); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.BytesRead(100); + _httpConnection.Tick(systemClock.UtcNow); // Not timed out - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); mockLogger.Verify( logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); // Tick at 4s, expected counted time is 4s, expected data rate drops below 100 bytes/second systemClock.UtcNow += TimeSpan.FromSeconds(1); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); // Timed out - Assert.True(_frameConnection.RequestTimedOut); + Assert.True(_httpConnection.RequestTimedOut); mockLogger.Verify( logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); @@ -371,39 +371,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var systemClock = new MockSystemClock(); var timeout = TimeSpan.FromSeconds(5); - _frameConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate = + _httpConnectionContext.ServiceContext.ServerOptions.Limits.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); - _frameConnectionContext.ServiceContext.SystemClock = systemClock; + _httpConnectionContext.ServiceContext.SystemClock = systemClock; var mockLogger = new Mock(); - _frameConnectionContext.ServiceContext.Log = mockLogger.Object; + _httpConnectionContext.ServiceContext.Log = mockLogger.Object; - _frameConnection.CreateFrame(new DummyApplication(), _frameConnectionContext.Transport, _frameConnectionContext.Application); - _frameConnection.Frame.Reset(); + _httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application); + _httpConnection.Http1Connection.Reset(); var startTime = systemClock.UtcNow; // Initialize timestamp - _frameConnection.Tick(startTime); + _httpConnection.Tick(startTime); - _frameConnection.StartTimingReads(); + _httpConnection.StartTimingReads(); - _frameConnection.SetTimeout(timeout.Ticks, TimeoutAction.StopProcessingNextRequest); + _httpConnection.SetTimeout(timeout.Ticks, TimeoutAction.StopProcessingNextRequest); // Tick beyond grace period with low data rate systemClock.UtcNow += TimeSpan.FromSeconds(3); - _frameConnection.BytesRead(1); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.BytesRead(1); + _httpConnection.Tick(systemClock.UtcNow); // Not timed out - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); // Tick just past timeout period, adjusted by Heartbeat.Interval systemClock.UtcNow = startTime + timeout + Heartbeat.Interval + TimeSpan.FromTicks(1); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); // Timed out - Assert.True(_frameConnection.RequestTimedOut); + Assert.True(_httpConnection.RequestTimedOut); } [Fact] @@ -412,31 +412,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var systemClock = new MockSystemClock(); var aborted = new ManualResetEventSlim(); - _frameConnectionContext.ServiceContext.ServerOptions.Limits.MinResponseDataRate = + _httpConnectionContext.ServiceContext.ServerOptions.Limits.MinResponseDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); - _frameConnectionContext.ServiceContext.SystemClock = systemClock; + _httpConnectionContext.ServiceContext.SystemClock = systemClock; var mockLogger = new Mock(); - _frameConnectionContext.ServiceContext.Log = mockLogger.Object; + _httpConnectionContext.ServiceContext.Log = mockLogger.Object; - _frameConnection.CreateFrame(new DummyApplication(), _frameConnectionContext.Transport, _frameConnectionContext.Application); - _frameConnection.Frame.Reset(); - _frameConnection.Frame.RequestAborted.Register(() => + _httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application); + _httpConnection.Http1Connection.Reset(); + _httpConnection.Http1Connection.RequestAborted.Register(() => { aborted.Set(); }); // Initialize timestamp - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); // Should complete within 4 seconds, but the timeout is adjusted by adding Heartbeat.Interval - _frameConnection.StartTimingWrite(400); + _httpConnection.StartTimingWrite(400); // Tick just past 4s plus Heartbeat.Interval systemClock.UtcNow += TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromTicks(1); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); - Assert.True(_frameConnection.RequestTimedOut); + Assert.True(_httpConnection.RequestTimedOut); Assert.True(aborted.Wait(TimeSpan.FromSeconds(10))); } @@ -447,38 +447,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var minResponseDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(5)); var aborted = new ManualResetEventSlim(); - _frameConnectionContext.ServiceContext.ServerOptions.Limits.MinResponseDataRate = minResponseDataRate; - _frameConnectionContext.ServiceContext.SystemClock = systemClock; + _httpConnectionContext.ServiceContext.ServerOptions.Limits.MinResponseDataRate = minResponseDataRate; + _httpConnectionContext.ServiceContext.SystemClock = systemClock; var mockLogger = new Mock(); - _frameConnectionContext.ServiceContext.Log = mockLogger.Object; + _httpConnectionContext.ServiceContext.Log = mockLogger.Object; - _frameConnection.CreateFrame(new DummyApplication(), _frameConnectionContext.Transport, _frameConnectionContext.Application); - _frameConnection.Frame.Reset(); - _frameConnection.Frame.RequestAborted.Register(() => + _httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application); + _httpConnection.Http1Connection.Reset(); + _httpConnection.Http1Connection.RequestAborted.Register(() => { aborted.Set(); }); // Initialize timestamp var startTime = systemClock.UtcNow; - _frameConnection.Tick(startTime); + _httpConnection.Tick(startTime); // Should complete within 1 second, but the timeout is adjusted by adding Heartbeat.Interval - _frameConnection.StartTimingWrite(100); + _httpConnection.StartTimingWrite(100); // Tick just past 1s plus Heartbeat.Interval systemClock.UtcNow += TimeSpan.FromSeconds(1) + Heartbeat.Interval + TimeSpan.FromTicks(1); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); // Still within grace period, not timed out - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); // Tick just past grace period (adjusted by Heartbeat.Interval) systemClock.UtcNow = startTime + minResponseDataRate.GracePeriod + Heartbeat.Interval + TimeSpan.FromTicks(1); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); - Assert.True(_frameConnection.RequestTimedOut); + Assert.True(_httpConnection.RequestTimedOut); Assert.True(aborted.Wait(TimeSpan.FromSeconds(10))); } @@ -488,44 +488,44 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var systemClock = new MockSystemClock(); var aborted = new ManualResetEventSlim(); - _frameConnectionContext.ServiceContext.ServerOptions.Limits.MinResponseDataRate = + _httpConnectionContext.ServiceContext.ServerOptions.Limits.MinResponseDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); - _frameConnectionContext.ServiceContext.SystemClock = systemClock; + _httpConnectionContext.ServiceContext.SystemClock = systemClock; var mockLogger = new Mock(); - _frameConnectionContext.ServiceContext.Log = mockLogger.Object; + _httpConnectionContext.ServiceContext.Log = mockLogger.Object; - _frameConnection.CreateFrame(new DummyApplication(), _frameConnectionContext.Transport, _frameConnectionContext.Application); - _frameConnection.Frame.Reset(); - _frameConnection.Frame.RequestAborted.Register(() => + _httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application); + _httpConnection.Http1Connection.Reset(); + _httpConnection.Http1Connection.RequestAborted.Register(() => { aborted.Set(); }); // Initialize timestamp - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); // Should complete within 5 seconds, but the timeout is adjusted by adding Heartbeat.Interval - _frameConnection.StartTimingWrite(500); + _httpConnection.StartTimingWrite(500); // Start a concurrent write after 3 seconds, which should complete within 3 seconds (adjusted by Heartbeat.Interval) - _frameConnection.StartTimingWrite(300); + _httpConnection.StartTimingWrite(300); // Tick just past 5s plus Heartbeat.Interval, when the first write should have completed systemClock.UtcNow += TimeSpan.FromSeconds(5) + Heartbeat.Interval + TimeSpan.FromTicks(1); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); // Not timed out because the timeout was pushed by the second write - Assert.False(_frameConnection.RequestTimedOut); + Assert.False(_httpConnection.RequestTimedOut); // Complete the first write, this should have no effect on the timeout - _frameConnection.StopTimingWrite(); + _httpConnection.StopTimingWrite(); // Tick just past +3s, when the second write should have completed systemClock.UtcNow += TimeSpan.FromSeconds(3) + TimeSpan.FromTicks(1); - _frameConnection.Tick(systemClock.UtcNow); + _httpConnection.Tick(systemClock.UtcNow); - Assert.True(_frameConnection.RequestTimedOut); + Assert.True(_httpConnection.RequestTimedOut); Assert.True(aborted.Wait(TimeSpan.FromSeconds(10))); } } diff --git a/test/Kestrel.Core.Tests/FrameHeadersTests.cs b/test/Kestrel.Core.Tests/HttpHeadersTests.cs similarity index 86% rename from test/Kestrel.Core.Tests/FrameHeadersTests.cs rename to test/Kestrel.Core.Tests/HttpHeadersTests.cs index 283645615e..2aef432898 100644 --- a/test/Kestrel.Core.Tests/FrameHeadersTests.cs +++ b/test/Kestrel.Core.Tests/HttpHeadersTests.cs @@ -10,7 +10,7 @@ using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { - public class FrameHeadersTests + public class HttpHeadersTests { [Theory] [InlineData("", ConnectionOptions.None)] @@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [InlineData("cl2se ,", ConnectionOptions.None)] public void TestParseConnection(string connection, ConnectionOptions expectedConnectionOptions) { - var connectionOptions = FrameHeaders.ParseConnection(connection); + var connectionOptions = HttpHeaders.ParseConnection(connection); Assert.Equal(expectedConnectionOptions, connectionOptions); } @@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public void TestParseConnectionMultipleValues(string value1, string value2, ConnectionOptions expectedConnectionOptions) { var connection = new StringValues(new[] { value1, value2 }); - var connectionOptions = FrameHeaders.ParseConnection(connection); + var connectionOptions = HttpHeaders.ParseConnection(connection); Assert.Equal(expectedConnectionOptions, connectionOptions); } @@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [InlineData("gzip, chunked2", TransferCoding.Other)] public void TestParseTransferEncoding(string transferEncoding, TransferCoding expectedTransferEncodingOptions) { - var transferEncodingOptions = FrameHeaders.GetFinalTransferCoding(transferEncoding); + var transferEncodingOptions = HttpHeaders.GetFinalTransferCoding(transferEncoding); Assert.Equal(expectedTransferEncodingOptions, transferEncodingOptions); } @@ -210,68 +210,68 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public void TestParseTransferEncodingMultipleValues(string value1, string value2, TransferCoding expectedTransferEncodingOptions) { var transferEncoding = new StringValues(new[] { value1, value2 }); - var transferEncodingOptions = FrameHeaders.GetFinalTransferCoding(transferEncoding); + var transferEncodingOptions = HttpHeaders.GetFinalTransferCoding(transferEncoding); Assert.Equal(expectedTransferEncodingOptions, transferEncodingOptions); } [Fact] public void ValidContentLengthsAccepted() { - ValidContentLengthsAcceptedImpl(new FrameRequestHeaders()); - ValidContentLengthsAcceptedImpl(new FrameResponseHeaders()); + ValidContentLengthsAcceptedImpl(new HttpRequestHeaders()); + ValidContentLengthsAcceptedImpl(new HttpResponseHeaders()); } - private static void ValidContentLengthsAcceptedImpl(FrameHeaders frameHeaders) + private static void ValidContentLengthsAcceptedImpl(HttpHeaders httpHeaders) { - IDictionary headers = frameHeaders; + IDictionary headers = httpHeaders; StringValues value; Assert.False(headers.TryGetValue("Content-Length", out value)); - Assert.Null(frameHeaders.ContentLength); - Assert.False(frameHeaders.ContentLength.HasValue); + Assert.Null(httpHeaders.ContentLength); + Assert.False(httpHeaders.ContentLength.HasValue); - frameHeaders.ContentLength = 1; + httpHeaders.ContentLength = 1; Assert.True(headers.TryGetValue("Content-Length", out value)); Assert.Equal("1", value[0]); - Assert.Equal(1, frameHeaders.ContentLength); - Assert.True(frameHeaders.ContentLength.HasValue); + Assert.Equal(1, httpHeaders.ContentLength); + Assert.True(httpHeaders.ContentLength.HasValue); - frameHeaders.ContentLength = long.MaxValue; + httpHeaders.ContentLength = long.MaxValue; Assert.True(headers.TryGetValue("Content-Length", out value)); Assert.Equal(HeaderUtilities.FormatNonNegativeInt64(long.MaxValue), value[0]); - Assert.Equal(long.MaxValue, frameHeaders.ContentLength); - Assert.True(frameHeaders.ContentLength.HasValue); + Assert.Equal(long.MaxValue, httpHeaders.ContentLength); + Assert.True(httpHeaders.ContentLength.HasValue); - frameHeaders.ContentLength = null; + httpHeaders.ContentLength = null; Assert.False(headers.TryGetValue("Content-Length", out value)); - Assert.Null(frameHeaders.ContentLength); - Assert.False(frameHeaders.ContentLength.HasValue); + Assert.Null(httpHeaders.ContentLength); + Assert.False(httpHeaders.ContentLength.HasValue); } [Fact] public void InvalidContentLengthsRejected() { - InvalidContentLengthsRejectedImpl(new FrameRequestHeaders()); - InvalidContentLengthsRejectedImpl(new FrameResponseHeaders()); + InvalidContentLengthsRejectedImpl(new HttpRequestHeaders()); + InvalidContentLengthsRejectedImpl(new HttpResponseHeaders()); } - private static void InvalidContentLengthsRejectedImpl(FrameHeaders frameHeaders) + private static void InvalidContentLengthsRejectedImpl(HttpHeaders httpHeaders) { - IDictionary headers = frameHeaders; + IDictionary headers = httpHeaders; StringValues value; Assert.False(headers.TryGetValue("Content-Length", out value)); - Assert.Null(frameHeaders.ContentLength); - Assert.False(frameHeaders.ContentLength.HasValue); + Assert.Null(httpHeaders.ContentLength); + Assert.False(httpHeaders.ContentLength.HasValue); - Assert.Throws(() => frameHeaders.ContentLength = -1); - Assert.Throws(() => frameHeaders.ContentLength = long.MinValue); + Assert.Throws(() => httpHeaders.ContentLength = -1); + Assert.Throws(() => httpHeaders.ContentLength = long.MinValue); Assert.False(headers.TryGetValue("Content-Length", out value)); - Assert.Null(frameHeaders.ContentLength); - Assert.False(frameHeaders.ContentLength.HasValue); + Assert.Null(httpHeaders.ContentLength); + Assert.False(httpHeaders.ContentLength.HasValue); } } } diff --git a/test/Kestrel.Core.Tests/HttpParserTests.cs b/test/Kestrel.Core.Tests/HttpParserTests.cs index 2113d756ea..17647743e5 100644 --- a/test/Kestrel.Core.Tests/HttpParserTests.cs +++ b/test/Kestrel.Core.Tests/HttpParserTests.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests string expectedRawTarget, string expectedRawPath, // This warns that theory methods should use all of their parameters, -// but this method is using a shared data collection with FrameTests.TakeStartLineSetsFrameProperties and others. +// but this method is using a shared data collection with Http1ConnectionTests.TakeStartLineSetsHttpProtocolProperties and others. #pragma warning disable xUnit1026 string expectedDecodedPath, string expectedQueryString, diff --git a/test/Kestrel.Core.Tests/FrameRequestHeadersTests.cs b/test/Kestrel.Core.Tests/HttpRequestHeadersTests.cs similarity index 90% rename from test/Kestrel.Core.Tests/FrameRequestHeadersTests.cs rename to test/Kestrel.Core.Tests/HttpRequestHeadersTests.cs index 88095e0d86..7104ef8db5 100644 --- a/test/Kestrel.Core.Tests/FrameRequestHeadersTests.cs +++ b/test/Kestrel.Core.Tests/HttpRequestHeadersTests.cs @@ -12,12 +12,12 @@ using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { - public class FrameRequestHeadersTests + public class HttpRequestHeadersTests { [Fact] public void InitialDictionaryIsEmpty() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); Assert.Equal(0, headers.Count); Assert.False(headers.IsReadOnly); @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void SettingUnknownHeadersWorks() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); headers["custom"] = new[] { "value" }; @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void SettingKnownHeadersWorks() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); headers["host"] = new[] { "value" }; headers["content-length"] = new[] { "0" }; @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void KnownAndCustomHeaderCountAddedTogether() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); headers["host"] = new[] { "value" }; headers["custom"] = new[] { "value" }; @@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void TryGetValueWorksForKnownAndUnknownHeaders() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); StringValues value; Assert.False(headers.TryGetValue("host", out value)); @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void SameExceptionThrownForMissingKey() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); Assert.Throws(() => headers["custom"]); Assert.Throws(() => headers["host"]); @@ -99,7 +99,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void EntriesCanBeEnumerated() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); var v1 = new[] { "localhost" }; var v2 = new[] { "0" }; var v3 = new[] { "value" }; @@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void KeysAndValuesCanBeEnumerated() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); StringValues v1 = new[] { "localhost" }; StringValues v2 = new[] { "0" }; StringValues v3 = new[] { "value" }; @@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void ContainsAndContainsKeyWork() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); var kv1 = new KeyValuePair("host", new[] { "localhost" }); var kv2 = new KeyValuePair("custom", new[] { "value" }); var kv3 = new KeyValuePair("Content-Length", new[] { "0" }); @@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void AddWorksLikeSetAndThrowsIfKeyExists() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); StringValues value; Assert.False(headers.TryGetValue("host", out value)); @@ -216,7 +216,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void ClearRemovesAllHeaders() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); headers.Add("host", new[] { "localhost" }); headers.Add("custom", new[] { "value" }); headers.Add("Content-Length", new[] { "0" }); @@ -238,7 +238,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void RemoveTakesHeadersOutOfDictionary() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); headers.Add("host", new[] { "localhost" }); headers.Add("custom", new[] { "value" }); headers.Add("Content-Length", new[] { "0" }); @@ -276,7 +276,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void CopyToMovesDataIntoArray() { - IDictionary headers = new FrameRequestHeaders(); + IDictionary headers = new HttpRequestHeaders(); headers.Add("host", new[] { "localhost" }); headers.Add("Content-Length", new[] { "0" }); headers.Add("custom", new[] { "value" }); @@ -303,7 +303,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void AppendThrowsWhenHeaderNameContainsNonASCIICharacters() { - var headers = new FrameRequestHeaders(); + var headers = new HttpRequestHeaders(); const string key = "\u00141\u00F3d\017c"; var encoding = Encoding.GetEncoding("iso-8859-1"); diff --git a/test/Kestrel.Core.Tests/FrameRequestStreamTests.cs b/test/Kestrel.Core.Tests/HttpRequestStreamTests.cs similarity index 75% rename from test/Kestrel.Core.Tests/FrameRequestStreamTests.cs rename to test/Kestrel.Core.Tests/HttpRequestStreamTests.cs index 12a1a87c81..1f641dc650 100644 --- a/test/Kestrel.Core.Tests/FrameRequestStreamTests.cs +++ b/test/Kestrel.Core.Tests/HttpRequestStreamTests.cs @@ -12,54 +12,54 @@ using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { - public class FrameRequestStreamTests + public class HttpRequestStreamTests { [Fact] public void CanReadReturnsTrue() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); Assert.True(stream.CanRead); } [Fact] public void CanSeekReturnsFalse() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); Assert.False(stream.CanSeek); } [Fact] public void CanWriteReturnsFalse() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); Assert.False(stream.CanWrite); } [Fact] public void SeekThrows() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); Assert.Throws(() => stream.Seek(0, SeekOrigin.Begin)); } [Fact] public void LengthThrows() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); Assert.Throws(() => stream.Length); } [Fact] public void SetLengthThrows() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); Assert.Throws(() => stream.SetLength(0)); } [Fact] public void PositionThrows() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); Assert.Throws(() => stream.Position); Assert.Throws(() => stream.Position = 0); } @@ -67,21 +67,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void WriteThrows() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); Assert.Throws(() => stream.Write(new byte[1], 0, 1)); } [Fact] public void WriteByteThrows() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); Assert.Throws(() => stream.WriteByte(0)); } [Fact] public async Task WriteAsyncThrows() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); await Assert.ThrowsAsync(() => stream.WriteAsync(new byte[1], 0, 1)); } @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void BeginWriteThrows() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); Assert.Throws(() => stream.BeginWrite(new byte[1], 0, 1, null, null)); } #elif NETCOREAPP2_0 @@ -100,14 +100,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void FlushThrows() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); Assert.Throws(() => stream.Flush()); } [Fact] public async Task FlushAsyncThrows() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); await Assert.ThrowsAsync(() => stream.FlushAsync()); } @@ -117,10 +117,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var allowSynchronousIO = false; var mockBodyControl = new Mock(); mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(() => allowSynchronousIO); - var mockMessageBody = new Mock((Frame)null); + var mockMessageBody = new Mock((HttpProtocol)null); mockMessageBody.Setup(m => m.ReadAsync(It.IsAny>(), CancellationToken.None)).ReturnsAsync(0); - var stream = new FrameRequestStream(mockBodyControl.Object); + var stream = new HttpRequestStream(mockBodyControl.Object); stream.StartAcceptingReads(mockMessageBody.Object); Assert.Equal(0, await stream.ReadAsync(new byte[1], 0, 1)); @@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void AbortCausesReadToCancel() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(null); stream.Abort(); var task = stream.ReadAsync(new byte[1], 0, 1); @@ -148,7 +148,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void AbortWithErrorCausesReadToCancel() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(null); var error = new Exception(); stream.Abort(error); @@ -160,7 +160,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void StopAcceptingReadsCausesReadToThrowObjectDisposedException() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(null); stream.StopAcceptingReads(); Assert.Throws(() => { stream.ReadAsync(new byte[1], 0, 1); }); @@ -169,7 +169,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void AbortCausesCopyToAsyncToCancel() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(null); stream.Abort(); var task = stream.CopyToAsync(Mock.Of()); @@ -179,7 +179,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void AbortWithErrorCausesCopyToAsyncToCancel() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(null); var error = new Exception(); stream.Abort(error); @@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void StopAcceptingReadsCausesCopyToAsyncToThrowObjectDisposedException() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(null); stream.StopAcceptingReads(); Assert.Throws(() => { stream.CopyToAsync(Mock.Of()); }); @@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void NullDestinationCausesCopyToAsyncToThrowArgumentNullException() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(null); Assert.Throws(() => { stream.CopyToAsync(null); }); } @@ -208,7 +208,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void ZeroBufferSizeCausesCopyToAsyncToThrowArgumentException() { - var stream = new FrameRequestStream(Mock.Of()); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(null); Assert.Throws(() => { stream.CopyToAsync(Mock.Of(), 0); }); } diff --git a/test/Kestrel.Core.Tests/FrameResponseHeadersTests.cs b/test/Kestrel.Core.Tests/HttpResponseHeadersTests.cs similarity index 89% rename from test/Kestrel.Core.Tests/FrameResponseHeadersTests.cs rename to test/Kestrel.Core.Tests/HttpResponseHeadersTests.cs index 0bc399629c..8ed1d70602 100644 --- a/test/Kestrel.Core.Tests/FrameResponseHeadersTests.cs +++ b/test/Kestrel.Core.Tests/HttpResponseHeadersTests.cs @@ -14,14 +14,14 @@ using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { - public class FrameResponseHeadersTests + public class HttpResponseHeadersTests { [Fact] public void InitialDictionaryIsEmpty() { var factory = new PipeFactory(); var pair = factory.CreateConnectionPair(); - var frameContext = new FrameContext + var http1ConnectionContext = new Http1ConnectionContext { ServiceContext = new TestServiceContext(), ConnectionFeatures = new FeatureCollection(), @@ -31,11 +31,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests TimeoutControl = null }; - var frame = new Frame(application: null, frameContext: frameContext); + var http1Connection = new Http1Connection(application: null, context: http1ConnectionContext); - frame.Reset(); + http1Connection.Reset(); - IDictionary headers = frame.ResponseHeaders; + IDictionary headers = http1Connection.ResponseHeaders; Assert.Equal(0, headers.Count); Assert.False(headers.IsReadOnly); @@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [InlineData("Seršver", "Data")] public void AddingControlOrNonAsciiCharactersToHeadersThrows(string key, string value) { - var responseHeaders = new FrameResponseHeaders(); + var responseHeaders = new HttpResponseHeaders(); Assert.Throws(() => { @@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void ThrowsWhenAddingHeaderAfterReadOnlyIsSet() { - var headers = new FrameResponseHeaders(); + var headers = new HttpResponseHeaders(); headers.SetReadOnly(); Assert.Throws(() => ((IDictionary)headers).Add("my-header", new[] { "value" })); @@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void ThrowsWhenChangingHeaderAfterReadOnlyIsSet() { - var headers = new FrameResponseHeaders(); + var headers = new HttpResponseHeaders(); var dictionary = (IDictionary)headers; dictionary.Add("my-header", new[] { "value" }); headers.SetReadOnly(); @@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void ThrowsWhenRemovingHeaderAfterReadOnlyIsSet() { - var headers = new FrameResponseHeaders(); + var headers = new HttpResponseHeaders(); var dictionary = (IDictionary)headers; dictionary.Add("my-header", new[] { "value" }); headers.SetReadOnly(); @@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void ThrowsWhenClearingHeadersAfterReadOnlyIsSet() { - var headers = new FrameResponseHeaders(); + var headers = new HttpResponseHeaders(); var dictionary = (IDictionary)headers; dictionary.Add("my-header", new[] { "value" }); headers.SetReadOnly(); @@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(BadContentLengths))] public void ThrowsWhenAddingContentLengthWithNonNumericValue(string contentLength) { - var headers = new FrameResponseHeaders(); + var headers = new HttpResponseHeaders(); var dictionary = (IDictionary)headers; var exception = Assert.Throws(() => dictionary.Add("Content-Length", new[] { contentLength })); @@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(BadContentLengths))] public void ThrowsWhenSettingContentLengthToNonNumericValue(string contentLength) { - var headers = new FrameResponseHeaders(); + var headers = new HttpResponseHeaders(); var dictionary = (IDictionary)headers; var exception = Assert.Throws(() => ((IHeaderDictionary)headers)["Content-Length"] = contentLength); @@ -174,7 +174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(BadContentLengths))] public void ThrowsWhenAssigningHeaderContentLengthToNonNumericValue(string contentLength) { - var headers = new FrameResponseHeaders(); + var headers = new HttpResponseHeaders(); var exception = Assert.Throws(() => headers.HeaderContentLength = contentLength); Assert.Equal(CoreStrings.FormatInvalidContentLength_InvalidNumber(contentLength), exception.Message); @@ -184,7 +184,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(GoodContentLengths))] public void ContentLengthValueCanBeReadAsLongAfterAddingHeader(string contentLength) { - var headers = new FrameResponseHeaders(); + var headers = new HttpResponseHeaders(); var dictionary = (IDictionary)headers; dictionary.Add("Content-Length", contentLength); @@ -195,7 +195,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(GoodContentLengths))] public void ContentLengthValueCanBeReadAsLongAfterSettingHeader(string contentLength) { - var headers = new FrameResponseHeaders(); + var headers = new HttpResponseHeaders(); var dictionary = (IDictionary)headers; dictionary["Content-Length"] = contentLength; @@ -206,7 +206,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(GoodContentLengths))] public void ContentLengthValueCanBeReadAsLongAfterAssigningHeader(string contentLength) { - var headers = new FrameResponseHeaders(); + var headers = new HttpResponseHeaders(); headers.HeaderContentLength = contentLength; Assert.Equal(ParseLong(contentLength), headers.ContentLength); @@ -215,7 +215,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void ContentLengthValueClearedWhenHeaderIsRemoved() { - var headers = new FrameResponseHeaders(); + var headers = new HttpResponseHeaders(); headers.HeaderContentLength = "42"; var dictionary = (IDictionary)headers; @@ -227,7 +227,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void ContentLengthValueClearedWhenHeadersCleared() { - var headers = new FrameResponseHeaders(); + var headers = new HttpResponseHeaders(); headers.HeaderContentLength = "42"; var dictionary = (IDictionary)headers; diff --git a/test/Kestrel.Core.Tests/FrameResponseStreamTests.cs b/test/Kestrel.Core.Tests/HttpResponseStreamTests.cs similarity index 66% rename from test/Kestrel.Core.Tests/FrameResponseStreamTests.cs rename to test/Kestrel.Core.Tests/HttpResponseStreamTests.cs index 14cc4ebb7e..cac27c5f52 100644 --- a/test/Kestrel.Core.Tests/FrameResponseStreamTests.cs +++ b/test/Kestrel.Core.Tests/HttpResponseStreamTests.cs @@ -13,82 +13,82 @@ using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { - public class FrameResponseStreamTests + public class HttpResponseStreamTests { [Fact] public void CanReadReturnsFalse() { - var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl()); + var stream = new HttpResponseStream(Mock.Of(), new MockHttpResponseControl()); Assert.False(stream.CanRead); } [Fact] public void CanSeekReturnsFalse() { - var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl()); + var stream = new HttpResponseStream(Mock.Of(), new MockHttpResponseControl()); Assert.False(stream.CanSeek); } [Fact] public void CanWriteReturnsTrue() { - var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl()); + var stream = new HttpResponseStream(Mock.Of(), new MockHttpResponseControl()); Assert.True(stream.CanWrite); } [Fact] public void ReadThrows() { - var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl()); + var stream = new HttpResponseStream(Mock.Of(), new MockHttpResponseControl()); Assert.Throws(() => stream.Read(new byte[1], 0, 1)); } [Fact] public void ReadByteThrows() { - var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl()); + var stream = new HttpResponseStream(Mock.Of(), new MockHttpResponseControl()); Assert.Throws(() => stream.ReadByte()); } [Fact] public async Task ReadAsyncThrows() { - var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl()); + var stream = new HttpResponseStream(Mock.Of(), new MockHttpResponseControl()); await Assert.ThrowsAsync(() => stream.ReadAsync(new byte[1], 0, 1)); } [Fact] public void BeginReadThrows() { - var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl()); + var stream = new HttpResponseStream(Mock.Of(), new MockHttpResponseControl()); Assert.Throws(() => stream.BeginRead(new byte[1], 0, 1, null, null)); } [Fact] public void SeekThrows() { - var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl()); + var stream = new HttpResponseStream(Mock.Of(), new MockHttpResponseControl()); Assert.Throws(() => stream.Seek(0, SeekOrigin.Begin)); } [Fact] public void LengthThrows() { - var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl()); + var stream = new HttpResponseStream(Mock.Of(), new MockHttpResponseControl()); Assert.Throws(() => stream.Length); } [Fact] public void SetLengthThrows() { - var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl()); + var stream = new HttpResponseStream(Mock.Of(), new MockHttpResponseControl()); Assert.Throws(() => stream.SetLength(0)); } [Fact] public void PositionThrows() { - var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl()); + var stream = new HttpResponseStream(Mock.Of(), new MockHttpResponseControl()); Assert.Throws(() => stream.Position); Assert.Throws(() => stream.Position = 0); } @@ -96,7 +96,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void StopAcceptingWritesCausesWriteToThrowObjectDisposedException() { - var stream = new FrameResponseStream(Mock.Of(), Mock.Of()); + var stream = new HttpResponseStream(Mock.Of(), Mock.Of()); stream.StartAcceptingWrites(); stream.StopAcceptingWrites(); Assert.Throws(() => { stream.WriteAsync(new byte[1], 0, 1); }); @@ -108,10 +108,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var allowSynchronousIO = false; var mockBodyControl = new Mock(); mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(() => allowSynchronousIO); - var mockFrameControl = new Mock(); - mockFrameControl.Setup(m => m.WriteAsync(It.IsAny>(), CancellationToken.None)).Returns(Task.CompletedTask); + var mockHttpResponseControl = new Mock(); + mockHttpResponseControl.Setup(m => m.WriteAsync(It.IsAny>(), CancellationToken.None)).Returns(Task.CompletedTask); - var stream = new FrameResponseStream(mockBodyControl.Object, mockFrameControl.Object); + var stream = new HttpResponseStream(mockBodyControl.Object, mockHttpResponseControl.Object); stream.StartAcceptingWrites(); // WriteAsync doesn't throw. diff --git a/test/Kestrel.Core.Tests/MessageBodyTests.cs b/test/Kestrel.Core.Tests/MessageBodyTests.cs index 58d7c5b480..76ecd86f7f 100644 --- a/test/Kestrel.Core.Tests/MessageBodyTests.cs +++ b/test/Kestrel.Core.Tests/MessageBodyTests.cs @@ -28,10 +28,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame); + var body = Http1MessageBody.For(httpVersion, new HttpRequestHeaders { HeaderContentLength = "5" }, input.Http1Connection); var mockBodyControl = new Mock(); mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(true); - var stream = new FrameRequestStream(mockBodyControl.Object); + var stream = new HttpRequestStream(mockBodyControl.Object); stream.StartAcceptingReads(body); input.Add("Hello"); @@ -56,8 +56,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(httpVersion, new HttpRequestHeaders { HeaderContentLength = "5" }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); input.Add("Hello"); @@ -80,10 +80,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Http1Connection); var mockBodyControl = new Mock(); mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(true); - var stream = new FrameRequestStream(mockBodyControl.Object); + var stream = new HttpRequestStream(mockBodyControl.Object); stream.StartAcceptingReads(body); input.Add("5\r\nHello\r\n"); @@ -108,8 +108,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); input.Add("5\r\nHello\r\n"); @@ -134,8 +134,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); input.Add("5;\r\0"); @@ -159,8 +159,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); input.Add("80000000\r\n"); @@ -180,8 +180,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); input.Add("012345678\r"); @@ -203,10 +203,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame); + var body = Http1MessageBody.For(httpVersion, new HttpRequestHeaders { HeaderConnection = "upgrade" }, input.Http1Connection); var mockBodyControl = new Mock(); mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(true); - var stream = new FrameRequestStream(mockBodyControl.Object); + var stream = new HttpRequestStream(mockBodyControl.Object); stream.StartAcceptingReads(body); input.Add("Hello"); @@ -230,8 +230,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(httpVersion, new HttpRequestHeaders { HeaderConnection = "upgrade" }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); input.Add("Hello"); @@ -255,10 +255,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame); + var body = Http1MessageBody.For(httpVersion, new HttpRequestHeaders(), input.Http1Connection); var mockBodyControl = new Mock(); mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(true); - var stream = new FrameRequestStream(mockBodyControl.Object); + var stream = new HttpRequestStream(mockBodyControl.Object); stream.StartAcceptingReads(body); input.Add("Hello"); @@ -277,8 +277,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(httpVersion, new HttpRequestHeaders(), input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); input.Add("Hello"); @@ -295,8 +295,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "8197" }, input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(HttpVersion.Http10, new HttpRequestHeaders { HeaderContentLength = "8197" }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); // Input needs to be greater than 4032 bytes to allocate a block not backed by a slab. @@ -324,7 +324,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests using (var input = new TestInput()) { var ex = Assert.Throws(() => - MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked, not-chunked" }, input.Frame)); + Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderTransferEncoding = "chunked, not-chunked" }, input.Http1Connection)); Assert.Equal(StatusCodes.Status400BadRequest, ex.StatusCode); Assert.Equal(CoreStrings.FormatBadRequest_FinalTransferCodingNotChunked("chunked, not-chunked"), ex.Message); @@ -338,9 +338,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - input.Frame.Method = method; + input.Http1Connection.Method = method; var ex = Assert.Throws(() => - MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders(), input.Frame)); + Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders(), input.Http1Connection)); Assert.Equal(StatusCodes.Status411LengthRequired, ex.StatusCode); Assert.Equal(CoreStrings.FormatBadRequest_LengthRequired(method), ex.Message); @@ -354,9 +354,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - input.Frame.Method = method; + input.Http1Connection.Method = method; var ex = Assert.Throws(() => - MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders(), input.Frame)); + Http1MessageBody.For(HttpVersion.Http10, new HttpRequestHeaders(), input.Http1Connection)); Assert.Equal(StatusCodes.Status400BadRequest, ex.StatusCode); Assert.Equal(CoreStrings.FormatBadRequest_LengthRequiredHttp10(method), ex.Message); @@ -368,7 +368,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame); + var body = Http1MessageBody.For(HttpVersion.Http10, new HttpRequestHeaders { HeaderContentLength = "5" }, input.Http1Connection); input.Add("Hello"); @@ -388,7 +388,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame); + var body = Http1MessageBody.For(HttpVersion.Http10, new HttpRequestHeaders { HeaderContentLength = "5" }, input.Http1Connection); input.Add("Hello"); @@ -409,9 +409,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public static IEnumerable RequestData => new[] { // Content-Length - new object[] { new FrameRequestHeaders { HeaderContentLength = "12" }, new[] { "Hello ", "World!" } }, + new object[] { new HttpRequestHeaders { HeaderContentLength = "12" }, new[] { "Hello ", "World!" } }, // Chunked - new object[] { new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, new[] { "6\r\nHello \r\n", "6\r\nWorld!\r\n0\r\n\r\n" } }, + new object[] { new HttpRequestHeaders { HeaderTransferEncoding = "chunked" }, new[] { "6\r\nHello \r\n", "6\r\nWorld!\r\n0\r\n\r\n" } }, }; public static IEnumerable CombinedData => @@ -421,7 +421,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Theory] [MemberData(nameof(RequestData))] - public async Task CopyToAsyncDoesNotCopyBlocks(FrameRequestHeaders headers, string[] data) + public async Task CopyToAsyncDoesNotCopyBlocks(HttpRequestHeaders headers, string[] data) { var writeCount = 0; var writeTcs = new TaskCompletionSource(); @@ -438,7 +438,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests using (var input = new TestInput()) { - var body = MessageBody.For(HttpVersion.Http11, headers, input.Frame); + var body = Http1MessageBody.For(HttpVersion.Http11, headers, input.Http1Connection); var copyToAsyncTask = body.CopyToAsync(mockDestination.Object); @@ -487,8 +487,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderConnection = headerConnection }, input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderConnection = headerConnection }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); input.Add("Hello"); @@ -508,8 +508,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderContentLength = "2" }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); // Add some input and consume it to ensure PumpAsync is running @@ -531,8 +531,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { using (var input = new TestInput()) { - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderContentLength = "2" }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); // Add some input and consume it to ensure PumpAsync is running @@ -557,16 +557,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var mockTimeoutControl = new Mock(); - input.FrameContext.TimeoutControl = mockTimeoutControl.Object; + input.Http1ConnectionContext.TimeoutControl = mockTimeoutControl.Object; - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderContentLength = "5" }, input.Http1Connection); // Add some input and read it to start PumpAsync input.Add("a"); Assert.Equal(1, await body.ReadAsync(new ArraySegment(new byte[1]))); // Time out on the next read - input.Frame.SendTimeoutResponse(); + input.Http1Connection.SendTimeoutResponse(); var exception = await Assert.ThrowsAsync(() => body.ReadAsync(new ArraySegment(new byte[1]))); Assert.Equal(StatusCodes.Status408RequestTimeout, exception.StatusCode); @@ -582,16 +582,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var mockTimeoutControl = new Mock(); - input.FrameContext.TimeoutControl = mockTimeoutControl.Object; + input.Http1ConnectionContext.TimeoutControl = mockTimeoutControl.Object; - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderContentLength = "5" }, input.Http1Connection); // Add some input and read it to start PumpAsync input.Add("a"); Assert.Equal(1, await body.ReadAsync(new ArraySegment(new byte[1]))); // Time out on the next read - input.Frame.SendTimeoutResponse(); + input.Http1Connection.SendTimeoutResponse(); var exception = await Assert.ThrowsAsync(() => body.ConsumeAsync()); Assert.Equal(StatusCodes.Status408RequestTimeout, exception.StatusCode); @@ -607,16 +607,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var mockTimeoutControl = new Mock(); - input.FrameContext.TimeoutControl = mockTimeoutControl.Object; + input.Http1ConnectionContext.TimeoutControl = mockTimeoutControl.Object; - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderContentLength = "5" }, input.Http1Connection); // Add some input and read it to start PumpAsync input.Add("a"); Assert.Equal(1, await body.ReadAsync(new ArraySegment(new byte[1]))); // Time out on the next read - input.Frame.SendTimeoutResponse(); + input.Http1Connection.SendTimeoutResponse(); using (var ms = new MemoryStream()) { @@ -634,12 +634,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests using (var input = new TestInput()) { var mockLogger = new Mock(); - input.Frame.ServiceContext.Log = mockLogger.Object; - input.Frame.ConnectionIdFeature = "ConnectionId"; - input.Frame.TraceIdentifier = "RequestId"; + input.Http1Connection.ServiceContext.Log = mockLogger.Object; + input.Http1Connection.ConnectionIdFeature = "ConnectionId"; + input.Http1Connection.TraceIdentifier = "RequestId"; - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderContentLength = "2" }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); // Add some input and consume it to ensure PumpAsync is running @@ -664,12 +664,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests mockLogger .Setup(logger => logger.RequestBodyDone("ConnectionId", "RequestId")) .Callback(() => logEvent.Set()); - input.Frame.ServiceContext.Log = mockLogger.Object; - input.Frame.ConnectionIdFeature = "ConnectionId"; - input.Frame.TraceIdentifier = "RequestId"; + input.Http1Connection.ServiceContext.Log = mockLogger.Object; + input.Http1Connection.ConnectionIdFeature = "ConnectionId"; + input.Http1Connection.TraceIdentifier = "RequestId"; - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame); - var stream = new FrameRequestStream(Mock.Of()); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderContentLength = "2" }, input.Http1Connection); + var stream = new HttpRequestStream(Mock.Of()); stream.StartAcceptingReads(body); // Add some input and consume it to ensure PumpAsync is running @@ -690,9 +690,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests using (var input = new TestInput()) { var mockTimeoutControl = new Mock(); - input.FrameContext.TimeoutControl = mockTimeoutControl.Object; + input.Http1ConnectionContext.TimeoutControl = mockTimeoutControl.Object; - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "12" }, input.Frame); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderContentLength = "12" }, input.Http1Connection); // Add some input and read it to start PumpAsync input.Add("hello,"); @@ -701,7 +701,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests input.Add(" world"); Assert.Equal(6, await body.ReadAsync(new ArraySegment(new byte[6]))); - // Due to the limits set on Frame.RequestBodyPipe, backpressure should be triggered on every write to that pipe. + // Due to the limits set on HttpProtocol.RequestBodyPipe, backpressure should be triggered on every write to that pipe. mockTimeoutControl.Verify(timeoutControl => timeoutControl.PauseTimingReads(), Times.Exactly(2)); mockTimeoutControl.Verify(timeoutControl => timeoutControl.ResumeTimingReads(), Times.Exactly(2)); } @@ -715,20 +715,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var produceContinueCalled = false; var startTimingReadsCalledAfterProduceContinue = false; - var mockFrameControl = new Mock(); - mockFrameControl - .Setup(frameControl => frameControl.ProduceContinue()) + var mockHttpResponseControl = new Mock(); + mockHttpResponseControl + .Setup(httpResponseControl => httpResponseControl.ProduceContinue()) .Callback(() => produceContinueCalled = true); - input.Frame.FrameControl = mockFrameControl.Object; + input.Http1Connection.HttpResponseControl = mockHttpResponseControl.Object; var mockTimeoutControl = new Mock(); mockTimeoutControl .Setup(timeoutControl => timeoutControl.StartTimingReads()) .Callback(() => startTimingReadsCalledAfterProduceContinue = produceContinueCalled); - input.FrameContext.TimeoutControl = mockTimeoutControl.Object; + input.Http1ConnectionContext.TimeoutControl = mockTimeoutControl.Object; - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderContentLength = "5" }, input.Http1Connection); // Add some input and read it to start PumpAsync var readTask = body.ReadAsync(new ArraySegment(new byte[1])); @@ -748,9 +748,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests using (var input = new TestInput()) { var mockTimeoutControl = new Mock(); - input.FrameContext.TimeoutControl = mockTimeoutControl.Object; + input.Http1ConnectionContext.TimeoutControl = mockTimeoutControl.Object; - var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame); + var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderConnection = "upgrade" }, input.Http1Connection); // Add some input and read it to start PumpAsync input.Add("a"); @@ -763,7 +763,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests mockTimeoutControl.Verify(timeoutControl => timeoutControl.StartTimingReads(), Times.Never); mockTimeoutControl.Verify(timeoutControl => timeoutControl.StopTimingReads(), Times.Never); - // Due to the limits set on Frame.RequestBodyPipe, backpressure should be triggered on every + // Due to the limits set on HttpProtocol.RequestBodyPipe, backpressure should be triggered on every // write to that pipe. Verify that read timing pause and resume are not called on upgrade // requests. mockTimeoutControl.Verify(timeoutControl => timeoutControl.PauseTimingReads(), Times.Never); diff --git a/test/Kestrel.Core.Tests/OutputProducerTests.cs b/test/Kestrel.Core.Tests/OutputProducerTests.cs index f95b13052e..2beb0fcda6 100644 --- a/test/Kestrel.Core.Tests/OutputProducerTests.cs +++ b/test/Kestrel.Core.Tests/OutputProducerTests.cs @@ -50,11 +50,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } } - private OutputProducer CreateOutputProducer(PipeOptions pipeOptions) + private Http1OutputProducer CreateOutputProducer(PipeOptions pipeOptions) { var pipe = _pipeFactory.Create(pipeOptions); var serviceContext = new TestServiceContext(); - var socketOutput = new OutputProducer( + var socketOutput = new Http1OutputProducer( pipe.Reader, pipe.Writer, "0", diff --git a/test/Kestrel.Core.Tests/PipeOptionsTests.cs b/test/Kestrel.Core.Tests/PipeOptionsTests.cs index 2d0db7c073..530d2698d6 100644 --- a/test/Kestrel.Core.Tests/PipeOptionsTests.cs +++ b/test/Kestrel.Core.Tests/PipeOptionsTests.cs @@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var serviceContext = new TestServiceContext(); serviceContext.ServerOptions.Limits.MaxRequestBufferSize = maxRequestBufferSize; - var connectionLifetime = new FrameConnection(new FrameConnectionContext + var connectionLifetime = new HttpConnection(new HttpConnectionContext { ServiceContext = serviceContext }); @@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var serviceContext = new TestServiceContext(); serviceContext.ServerOptions.Limits.MaxResponseBufferSize = maxRequestBufferSize; - var connectionLifetime = new FrameConnection(new FrameConnectionContext + var connectionLifetime = new HttpConnection(new HttpConnectionContext { ServiceContext = serviceContext }); diff --git a/test/Kestrel.Core.Tests/StreamsTests.cs b/test/Kestrel.Core.Tests/StreamsTests.cs index 7b6d73c1c1..e80b745640 100644 --- a/test/Kestrel.Core.Tests/StreamsTests.cs +++ b/test/Kestrel.Core.Tests/StreamsTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public async Task StreamsThrowAfterAbort() { - var streams = new Streams(Mock.Of(), Mock.Of()); + var streams = new Streams(Mock.Of(), Mock.Of()); var (request, response) = streams.Start(new MockMessageBody()); var ex = new Exception("My error"); @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public async Task StreamsThrowOnAbortAfterUpgrade() { - var streams = new Streams(Mock.Of(), Mock.Of()); + var streams = new Streams(Mock.Of(), Mock.Of()); var (request, response) = streams.Start(new MockMessageBody(upgradeable: true)); var upgrade = streams.Upgrade(); @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public async Task StreamsThrowOnUpgradeAfterAbort() { - var streams = new Streams(Mock.Of(), Mock.Of()); + var streams = new Streams(Mock.Of(), Mock.Of()); var (request, response) = streams.Start(new MockMessageBody(upgradeable: true)); var ex = new Exception("My error"); @@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await upgrade.WriteAsync(new byte[1], 0, 1); } - private class MockMessageBody : MessageBody + private class MockMessageBody : Http1MessageBody { public MockMessageBody(bool upgradeable = false) : base(null) diff --git a/test/Kestrel.Core.Tests/TestHelpers/MockFrameControl.cs b/test/Kestrel.Core.Tests/TestHelpers/MockHttpResponseControl.cs similarity index 91% rename from test/Kestrel.Core.Tests/TestHelpers/MockFrameControl.cs rename to test/Kestrel.Core.Tests/TestHelpers/MockHttpResponseControl.cs index df99b9fb05..2ff611f49e 100644 --- a/test/Kestrel.Core.Tests/TestHelpers/MockFrameControl.cs +++ b/test/Kestrel.Core.Tests/TestHelpers/MockHttpResponseControl.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests.TestHelpers { - public class MockFrameControl : IFrameControl + public class MockHttpResponseControl : IHttpResponseControl { public Task FlushAsync(CancellationToken cancellationToken) { diff --git a/test/Kestrel.Core.Tests/TestInput.cs b/test/Kestrel.Core.Tests/TestInput.cs index 9033c90d24..9905a4a965 100644 --- a/test/Kestrel.Core.Tests/TestInput.cs +++ b/test/Kestrel.Core.Tests/TestInput.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Transport = pair.Transport; Application = pair.Application; - FrameContext = new FrameContext + Http1ConnectionContext = new Http1ConnectionContext { ServiceContext = new TestServiceContext(), ConnectionFeatures = new FeatureCollection(), @@ -35,8 +35,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests TimeoutControl = Mock.Of() }; - Frame = new Frame(null, FrameContext); - Frame.FrameControl = Mock.Of(); + Http1Connection = new Http1Connection(null, Http1ConnectionContext); + Http1Connection.HttpResponseControl = Mock.Of(); } public IPipeConnection Transport { get; } @@ -45,9 +45,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public PipeFactory PipeFactory => _pipelineFactory; - public FrameContext FrameContext { get; } + public Http1ConnectionContext Http1ConnectionContext { get; } - public Frame Frame { get; set; } + public Http1Connection Http1Connection { get; set; } public void Add(string text) { diff --git a/test/Kestrel.FunctionalTests/GeneratedCodeTests.cs b/test/Kestrel.FunctionalTests/GeneratedCodeTests.cs index 548a028a8a..a9b5ffc524 100644 --- a/test/Kestrel.FunctionalTests/GeneratedCodeTests.cs +++ b/test/Kestrel.FunctionalTests/GeneratedCodeTests.cs @@ -12,41 +12,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [Fact] public void GeneratedCodeIsUpToDate() { - const string frameHeadersGeneratedPath = "../../../../../src/Kestrel.Core/Internal/Http/FrameHeaders.Generated.cs"; - const string frameGeneratedPath = "../../../../../src/Kestrel.Core/Internal/Http/Frame.Generated.cs"; - const string http2StreamGeneratedPath = "../../../../../src/Kestrel.Core/Internal/Http2/Http2Stream.Generated.cs"; + const string httpHeadersGeneratedPath = "../../../../../src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs"; + const string httpProtocolGeneratedPath = "../../../../../src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs"; const string httpUtilitiesGeneratedPath = "../../../../../src/Kestrel.Core/Internal/Infrastructure/HttpUtilities.Generated.cs"; - var testFrameHeadersGeneratedPath = Path.GetTempFileName(); - var testFrameGeneratedPath = Path.GetTempFileName(); - var testHttp2StreamGeneratedPath = Path.GetTempFileName(); + var testHttpHeadersGeneratedPath = Path.GetTempFileName(); + var testHttpProtocolGeneratedPath = Path.GetTempFileName(); var testHttpUtilitiesGeneratedPath = Path.GetTempFileName(); try { - var currentFrameHeadersGenerated = File.ReadAllText(frameHeadersGeneratedPath); - var currentFrameGenerated = File.ReadAllText(frameGeneratedPath); - var currentHttp2StreamGenerated = File.ReadAllText(http2StreamGeneratedPath); + var currentHttpHeadersGenerated = File.ReadAllText(httpHeadersGeneratedPath); + var currentHttpProtocolGenerated = File.ReadAllText(httpProtocolGeneratedPath); var currentHttpUtilitiesGenerated = File.ReadAllText(httpUtilitiesGeneratedPath); - CodeGenerator.Program.Run(testFrameHeadersGeneratedPath, testFrameGeneratedPath, testHttp2StreamGeneratedPath, testHttpUtilitiesGeneratedPath); + CodeGenerator.Program.Run(testHttpHeadersGeneratedPath, testHttpProtocolGeneratedPath, testHttpUtilitiesGeneratedPath); - var testFrameHeadersGenerated = File.ReadAllText(testFrameHeadersGeneratedPath); - var testFrameGenerated = File.ReadAllText(testFrameGeneratedPath); - var testHttp2StreamGenerated = File.ReadAllText(testHttp2StreamGeneratedPath); + var testHttpHeadersGenerated = File.ReadAllText(testHttpHeadersGeneratedPath); + var testHttpProtocolGenerated = File.ReadAllText(testHttpProtocolGeneratedPath); var testHttpUtilitiesGenerated = File.ReadAllText(testHttpUtilitiesGeneratedPath); - Assert.Equal(currentFrameHeadersGenerated, testFrameHeadersGenerated, ignoreLineEndingDifferences: true); - Assert.Equal(currentFrameGenerated, testFrameGenerated, ignoreLineEndingDifferences: true); - Assert.Equal(currentHttp2StreamGenerated, testHttp2StreamGenerated, ignoreLineEndingDifferences: true); + Assert.Equal(currentHttpHeadersGenerated, testHttpHeadersGenerated, ignoreLineEndingDifferences: true); + Assert.Equal(currentHttpProtocolGenerated, testHttpProtocolGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentHttpUtilitiesGenerated, testHttpUtilitiesGenerated, ignoreLineEndingDifferences: true); } finally { - File.Delete(testFrameHeadersGeneratedPath); - File.Delete(testFrameGeneratedPath); - File.Delete(testHttp2StreamGeneratedPath); + File.Delete(testHttpHeadersGeneratedPath); + File.Delete(testHttpProtocolGeneratedPath); File.Delete(testHttpUtilitiesGeneratedPath); } } diff --git a/test/Kestrel.FunctionalTests/FrameConnectionManagerTests.cs b/test/Kestrel.FunctionalTests/HttpConnectionManagerTests.cs similarity index 96% rename from test/Kestrel.FunctionalTests/FrameConnectionManagerTests.cs rename to test/Kestrel.FunctionalTests/HttpConnectionManagerTests.cs index 3ecd575884..50c5181b4e 100644 --- a/test/Kestrel.FunctionalTests/FrameConnectionManagerTests.cs +++ b/test/Kestrel.FunctionalTests/HttpConnectionManagerTests.cs @@ -14,7 +14,7 @@ using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { - public class FrameConnectionManagerTests + public class HttpConnectionManagerTests { // This test causes MemoryPoolBlocks to be finalized which in turn causes an assert failure in debug builds. #if !DEBUG @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { //////////////////////////////////////////////////////////////////////////////////////// // WARNING: This test will fail under a debugger because Task.s_currentActiveTasks // - // roots FrameConnection. // + // roots HttpConnection. // //////////////////////////////////////////////////////////////////////////////////////// var logWh = new SemaphoreSlim(0); diff --git a/test/Kestrel.FunctionalTests/RequestTests.cs b/test/Kestrel.FunctionalTests/RequestTests.cs index 281e5e057f..1362651bde 100644 --- a/test/Kestrel.FunctionalTests/RequestTests.cs +++ b/test/Kestrel.FunctionalTests/RequestTests.cs @@ -1111,7 +1111,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests if (firstRequest) { originalRequestHeaders = requestFeature.Headers; - requestFeature.Headers = new FrameRequestHeaders(); + requestFeature.Headers = new HttpRequestHeaders(); firstRequest = false; } else diff --git a/test/Kestrel.FunctionalTests/ResponseTests.cs b/test/Kestrel.FunctionalTests/ResponseTests.cs index 05328b3a2c..d19ffbfb14 100644 --- a/test/Kestrel.FunctionalTests/ResponseTests.cs +++ b/test/Kestrel.FunctionalTests/ResponseTests.cs @@ -154,7 +154,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests context.Response.OnStarting(() => Task.Run(() => onStartingCalled = true)); context.Response.OnCompleted(() => Task.Run(() => onCompletedCalled = true)); - // Prevent OnStarting call (see Frame.ProcessRequestsAsync()). + // Prevent OnStarting call (see HttpProtocol.ProcessRequestsAsync()). throw new Exception(); }); }); @@ -501,7 +501,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests ""); // Wait for message to be logged before disposing the socket. - // Disposing the socket will abort the connection and Frame._requestAborted + // Disposing the socket will abort the connection and HttpProtocol._requestAborted // might be 1 by the time ProduceEnd() gets called and the message is logged. await logTcs.Task.TimeoutAfter(TimeSpan.FromSeconds(10)); } @@ -736,7 +736,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests httpContext.Response.ContentLength = 12; await httpContext.Response.WriteAsync("hello,"); - // Wait until the request is aborted so we know Frame will skip the response content length check. + // Wait until the request is aborted so we know HttpProtocol will skip the response content length check. Assert.True(await requestAborted.WaitAsync(TimeSpan.FromSeconds(10))); }, new TestServiceContext { Log = mockTrace.Object })) { @@ -1526,12 +1526,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests "a", ""); - // This will be consumed by Frame when it attempts to + // This will be consumed by Http1Connection when it attempts to // consume the request body and will cause an error. await connection.Send( "gg"); - // If 100 Continue sets Frame.HasResponseStarted to true, + // If 100 Continue sets HttpProtocol.HasResponseStarted to true, // a success response will be produced before the server sees the // bad chunk header above, making this test fail. await connection.ReceiveForcedEnd( @@ -2215,7 +2215,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests if (firstRequest) { originalResponseHeaders = responseFeature.Headers; - responseFeature.Headers = new FrameResponseHeaders(); + responseFeature.Headers = new HttpResponseHeaders(); firstRequest = false; } else diff --git a/test/Kestrel.FunctionalTests/UpgradeTests.cs b/test/Kestrel.FunctionalTests/UpgradeTests.cs index d2578dd393..6d77b7758b 100644 --- a/test/Kestrel.FunctionalTests/UpgradeTests.cs +++ b/test/Kestrel.FunctionalTests/UpgradeTests.cs @@ -260,7 +260,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests const int limit = 10; var upgradeTcs = new TaskCompletionSource(); var serviceContext = new TestServiceContext(); - serviceContext.ConnectionManager = new FrameConnectionManager(serviceContext.Log, ResourceCounter.Quota(limit)); + serviceContext.ConnectionManager = new HttpConnectionManager(serviceContext.Log, ResourceCounter.Quota(limit)); using (var server = new TestServer(async context => { diff --git a/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs b/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs index 09a88c883d..be256e30d0 100644 --- a/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs +++ b/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs @@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var buffer = new ArraySegment(new byte[bufferSize], 0, bufferSize); // Act - var writeTask = outputProducer.WriteAsync(buffer); + var writeTask = outputProducer.WriteDataAsync(buffer); // Assert await writeTask.TimeoutAfter(TimeSpan.FromSeconds(5)); @@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var buffer = new ArraySegment(new byte[bufferSize], 0, bufferSize); // Act - var writeTask = outputProducer.WriteAsync(buffer); + var writeTask = outputProducer.WriteDataAsync(buffer); // Assert await writeTask.TimeoutAfter(TimeSpan.FromSeconds(5)); @@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var buffer = new ArraySegment(new byte[bufferSize], 0, bufferSize); // Act - var writeTask = outputProducer.WriteAsync(buffer); + var writeTask = outputProducer.WriteDataAsync(buffer); // Assert Assert.False(writeTask.IsCompleted); @@ -211,14 +211,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var buffer = new ArraySegment(new byte[bufferSize], 0, bufferSize); // Act - var writeTask1 = outputProducer.WriteAsync(buffer); + var writeTask1 = outputProducer.WriteDataAsync(buffer); // Assert // The first write should pre-complete since it is <= _maxBytesPreCompleted. Assert.Equal(TaskStatus.RanToCompletion, writeTask1.Status); // Act - var writeTask2 = outputProducer.WriteAsync(buffer); + var writeTask2 = outputProducer.WriteDataAsync(buffer); await _mockLibuv.OnPostTask; // Assert @@ -275,7 +275,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var halfWriteBehindBuffer = new ArraySegment(data, 0, bufferSize); // Act - var writeTask1 = outputProducer.WriteAsync(halfWriteBehindBuffer); + var writeTask1 = outputProducer.WriteDataAsync(halfWriteBehindBuffer); // Assert // The first write should pre-complete since it is <= _maxBytesPreCompleted. @@ -291,10 +291,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests halfWriteBehindBuffer); // Act - var writeTask2 = outputProducer.WriteAsync(halfWriteBehindBuffer); + var writeTask2 = outputProducer.WriteDataAsync(halfWriteBehindBuffer); Assert.False(writeTask2.IsCompleted); - var writeTask3 = outputProducer.WriteAsync(halfWriteBehindBuffer); + var writeTask3 = outputProducer.WriteDataAsync(halfWriteBehindBuffer); Assert.False(writeTask3.IsCompleted); // Drain the write queue @@ -345,7 +345,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var fullBuffer = new ArraySegment(data, 0, bufferSize); // Act - var task1Success = outputProducer.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token); + var task1Success = outputProducer.WriteDataAsync(fullBuffer, cancellationToken: abortedSource.Token); // task1 should complete successfully as < _maxBytesPreCompleted // First task is completed and successful @@ -354,8 +354,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests Assert.False(task1Success.IsFaulted); // following tasks should wait. - var task2Success = outputProducer.WriteAsync(fullBuffer); - var task3Canceled = outputProducer.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token); + var task2Success = outputProducer.WriteDataAsync(fullBuffer); + var task3Canceled = outputProducer.WriteDataAsync(fullBuffer, cancellationToken: abortedSource.Token); // Give time for tasks to percolate await _mockLibuv.OnPostTask; @@ -383,7 +383,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests // A final write guarantees that the error is observed by OutputProducer, // but doesn't return a canceled/faulted task. - var task4Success = outputProducer.WriteAsync(fullBuffer, cancellationToken: default(CancellationToken)); + var task4Success = outputProducer.WriteDataAsync(fullBuffer, cancellationToken: default(CancellationToken)); Assert.True(task4Success.IsCompleted); Assert.False(task4Success.IsCanceled); Assert.False(task4Success.IsFaulted); @@ -437,7 +437,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var fullBuffer = new ArraySegment(data, 0, bufferSize); // Act - var task1Success = outputProducer.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token); + var task1Success = outputProducer.WriteDataAsync(fullBuffer, cancellationToken: abortedSource.Token); // task1 should complete successfully as < _maxBytesPreCompleted // First task is completed and successful @@ -446,7 +446,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests Assert.False(task1Success.IsFaulted); // following tasks should wait. - var task3Canceled = outputProducer.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token); + var task3Canceled = outputProducer.WriteDataAsync(fullBuffer, cancellationToken: abortedSource.Token); // Give time for tasks to percolate await _mockLibuv.OnPostTask; @@ -466,7 +466,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests // A final write guarantees that the error is observed by OutputProducer, // but doesn't return a canceled/faulted task. - var task4Success = outputProducer.WriteAsync(fullBuffer); + var task4Success = outputProducer.WriteDataAsync(fullBuffer); Assert.True(task4Success.IsCompleted); Assert.False(task4Success.IsCanceled); Assert.False(task4Success.IsFaulted); @@ -520,7 +520,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var fullBuffer = new ArraySegment(data, 0, bufferSize); // Act - var task1Waits = outputProducer.WriteAsync(fullBuffer); + var task1Waits = outputProducer.WriteDataAsync(fullBuffer); // First task is not completed Assert.False(task1Waits.IsCompleted); @@ -528,7 +528,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests Assert.False(task1Waits.IsFaulted); // following tasks should wait. - var task3Canceled = outputProducer.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token); + var task3Canceled = outputProducer.WriteDataAsync(fullBuffer, cancellationToken: abortedSource.Token); // Give time for tasks to percolate await _mockLibuv.OnPostTask; @@ -553,7 +553,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests // A final write guarantees that the error is observed by OutputProducer, // but doesn't return a canceled/faulted task. - var task4Success = outputProducer.WriteAsync(fullBuffer); + var task4Success = outputProducer.WriteDataAsync(fullBuffer); Assert.True(task4Success.IsCompleted); Assert.False(task4Success.IsCanceled); Assert.False(task4Success.IsFaulted); @@ -599,7 +599,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var buffer = new ArraySegment(new byte[bufferSize], 0, bufferSize); // Act (Pre-complete the maximum number of bytes in preparation for the rest of the test) - var writeTask1 = outputProducer.WriteAsync(buffer); + var writeTask1 = outputProducer.WriteDataAsync(buffer); // Assert // The first write should pre-complete since it is < _maxBytesPreCompleted. @@ -608,8 +608,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests Assert.NotEmpty(completeQueue); // Act - var writeTask2 = outputProducer.WriteAsync(buffer); - var writeTask3 = outputProducer.WriteAsync(buffer); + var writeTask2 = outputProducer.WriteDataAsync(buffer); + var writeTask3 = outputProducer.WriteDataAsync(buffer); await _mockLibuv.OnPostTask; @@ -661,8 +661,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests // Two calls to WriteAsync trigger uv_write once if both calls // are made before write is scheduled - var ignore = outputProducer.WriteAsync(buffer); - ignore = outputProducer.WriteAsync(buffer); + var ignore = outputProducer.WriteDataAsync(buffer); + ignore = outputProducer.WriteDataAsync(buffer); _mockLibuv.KestrelThreadBlocker.Set(); @@ -682,7 +682,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests } } - private OutputProducer CreateOutputProducer(PipeOptions pipeOptions, CancellationTokenSource cts = null) + private Http1OutputProducer CreateOutputProducer(PipeOptions pipeOptions, CancellationTokenSource cts = null) { var pair = _pipeFactory.CreateConnectionPair(pipeOptions, pipeOptions); @@ -697,7 +697,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var socket = new MockSocket(_mockLibuv, _libuvThread.Loop.ThreadId, transportContext.Log); var consumer = new LibuvOutputConsumer(pair.Application.Input, _libuvThread, socket, "0", transportContext.Log); - var frame = new Frame(null, new FrameContext + var http1Connection = new Http1Connection(null, new Http1ConnectionContext { ServiceContext = serviceContext, ConnectionFeatures = new FeatureCollection(), @@ -709,15 +709,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests if (cts != null) { - frame.RequestAborted.Register(cts.Cancel); + http1Connection.RequestAborted.Register(cts.Cancel); } - var ignore = WriteOutputAsync(consumer, pair.Application.Input, frame); + var ignore = WriteOutputAsync(consumer, pair.Application.Input, http1Connection); - return frame.Output; + return (Http1OutputProducer)http1Connection.Output; } - private async Task WriteOutputAsync(LibuvOutputConsumer consumer, IPipeReader outputReader, Frame frame) + private async Task WriteOutputAsync(LibuvOutputConsumer consumer, IPipeReader outputReader, Http1Connection http1Connection) { // This WriteOutputAsync() calling code is equivalent to that in LibuvConnection. try @@ -726,14 +726,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests // Without ConfigureAwait(false), xunit will dispatch. await consumer.WriteOutputAsync().ConfigureAwait(false); - frame.Abort(error: null); + http1Connection.Abort(error: null); outputReader.Complete(); } catch (UvException ex) { - frame.Abort(ex); + http1Connection.Abort(ex); outputReader.Complete(ex); } } } -} \ No newline at end of file +} diff --git a/test/shared/TestFrame.cs b/test/shared/TestHttp1Connection.cs similarity index 82% rename from test/shared/TestFrame.cs rename to test/shared/TestHttp1Connection.cs index 74b8bac07c..8888a468be 100644 --- a/test/shared/TestFrame.cs +++ b/test/shared/TestHttp1Connection.cs @@ -8,9 +8,9 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Testing { - public class TestFrame : Frame + public class TestHttp1Connection : Http1Connection { - public TestFrame(IHttpApplication application, FrameContext context) + public TestHttp1Connection(IHttpApplication application, Http1ConnectionContext context) : base(application, context) { } diff --git a/test/shared/TestServiceContext.cs b/test/shared/TestServiceContext.cs index 5661d3678f..556e36be58 100644 --- a/test/shared/TestServiceContext.cs +++ b/test/shared/TestServiceContext.cs @@ -38,8 +38,8 @@ namespace Microsoft.AspNetCore.Testing ThreadPool = new LoggingThreadPool(Log); SystemClock = new MockSystemClock(); DateHeaderValueManager = new DateHeaderValueManager(SystemClock); - ConnectionManager = new FrameConnectionManager(Log, ResourceCounter.Unlimited); - HttpParserFactory = frameAdapter => new HttpParser(frameAdapter.Frame.ServiceContext.Log.IsEnabled(LogLevel.Information)); + ConnectionManager = new HttpConnectionManager(Log, ResourceCounter.Unlimited); + HttpParserFactory = handler => new HttpParser(handler.Connection.ServiceContext.Log.IsEnabled(LogLevel.Information)); ServerOptions = new KestrelServerOptions { AddServerHeader = false diff --git a/tools/CodeGenerator/CodeGenerator.csproj b/tools/CodeGenerator/CodeGenerator.csproj index 51021280d2..fcdc836a7c 100644 --- a/tools/CodeGenerator/CodeGenerator.csproj +++ b/tools/CodeGenerator/CodeGenerator.csproj @@ -18,7 +18,7 @@ $(MSBuildThisFileDirectory)..\..\src\Kestrel.Core - Internal/Http/FrameHeaders.Generated.cs Internal/Http/Frame.Generated.cs Internal/Http2/Http2Stream.Generated.cs Internal/Infrastructure/HttpUtilities.Generated.cs + Internal/Http/HttpHeaders.Generated.cs Internal/Http/HttpProtocol.Generated.cs Internal/Infrastructure/HttpUtilities.Generated.cs diff --git a/tools/CodeGenerator/FrameFeatureCollection.cs b/tools/CodeGenerator/HttpProtocolFeatureCollection.cs similarity index 87% rename from tools/CodeGenerator/FrameFeatureCollection.cs rename to tools/CodeGenerator/HttpProtocolFeatureCollection.cs index b32dacbb72..6582f445e9 100644 --- a/tools/CodeGenerator/FrameFeatureCollection.cs +++ b/tools/CodeGenerator/HttpProtocolFeatureCollection.cs @@ -12,17 +12,15 @@ namespace CodeGenerator { // This project can output the Class library as a NuGet Package. // To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build". - public class FrameFeatureCollection + public class HttpProtocolFeatureCollection { static string Each(IEnumerable values, Func formatter) { return values.Select(formatter).Aggregate((a, b) => a + b); } - public static string GeneratedFile(string className, string namespaceSuffix, IEnumerable additionalFeatures = null) + public static string GeneratedFile(string className) { - additionalFeatures = additionalFeatures ?? new Type[] { }; - var alwaysFeatures = new[] { typeof(IHttpRequestFeature), @@ -43,6 +41,7 @@ namespace CodeGenerator var sometimesFeatures = new[] { typeof(IHttpUpgradeFeature), + typeof(IHttp2StreamIdFeature), typeof(IResponseCookiesFeature), typeof(IItemsFeature), typeof(ITlsConnectionFeature), @@ -59,15 +58,14 @@ namespace CodeGenerator typeof(IHttpSendFileFeature), }; - var allFeatures = alwaysFeatures.Concat(commonFeatures).Concat(sometimesFeatures).Concat(rareFeatures).Concat(additionalFeatures); + var allFeatures = alwaysFeatures.Concat(commonFeatures).Concat(sometimesFeatures).Concat(rareFeatures); - // NOTE: This list MUST always match the set of feature interfaces implemented by Frame. - // See also: src/Kestrel/Http/Frame.FeatureCollection.cs + // NOTE: This list MUST always match the set of feature interfaces implemented by HttpProtocol. + // See also: src/Kestrel/Http/HttpProtocol.FeatureCollection.cs var implementedFeatures = new[] { typeof(IHttpRequestFeature), typeof(IHttpResponseFeature), - typeof(IHttpUpgradeFeature), typeof(IHttpRequestIdentifierFeature), typeof(IHttpRequestLifetimeFeature), typeof(IHttpConnectionFeature), @@ -75,7 +73,7 @@ namespace CodeGenerator typeof(IHttpMinRequestBodyDataRateFeature), typeof(IHttpMinResponseDataRateFeature), typeof(IHttpBodyControlFeature), - }.Concat(additionalFeatures); + }; return $@"// 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. @@ -83,7 +81,7 @@ namespace CodeGenerator using System; using System.Collections.Generic; -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.{namespaceSuffix} +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http {{ public partial class {className} {{{Each(allFeatures, feature => $@" @@ -107,7 +105,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.{namespaceSuffix} return ExtraFeatureGet(key); }} - internal void FastFeatureSet(Type key, object feature) + protected void FastFeatureSet(Type key, object feature) {{ _featureRevision++; {Each(allFeatures, feature => $@" diff --git a/tools/CodeGenerator/KnownHeaders.cs b/tools/CodeGenerator/KnownHeaders.cs index 16aac8269f..89a66fc2ac 100644 --- a/tools/CodeGenerator/KnownHeaders.cs +++ b/tools/CodeGenerator/KnownHeaders.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; namespace CodeGenerator { @@ -265,14 +266,14 @@ namespace CodeGenerator { Headers = requestHeaders, HeadersByLength = requestHeaders.GroupBy(x => x.Name.Length), - ClassName = "FrameRequestHeaders", + ClassName = nameof(HttpRequestHeaders), Bytes = default(byte[]) }, new { Headers = responseHeaders, HeadersByLength = responseHeaders.GroupBy(x => x.Name.Length), - ClassName = "FrameResponseHeaders", + ClassName = nameof(HttpResponseHeaders), Bytes = responseHeaders.SelectMany(header => header.Bytes).ToArray() } }; @@ -383,7 +384,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http }} protected override void SetValueFast(string key, StringValues value) - {{{(loop.ClassName == "FrameResponseHeaders" ? @" + {{{(loop.ClassName == nameof(HttpResponseHeaders) ? @" ValidateHeaderCharacters(value);" : "")} switch (key.Length) {{{Each(loop.HeadersByLength, byLength => $@" @@ -405,7 +406,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http }} protected override bool AddValueFast(string key, StringValues value) - {{{(loop.ClassName == "FrameResponseHeaders" ? @" + {{{(loop.ClassName == nameof(HttpResponseHeaders) ? @" ValidateHeaderCharacters(value);" : "")} switch (key.Length) {{{Each(loop.HeadersByLength, byLength => $@" @@ -431,7 +432,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http }} break;")} }} -{(loop.ClassName == "FrameResponseHeaders" ? @" +{(loop.ClassName == nameof(HttpResponseHeaders) ? @" ValidateHeaderCharacters(key);" : "")} Unknown.Add(key, value); // Return true, above will throw and exit for false @@ -474,7 +475,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _contentLength = null; var tempBits = _bits; _bits = 0; - if(FrameHeaders.BitCount(tempBits) > 12) + if(HttpHeaders.BitCount(tempBits) > 12) {{ _headers = default(HeaderReferences); return; @@ -521,7 +522,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return true; }} - {(loop.ClassName == "FrameResponseHeaders" ? $@" + {(loop.ClassName == nameof(HttpResponseHeaders) ? $@" protected void CopyToFast(ref WritableBufferWriter output) {{ var tempBits = _bits | (_contentLength.HasValue ? {1L << 63}L : 0); @@ -564,7 +565,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http tempBits &= ~{1L << 63}L; }}" : "")}")} }}" : "")} - {(loop.ClassName == "FrameRequestHeaders" ? $@" + {(loop.ClassName == nameof(HttpRequestHeaders) ? $@" public unsafe void Append(byte* pKeyBytes, int keyLength, string value) {{ var pUB = pKeyBytes; diff --git a/tools/CodeGenerator/Program.cs b/tools/CodeGenerator/Program.cs index 59c190a2be..1d8596890e 100644 --- a/tools/CodeGenerator/Program.cs +++ b/tools/CodeGenerator/Program.cs @@ -5,7 +5,6 @@ using System; using System.IO; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; namespace CodeGenerator { @@ -15,12 +14,12 @@ namespace CodeGenerator { if (args.Length < 1) { - Console.Error.WriteLine("Missing path to FrameHeaders.Generated.cs"); + Console.Error.WriteLine("Missing path to HttpHeaders.Generated.cs"); return 1; } else if (args.Length < 2) { - Console.Error.WriteLine("Missing path to Frame.Generated.cs"); + Console.Error.WriteLine("Missing path to HttpProtocol.Generated.cs"); return 1; } else if (args.Length < 3) @@ -29,16 +28,15 @@ namespace CodeGenerator return 1; } - Run(args[0], args[1], args[2], args[3]); + Run(args[0], args[1], args[2]); return 0; } - public static void Run(string knownHeadersPath, string frameFeatureCollectionPath, string http2StreamFeatureCollectionPath, string httpUtilitiesPath) + public static void Run(string knownHeadersPath, string httpProtocolFeatureCollectionPath, string httpUtilitiesPath) { var knownHeadersContent = KnownHeaders.GeneratedFile(); - var frameFeatureCollectionContent = FrameFeatureCollection.GeneratedFile(nameof(Frame), "Http"); - var http2StreamFeatureCollectionContent = FrameFeatureCollection.GeneratedFile(nameof(Http2Stream), "Http2", new[] { typeof(IHttp2StreamIdFeature) }); + var httpProtocolFeatureCollectionContent = HttpProtocolFeatureCollection.GeneratedFile(nameof(HttpProtocol)); var httpUtilitiesContent = HttpUtilities.HttpUtilities.GeneratedFile(); var existingKnownHeaders = File.Exists(knownHeadersPath) ? File.ReadAllText(knownHeadersPath) : ""; @@ -47,16 +45,10 @@ namespace CodeGenerator File.WriteAllText(knownHeadersPath, knownHeadersContent); } - var existingFrameFeatureCollection = File.Exists(frameFeatureCollectionPath) ? File.ReadAllText(frameFeatureCollectionPath) : ""; - if (!string.Equals(frameFeatureCollectionContent, existingFrameFeatureCollection)) + var existingHttpProtocolFeatureCollection = File.Exists(httpProtocolFeatureCollectionPath) ? File.ReadAllText(httpProtocolFeatureCollectionPath) : ""; + if (!string.Equals(httpProtocolFeatureCollectionContent, existingHttpProtocolFeatureCollection)) { - File.WriteAllText(frameFeatureCollectionPath, frameFeatureCollectionContent); - } - - var existingHttp2StreamFeatureCollection = File.Exists(http2StreamFeatureCollectionPath) ? File.ReadAllText(http2StreamFeatureCollectionPath) : ""; - if (!string.Equals(http2StreamFeatureCollectionContent, existingHttp2StreamFeatureCollection)) - { - File.WriteAllText(http2StreamFeatureCollectionPath, http2StreamFeatureCollectionContent); + File.WriteAllText(httpProtocolFeatureCollectionPath, httpProtocolFeatureCollectionContent); } var existingHttpUtilities = File.Exists(httpUtilitiesPath) ? File.ReadAllText(httpUtilitiesPath) : "";