From 647e7c326670c0c0fdf865a1a30ba5775ec42cc5 Mon Sep 17 00:00:00 2001 From: "Chris Ross (ASP.NET)" Date: Fri, 27 Jul 2018 15:25:18 -0700 Subject: [PATCH 1/6] Add new ConfigureKestrel extensions #2760 --- samples/Http2SampleApp/Program.cs | 3 +- .../WebHostBuilderKestrelExtensions.cs | 36 +++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/samples/Http2SampleApp/Program.cs b/samples/Http2SampleApp/Program.cs index acf22dc01c..0aee02c7a7 100644 --- a/samples/Http2SampleApp/Program.cs +++ b/samples/Http2SampleApp/Program.cs @@ -24,7 +24,8 @@ namespace Http2SampleApp factory.SetMinimumLevel(LogLevel.Trace); factory.AddConsole(); }) - .UseKestrel((context, options) => + .UseKestrel() + .ConfigureKestrel((context, options) => { var basePort = context.Configuration.GetValue("BASE_PORT") ?? 5000; diff --git a/src/Kestrel/WebHostBuilderKestrelExtensions.cs b/src/Kestrel/WebHostBuilderKestrelExtensions.cs index 6552da10f2..62411b168d 100644 --- a/src/Kestrel/WebHostBuilderKestrelExtensions.cs +++ b/src/Kestrel/WebHostBuilderKestrelExtensions.cs @@ -50,7 +50,24 @@ namespace Microsoft.AspNetCore.Hosting /// public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder, Action options) { - return hostBuilder.UseKestrel().ConfigureServices(services => + return hostBuilder.UseKestrel().ConfigureKestrel(options); + } + + /// + /// Configures Kestrel options but does not register an IServer. See . + /// + /// + /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure. + /// + /// + /// A callback to configure Kestrel options. + /// + /// + /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder. + /// + public static IWebHostBuilder ConfigureKestrel(this IWebHostBuilder hostBuilder, Action options) + { + return hostBuilder.ConfigureServices(services => { services.Configure(options); }); @@ -67,13 +84,28 @@ namespace Microsoft.AspNetCore.Hosting /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder. /// public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder, Action configureOptions) + { + return hostBuilder.UseKestrel().ConfigureKestrel(configureOptions); + } + + /// + /// Configures Kestrel options but does not register an IServer. See . + /// + /// + /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure. + /// + /// A callback to configure Kestrel options. + /// + /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder. + /// + public static IWebHostBuilder ConfigureKestrel(this IWebHostBuilder hostBuilder, Action configureOptions) { if (configureOptions == null) { throw new ArgumentNullException(nameof(configureOptions)); } - return hostBuilder.UseKestrel().ConfigureServices((context, services) => + return hostBuilder.ConfigureServices((context, services) => { services.Configure(options => { From 47e643f20a3ab74a466b320f830ffb81da55eb68 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 29 Jul 2018 12:18:16 -0700 Subject: [PATCH 2/6] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 51 ++++++++++++++++++++-------------------- korebuild-lock.txt | 4 ++-- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index d3ae9f2de7..74a1fc1704 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,34 +5,34 @@ 0.10.13 - 2.2.0-preview1-34755 - 2.2.0-preview1-17099 + 2.2.0-preview1-34823 + 2.2.0-preview1-17102 1.10.0 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 2.1.1 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34755 + 2.2.0-preview1-34823 15.6.1 4.7.49 2.0.3 @@ -46,9 +46,10 @@ 4.5.0 4.5.1 1.3.7 - 0.9.0 + 0.10.0 2.3.1 - 2.4.0-rc.1.build4038 + 2.4.0 + diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 27e2e80f9a..6b8da29e6b 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17099 -commithash:263ed1db9866b6b419b1f5d5189a712aa218acb3 +version:2.2.0-preview1-17102 +commithash:e7e2b5a97ca92cfc6acc4def534cb0901a6d1eb9 From 6d46410a763903b08b6187540d58049d4cba8370 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Mon, 30 Jul 2018 12:25:32 -0700 Subject: [PATCH 3/6] Don't ACK ACKs (#2767) --- src/Kestrel.Core/Internal/Http2/Http2Connection.cs | 9 +++++++-- test/Kestrel.Core.Tests/Http2ConnectionTests.cs | 12 ++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs index 1267a0fc20..ae457545a1 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs @@ -541,9 +541,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); } - if ((_incomingFrame.SettingsFlags & Http2SettingsFrameFlags.ACK) == Http2SettingsFrameFlags.ACK && _incomingFrame.Length != 0) + if ((_incomingFrame.SettingsFlags & Http2SettingsFrameFlags.ACK) == Http2SettingsFrameFlags.ACK) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsAckLengthNotZero, Http2ErrorCode.FRAME_SIZE_ERROR); + if (_incomingFrame.Length != 0) + { + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsAckLengthNotZero, Http2ErrorCode.FRAME_SIZE_ERROR); + } + + return Task.CompletedTask; } if (_incomingFrame.Length % 6 != 0) diff --git a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs index 1a6c8b16d8..fe146599f0 100644 --- a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs +++ b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs @@ -2203,6 +2203,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); } + [Fact] + public async Task SETTINGS_ACK_Received_DoesNotSend_ACK() + { + await InitializeConnectionAsync(_noopApplication); + + var frame = new Http2Frame(); + frame.PrepareSettings(Http2SettingsFrameFlags.ACK); + await SendAsync(frame.Raw); + + await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); + } + [Fact] public async Task SETTINGS_Received_StreamIdNotZero_ConnectionError() { From 3cdb73440e3f1a5a53edcf355beb6dbbb85f7955 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Mon, 30 Jul 2018 14:33:45 -0700 Subject: [PATCH 4/6] Disable memory pool late return validation in H2SpecTests (#2768) - We should reenable once HTTP/2 graceful shutdown is implemented --- test/Kestrel.FunctionalTests/Http2/H2SpecTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/Kestrel.FunctionalTests/Http2/H2SpecTests.cs b/test/Kestrel.FunctionalTests/Http2/H2SpecTests.cs index 7e0c85e55c..239d051200 100644 --- a/test/Kestrel.FunctionalTests/Http2/H2SpecTests.cs +++ b/test/Kestrel.FunctionalTests/Http2/H2SpecTests.cs @@ -26,7 +26,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 [MemberData(nameof(H2SpecTestCases))] public async Task RunIndividualTestCase(H2SpecTestCase testCase) { - var hostBuilder = TransportSelector.GetWebHostBuilder() + var memoryPoolFactory = new DiagnosticMemoryPoolFactory(allowLateReturn: true); + + var hostBuilder = TransportSelector.GetWebHostBuilder(memoryPoolFactory.Create) .UseKestrel(options => { options.Listen(IPAddress.Loopback, 0, listenOptions => From beca0259c27aad95e747df6d4bcaf53b07c1b1f2 Mon Sep 17 00:00:00 2001 From: "Chris Ross (ASP.NET)" Date: Tue, 31 Jul 2018 15:33:57 -0700 Subject: [PATCH 5/6] Verify request Content-Length #2733 --- src/Kestrel.Core/CoreStrings.resx | 6 + .../Internal/Http2/Http2Connection.cs | 17 +- .../Internal/Http2/Http2Stream.cs | 33 +- .../Properties/CoreStrings.Designer.cs | 28 ++ test/Kestrel.Core.Tests/Http2StreamTests.cs | 356 ++++++++++++++++-- .../Http2/H2SpecTests.cs | 9 +- test/shared/KestrelTestLoggerProvider.cs | 1 - 7 files changed, 415 insertions(+), 35 deletions(-) diff --git a/src/Kestrel.Core/CoreStrings.resx b/src/Kestrel.Core/CoreStrings.resx index 7bbbdfff3a..66048b1b15 100644 --- a/src/Kestrel.Core/CoreStrings.resx +++ b/src/Kestrel.Core/CoreStrings.resx @@ -557,4 +557,10 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l The request :scheme header '{requestScheme}' does not match the transport scheme '{transportScheme}'. + + Less data received than specified in the Content-Length header. + + + More data received than specified in the Content-Length header. + \ No newline at end of file diff --git a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs index ae457545a1..487b8fae27 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs @@ -73,6 +73,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private Http2Stream _currentHeadersStream; private RequestHeaderParsingState _requestHeaderParsingState; private PseudoHeaderFields _parsedPseudoHeaderFields; + private Http2HeadersFrameFlags _headerFlags; private bool _isMethodConnect; private int _highestOpenedStreamId; @@ -469,12 +470,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 TimeoutControl = this, }); - if ((_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM) - { - _currentHeadersStream.OnEndStreamReceived(); - } - _currentHeadersStream.Reset(); + _headerFlags = _incomingFrame.HeadersFlags; var endHeaders = (_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS; await DecodeHeadersAsync(application, endHeaders, _incomingFrame.HeadersPayload); @@ -768,6 +765,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields, Http2ErrorCode.PROTOCOL_ERROR); } + // This must be initialized before we offload the request or else we may start processing request body frames without it. + _currentHeadersStream.InputRemaining = _currentHeadersStream.RequestHeaders.ContentLength; + + // This must wait until we've received all of the headers so we can verify the content-length. + if ((_headerFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM) + { + _currentHeadersStream.OnEndStreamReceived(); + } + _streams[_incomingFrame.StreamId] = _currentHeadersStream; // Must not allow app code to block the connection handling loop. ThreadPool.UnsafeQueueUserWorkItem(state => @@ -788,6 +794,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 _currentHeadersStream = null; _requestHeaderParsingState = RequestHeaderParsingState.Ready; _parsedPseudoHeaderFields = PseudoHeaderFields.None; + _headerFlags = Http2HeadersFrameFlags.NONE; _isMethodConnect = false; } diff --git a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs index a0828e7ed2..48699e84ae 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs @@ -48,8 +48,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 public int StreamId => _context.StreamId; + public long? InputRemaining { get; internal set; } + public bool RequestBodyStarted { get; private set; } public bool EndStreamReceived => (_completionState & StreamCompletionFlags.EndStreamReceived) == StreamCompletionFlags.EndStreamReceived; + private bool IsAborted => (_completionState & StreamCompletionFlags.Aborted) == StreamCompletionFlags.Aborted; public override bool IsUpgradableRequest => false; @@ -263,8 +266,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 public Task OnDataAsync(Http2Frame dataFrame) { - // TODO: content-length accounting - // Since padding isn't buffered, immediately count padding bytes as read for flow control purposes. if (dataFrame.DataHasPadding) { @@ -288,6 +289,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 _inputFlowControl.Advance(payload.Count); + if (IsAborted) + { + // Ignore data frames for aborted streams, but only after counting them for purposes of connection level flow control. + return Task.CompletedTask; + } + + // This check happens after flow control so that when we throw and abort, the byte count is returned to the connection + // level accounting. + if (InputRemaining.HasValue) + { + // https://tools.ietf.org/html/rfc7540#section-8.1.2.6 + if (payload.Count > InputRemaining.Value) + { + throw new Http2StreamErrorException(StreamId, CoreStrings.Http2StreamErrorMoreDataThanLength, Http2ErrorCode.PROTOCOL_ERROR); + } + + InputRemaining -= payload.Count; + } + RequestBodyPipe.Writer.Write(payload); var flushTask = RequestBodyPipe.Writer.FlushAsync(); @@ -306,6 +326,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 public void OnEndStreamReceived() { + if (InputRemaining.HasValue) + { + // https://tools.ietf.org/html/rfc7540#section-8.1.2.6 + if (InputRemaining.Value != 0) + { + throw new Http2StreamErrorException(StreamId, CoreStrings.Http2StreamErrorLessDataThanLength, Http2ErrorCode.PROTOCOL_ERROR); + } + } + TryApplyCompletionFlag(StreamCompletionFlags.EndStreamReceived); RequestBodyPipe.Writer.Complete(); diff --git a/src/Kestrel.Core/Properties/CoreStrings.Designer.cs b/src/Kestrel.Core/Properties/CoreStrings.Designer.cs index 09e593c57b..8b977316ec 100644 --- a/src/Kestrel.Core/Properties/CoreStrings.Designer.cs +++ b/src/Kestrel.Core/Properties/CoreStrings.Designer.cs @@ -2058,6 +2058,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core internal static string FormatHttp2StreamErrorSchemeMismatch(object requestScheme, object transportScheme) => string.Format(CultureInfo.CurrentCulture, GetString("Http2StreamErrorSchemeMismatch", "requestScheme", "transportScheme"), requestScheme, transportScheme); + /// + /// Less data received than specified in the Content-Length header. + /// + internal static string Http2StreamErrorLessDataThanLength + { + get => GetString("Http2StreamErrorLessDataThanLength"); + } + + /// + /// Less data received than specified in the Content-Length header. + /// + internal static string FormatHttp2StreamErrorLessDataThanLength() + => GetString("Http2StreamErrorLessDataThanLength"); + + /// + /// More data received than specified in the Content-Length header. + /// + internal static string Http2StreamErrorMoreDataThanLength + { + get => GetString("Http2StreamErrorMoreDataThanLength"); + } + + /// + /// More data received than specified in the Content-Length header. + /// + internal static string FormatHttp2StreamErrorMoreDataThanLength() + => GetString("Http2StreamErrorMoreDataThanLength"); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/test/Kestrel.Core.Tests/Http2StreamTests.cs b/test/Kestrel.Core.Tests/Http2StreamTests.cs index da8e98b987..c908ee8031 100644 --- a/test/Kestrel.Core.Tests/Http2StreamTests.cs +++ b/test/Kestrel.Core.Tests/Http2StreamTests.cs @@ -42,11 +42,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests new KeyValuePair("upgrade-insecure-requests", "1"), }; - private readonly MemoryPool _memoryPool = KestrelMemoryPool.Create(); - private readonly DuplexPipe.DuplexPipePair _pair; + private MemoryPool _memoryPool = KestrelMemoryPool.Create(); + private DuplexPipe.DuplexPipePair _pair; private readonly TestApplicationErrorLogger _logger; - private readonly Http2ConnectionContext _connectionContext; - private readonly Http2Connection _connection; + private Http2ConnectionContext _connectionContext; + private Http2Connection _connection; private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings(); private readonly HPackEncoder _hpackEncoder = new HPackEncoder(); private readonly HPackDecoder _hpackDecoder; @@ -68,24 +68,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public Http2StreamTests() { - // Always dispatch test code back to the ThreadPool. This prevents deadlocks caused by continuing - // Http2Connection.ProcessRequestsAsync() loop with writer locks acquired. Run product code inline to make - // it easier to verify request frames are processed correctly immediately after sending the them. - var inputPipeOptions = new PipeOptions( - pool: _memoryPool, - readerScheduler: PipeScheduler.Inline, - writerScheduler: PipeScheduler.ThreadPool, - useSynchronizationContext: false - ); - var outputPipeOptions = new PipeOptions( - pool: _memoryPool, - readerScheduler: PipeScheduler.ThreadPool, - writerScheduler: PipeScheduler.Inline, - useSynchronizationContext: false - ); - - _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); - _noopApplication = context => Task.CompletedTask; _echoMethod = context => @@ -178,6 +160,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _logger = new TestApplicationErrorLogger(); + InitializeConnectionFields(KestrelMemoryPool.Create()); + } + + private void InitializeConnectionFields(MemoryPool memoryPool) + { + _memoryPool = memoryPool; + + // Always dispatch test code back to the ThreadPool. This prevents deadlocks caused by continuing + // Http2Connection.ProcessRequestsAsync() loop with writer locks acquired. Run product code inline to make + // it easier to verify request frames are processed correctly immediately after sending the them. + var inputPipeOptions = new PipeOptions( + pool: _memoryPool, + readerScheduler: PipeScheduler.Inline, + writerScheduler: PipeScheduler.ThreadPool, + useSynchronizationContext: false + ); + var outputPipeOptions = new PipeOptions( + pool: _memoryPool, + readerScheduler: PipeScheduler.ThreadPool, + writerScheduler: PipeScheduler.Inline, + useSynchronizationContext: false + ); + + _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); + _connectionContext = new Http2ConnectionContext { ConnectionFeatures = new FeatureCollection(), @@ -754,6 +761,308 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); } + [Fact] + public async Task ContentLength_Received_SingleDataFrame_Verified() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + await InitializeConnectionAsync(async context => + { + var buffer = new byte[100]; + var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length); + Assert.Equal(12, read); + }); + + await StartStreamAsync(1, headers, endStream: false); + await SendDataAsync(1, new byte[12].AsSpan(), endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 55, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 0, + withFlags: (byte)Http2DataFrameFlags.END_STREAM, + withStreamId: 1); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + _hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this); + + Assert.Equal(3, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); + Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]); + } + + [Fact] + public async Task ContentLength_ReceivedInContinuation_SingleDataFrame_Verified() + { + await InitializeConnectionAsync(async context => + { + var buffer = new byte[100]; + var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length); + Assert.Equal(12, read); + }); + + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair("a", _largeHeaderValue), + new KeyValuePair("b", _largeHeaderValue), + new KeyValuePair("c", _largeHeaderValue), + new KeyValuePair("d", _largeHeaderValue), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + await StartStreamAsync(1, headers, endStream: false); + await SendDataAsync(1, new byte[12].AsSpan(), endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 55, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 0, + withFlags: (byte)Http2DataFrameFlags.END_STREAM, + withStreamId: 1); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + _hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this); + + Assert.Equal(3, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); + Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]); + } + + [Fact] + public async Task ContentLength_Received_MultipleDataFrame_Verified() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + await InitializeConnectionAsync(async context => + { + var buffer = new byte[100]; + var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length); + var total = read; + while (read > 0) + { + read = await context.Request.Body.ReadAsync(buffer, total, buffer.Length - total); + total += read; + } + Assert.Equal(12, total); + }); + + + await StartStreamAsync(1, headers, endStream: false); + await SendDataAsync(1, new byte[1].AsSpan(), endStream: false); + await SendDataAsync(1, new byte[3].AsSpan(), endStream: false); + await SendDataAsync(1, new byte[8].AsSpan(), endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 55, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 0, + withFlags: (byte)Http2DataFrameFlags.END_STREAM, + withStreamId: 1); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + _hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this); + + Assert.Equal(3, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); + Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]); + } + + [Fact] + public async Task ContentLength_Received_NoDataFrames_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + await InitializeConnectionAsync(_noopApplication); + + await StartStreamAsync(1, headers, endStream: true); + + await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + } + + [Fact] + public async Task ContentLength_ReceivedInContinuation_NoDataFrames_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair("a", _largeHeaderValue), + new KeyValuePair("b", _largeHeaderValue), + new KeyValuePair("c", _largeHeaderValue), + new KeyValuePair("d", _largeHeaderValue), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + await InitializeConnectionAsync(_noopApplication); + + await StartStreamAsync(1, headers, endStream: true); + + await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + } + + [Fact] + public async Task ContentLength_Received_SingleDataFrameOverSize_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + await InitializeConnectionAsync(async context => + { + await Assert.ThrowsAsync(async () => + { + var buffer = new byte[100]; + while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { } + }); + }); + + await StartStreamAsync(1, headers, endStream: false); + await SendDataAsync(1, new byte[13].AsSpan(), endStream: true); + + await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorMoreDataThanLength); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + } + + [Fact] + public async Task ContentLength_Received_SingleDataFrameUnderSize_Reset() + { + // I hate doing this, but it avoids exceptions from MemoryPool.Dipose() in debug mode. The problem is since + // the stream's ProcessRequestsAsync loop is never awaited by the connection, it's not really possible to + // observe when all the blocks are returned. This can be removed after we implement graceful shutdown. + Dispose(); + InitializeConnectionFields(new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(), allowLateReturn: true)); + + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + await InitializeConnectionAsync(async context => + { + await Assert.ThrowsAsync(async () => + { + var buffer = new byte[100]; + while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { } + }); + }); + + await StartStreamAsync(1, headers, endStream: false); + await SendDataAsync(1, new byte[11].AsSpan(), endStream: true); + + await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + } + + [Fact] + public async Task ContentLength_Received_MultipleDataFramesOverSize_Reset() + { + // I hate doing this, but it avoids exceptions from MemoryPool.Dipose() in debug mode. The problem is since + // the stream's ProcessRequestsAsync loop is never awaited by the connection, it's not really possible to + // observe when all the blocks are returned. This can be removed after we implement graceful shutdown. + Dispose(); + InitializeConnectionFields(new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(), allowLateReturn: true)); + + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + await InitializeConnectionAsync(async context => + { + await Assert.ThrowsAsync(async () => + { + var buffer = new byte[100]; + while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { } + }); + }); + + await StartStreamAsync(1, headers, endStream: false); + await SendDataAsync(1, new byte[1].AsSpan(), endStream: false); + await SendDataAsync(1, new byte[2].AsSpan(), endStream: false); + await SendDataAsync(1, new byte[10].AsSpan(), endStream: false); + await SendDataAsync(1, new byte[2].AsSpan(), endStream: true); + + await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorMoreDataThanLength); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + } + + [Fact] + public async Task ContentLength_Received_MultipleDataFramesUnderSize_Reset() + { + // I hate doing this, but it avoids exceptions from MemoryPool.Dipose() in debug mode. The problem is since + // the stream's ProcessRequestsAsync loop is never awaited by the connection, it's not really possible to + // observe when all the blocks are returned. This can be removed after we implement graceful shutdown. + Dispose(); + InitializeConnectionFields(new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(), allowLateReturn: true)); + + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + await InitializeConnectionAsync(async context => + { + await Assert.ThrowsAsync(async () => + { + var buffer = new byte[100]; + while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { } + }); + }); + + await StartStreamAsync(1, headers, endStream: false); + await SendDataAsync(1, new byte[1].AsSpan(), endStream: false); + await SendDataAsync(1, new byte[2].AsSpan(), endStream: true); + + await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + } + [Fact] public async Task RST_STREAM_Received_AbortsStream() { @@ -1182,8 +1491,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests if (expectedErrorMessage != null) { - var message = Assert.Single(_logger.Messages, m => m.Exception is ConnectionAbortedException); - Assert.Contains(expectedErrorMessage, message.Exception.Message); + Assert.Contains(_logger.Messages, m => m.Exception?.Message.Contains(expectedErrorMessage) ?? false); } } } diff --git a/test/Kestrel.FunctionalTests/Http2/H2SpecTests.cs b/test/Kestrel.FunctionalTests/Http2/H2SpecTests.cs index 239d051200..b5a196c5f2 100644 --- a/test/Kestrel.FunctionalTests/Http2/H2SpecTests.cs +++ b/test/Kestrel.FunctionalTests/Http2/H2SpecTests.cs @@ -3,6 +3,7 @@ #if NETCOREAPP2_2 +using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; @@ -56,7 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 get { var dataset = new TheoryData(); - var toSkip = new[] { "hpack/4.2/1", "http2/5.1/8", "http2/8.1.2.6/1", "http2/8.1.2.6/2" }; + var toSkip = new[] { "hpack/4.2/1", "http2/5.1/8" }; foreach (var testcase in H2SpecCommands.EnumerateTestCases()) { @@ -123,9 +124,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 private void ConfigureHelloWorld(IApplicationBuilder app) { - app.Run(context => + app.Run(async context => { - return context.Request.Body.CopyToAsync(context.Response.Body); + // Read the whole request body to check for errors. + await context.Request.Body.CopyToAsync(Stream.Null); + await context.Response.WriteAsync("Hello World"); }); } } diff --git a/test/shared/KestrelTestLoggerProvider.cs b/test/shared/KestrelTestLoggerProvider.cs index 48455557fb..69984f2770 100644 --- a/test/shared/KestrelTestLoggerProvider.cs +++ b/test/shared/KestrelTestLoggerProvider.cs @@ -27,7 +27,6 @@ namespace Microsoft.AspNetCore.Testing public void Dispose() { - throw new NotImplementedException(); } } } From 7e073cb5f62609d07c043379c5ad8c66df76bd1b Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 5 Aug 2018 19:20:23 +0000 Subject: [PATCH 6/6] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 46 ++++++++++++++++++++-------------------- korebuild-lock.txt | 4 ++-- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 74a1fc1704..fd19c0d6b2 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,34 +5,34 @@ 0.10.13 - 2.2.0-preview1-34823 - 2.2.0-preview1-17102 + 2.2.0-preview1-34882 + 2.2.0-preview1-20180731.1 1.10.0 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 + 2.2.0-preview1-34882 2.1.1 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34823 + 2.2.0-preview1-34882 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 6b8da29e6b..c7af2292c7 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17102 -commithash:e7e2b5a97ca92cfc6acc4def534cb0901a6d1eb9 +version:2.2.0-preview1-20180731.1 +commithash:29fde58465439f4bb9df40830635ed758e063daf