public async Task ConnectionClosedWhenResponseNotDrainedAtMinimumDataRate(ListenOptions listenOptions) { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); var minRate = new MinDataRate(16384, TimeSpan.FromSeconds(2)); await using (var server = new TestServer(context => { context.Features.Get <IHttpMinResponseDataRateFeature>().MinDataRate = minRate; return(Task.CompletedTask); }, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { var transportConnection = connection.TransportConnection; var outputBufferedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); #pragma warning disable 0618 // TODO: Repalce OnWriterCompleted transportConnection.Output.OnWriterCompleted((ex, state) => { ((TaskCompletionSource)state).SetResult(); }, outputBufferedTcs); #pragma warning restore await connection.Send( "GET / HTTP/1.1", "Host:", "Connection: close", "", ""); // Wait for the drain timeout to be set. await outputBufferedTcs.Task.DefaultTimeout(); // Advance the clock to the grace period for (var i = 0; i < 2; i++) { testContext.MockSystemClock.UtcNow += TimeSpan.FromSeconds(1); heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); } testContext.MockSystemClock.UtcNow += Heartbeat.Interval - TimeSpan.FromSeconds(.5); heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); Assert.Null(transportConnection.AbortReason); testContext.MockSystemClock.UtcNow += TimeSpan.FromSeconds(1); heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); Assert.NotNull(transportConnection.AbortReason); Assert.Equal(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied, transportConnection.AbortReason.Message); Assert.Single(LogMessages, w => w.EventId.Id == 28 && w.LogLevel <= LogLevel.Debug); } } }
public async Task ConnectionClosedWhenResponseNotDrainedAtMinimumDataRate(ListenOptions listenOptions) { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); var minRate = new MinDataRate(16384, TimeSpan.FromSeconds(2)); using (var server = new TestServer(context => { context.Features.Get <IHttpMinResponseDataRateFeature>().MinDataRate = minRate; return(Task.CompletedTask); }, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { var transportConnection = connection.TransportConnection; var outputBufferedTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); transportConnection.Output.OnWriterCompleted((ex, state) => { ((TaskCompletionSource <object>)state).SetResult(null); }, outputBufferedTcs); await connection.Send( "GET / HTTP/1.1", "Host:", "Connection: close", "", ""); // Wait for the drain timeout to be set. await outputBufferedTcs.Task.DefaultTimeout(); testContext.MockSystemClock.UtcNow += Heartbeat.Interval + TimeSpan.FromSeconds(testContext.ServerOptions.Limits.MaxResponseBufferSize.Value * 2 / minRate.BytesPerSecond); heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); Assert.Null(transportConnection.AbortReason); testContext.MockSystemClock.UtcNow += TimeSpan.FromTicks(1); heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); Assert.NotNull(transportConnection.AbortReason); Assert.Equal(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied, transportConnection.AbortReason.Message); Assert.Single(TestApplicationErrorLogger.Messages, w => w.EventId.Id == 28 && w.LogLevel == LogLevel.Information); } } }
public async Task RequestHeadersTimeoutCanceledAfterHeadersReceived() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); await using (var server = CreateServer(testContext)) { using (var connection = server.CreateConnection()) { await connection.TransportConnection.WaitForReadTask; await connection.Send( "POST / HTTP/1.1", "Host:", "Content-Length: 1", "", ""); // Min amount of time between requests that triggers a request headers timeout. testContext.MockSystemClock.UtcNow += RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1); heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); await connection.Send( "a"); await ReceiveResponse(connection, testContext); } } }
public async Task TimeoutNotResetOnEachRequestLineCharacterReceived() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); // Disable response rate, so we can finish the send loop without timing out the response. testContext.ServerOptions.Limits.MinResponseDataRate = null; await using (var server = CreateServer(testContext)) { using (var connection = server.CreateConnection()) { await connection.TransportConnection.WaitForReadTask; foreach (var ch in "POST / HTTP/1.1\r\nHost:\r\n\r\n") { await connection.Send(ch.ToString()); testContext.MockSystemClock.UtcNow += ShortDelay; heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); } await ReceiveTimeoutResponse(connection, testContext); await connection.WaitForConnectionClose(); } } }
public async Task ConnectionKeptAliveBetweenRequests() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); await using (var server = CreateServer(testContext)) { using (var connection = server.CreateConnection()) { await connection.TransportConnection.WaitForReadTask; for (var i = 0; i < 10; i++) { await connection.Send( "GET / HTTP/1.1", "Host:", "", ""); await ReceiveResponse(connection, testContext); // Max amount of time between requests that doesn't trigger a keep-alive timeout. testContext.MockSystemClock.UtcNow += _keepAliveTimeout + Heartbeat.Interval; heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); } } } }
public async Task ConnectionNotTimedOutWhileRequestBeingSent() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); using (var server = CreateServer(testContext)) using (var connection = server.CreateConnection()) { await connection.Send( "POST /consume HTTP/1.1", "Host:", "Transfer-Encoding: chunked", "", ""); await _firstRequestReceived.Task.DefaultTimeout(); for (var totalDelay = TimeSpan.Zero; totalDelay < _longDelay; totalDelay += _shortDelay) { await connection.Send( "1", "a", ""); testContext.MockSystemClock.UtcNow += _shortDelay; heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); } await connection.Send( "0", "", ""); await ReceiveResponse(connection, testContext); } }
private async Task KeepAliveTimeoutDoesNotApplyToUpgradedConnections() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); var cts = new CancellationTokenSource(); using (var server = CreateServer(testContext, upgradeCt: cts.Token)) using (var connection = server.CreateConnection()) { await connection.Send( "GET /upgrade HTTP/1.1", "Host:", "Connection: Upgrade", "", ""); await connection.Receive( "HTTP/1.1 101 Switching Protocols", "Connection: Upgrade", $"Date: {testContext.DateHeaderValue}", "", ""); for (var totalDelay = TimeSpan.Zero; totalDelay < _longDelay; totalDelay += _shortDelay) { testContext.MockSystemClock.UtcNow += _shortDelay; heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); } cts.Cancel(); await connection.Receive("hello, world"); } }
private async Task ConnectionNotTimedOutWhileAppIsRunning() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); var cts = new CancellationTokenSource(); using (var server = CreateServer(testContext, longRunningCt: cts.Token)) using (var connection = server.CreateConnection()) { await connection.Send( "GET /longrunning HTTP/1.1", "Host:", "", ""); await _firstRequestReceived.Task.DefaultTimeout(); for (var totalDelay = TimeSpan.Zero; totalDelay < _longDelay; totalDelay += _shortDelay) { testContext.MockSystemClock.UtcNow += _shortDelay; heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); } cts.Cancel(); await ReceiveResponse(connection, testContext); await connection.Send( "GET / HTTP/1.1", "Host:", "", ""); await ReceiveResponse(connection, testContext); } }
public async Task ConnectionClosedWhenKeepAliveTimeoutExpires() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); await using (var server = CreateServer(testContext)) { using (var connection = server.CreateConnection()) { await connection.TransportConnection.WaitForReadTask; await connection.Send( "GET / HTTP/1.1", "Host:", "", ""); await ReceiveResponse(connection, testContext); // Min amount of time between requests that triggers a keep-alive timeout. testContext.MockSystemClock.UtcNow += _keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1); heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); await connection.WaitForConnectionClose(); } } }
public async Task DoesNotEnforceRequestBodyMinimumDataRateOnUpgradedRequest() { var appEvent = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var delayEvent = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var serviceContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(serviceContext.ConnectionManager); using (var server = new TestServer(async context => { context.Features.Get <IHttpMinRequestBodyDataRateFeature>().MinDataRate = new MinDataRate(bytesPerSecond: double.MaxValue, gracePeriod: Heartbeat.Interval + TimeSpan.FromTicks(1)); using (var stream = await context.Features.Get <IHttpUpgradeFeature>().UpgradeAsync()) { appEvent.SetResult(null); // Read once to go through one set of TryPauseTimingReads()/TryResumeTimingReads() calls await stream.ReadAsync(new byte[1], 0, 1); await delayEvent.Task.DefaultTimeout(); // Read again to check that the connection is still alive await stream.ReadAsync(new byte[1], 0, 1); // Send a response to distinguish from the timeout case where the 101 is still received, but without any content var response = Encoding.ASCII.GetBytes("hello"); await stream.WriteAsync(response, 0, response.Length); } }, serviceContext)) { using (var connection = server.CreateConnection()) { await connection.Send( "GET / HTTP/1.1", "Host:", "Connection: upgrade", "", "a"); await appEvent.Task.DefaultTimeout(); serviceContext.MockSystemClock.UtcNow += TimeSpan.FromSeconds(5); heartbeatManager.OnHeartbeat(serviceContext.SystemClock.UtcNow); delayEvent.SetResult(null); await connection.Send("b"); await connection.ReceiveEnd( "HTTP/1.1 101 Switching Protocols", "Connection: Upgrade", $"Date: {server.Context.DateHeaderValue}", "", "hello"); } await server.StopAsync(); } }
public async Task HandshakeTimesOutAndIsLoggedAsDebug() { var loggerProvider = new HandshakeErrorLoggerProvider(); LoggerFactory.AddProvider(loggerProvider); var testContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); var handshakeStartedTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); TimeSpan handshakeTimeout = default; await using (var server = new TestServer(context => Task.CompletedTask, testContext, listenOptions => { listenOptions.UseHttps(o => { o.ServerCertificate = new X509Certificate2(TestResources.GetTestCertificate()); o.OnAuthenticate = (_, __) => { handshakeStartedTcs.SetResult(null); }; handshakeTimeout = o.HandshakeTimeout; }); })) { using (var connection = server.CreateConnection()) { // HttpsConnectionAdapter dispatches via Task.Run() before starting the handshake. // Wait for the handshake to start before advancing the system clock. await handshakeStartedTcs.Task.DefaultTimeout(); // Min amount of time between requests that triggers a handshake timeout. testContext.MockSystemClock.UtcNow += handshakeTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1); heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); Assert.Equal(0, await connection.Stream.ReadAsync(new byte[1], 0, 1).DefaultTimeout()); } } await loggerProvider.FilterLogger.LogTcs.Task.DefaultTimeout(); Assert.Equal(2, loggerProvider.FilterLogger.LastEventId); Assert.Equal(LogLevel.Debug, loggerProvider.FilterLogger.LastLogLevel); }
private async Task ConnectionTimesOutWhenOpenedButNoRequestSent() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); using (var server = CreateServer(testContext)) using (var connection = server.CreateConnection()) { // Min amount of time between requests that triggers a keep-alive timeout. testContext.MockSystemClock.UtcNow += _keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1); heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); await connection.WaitForConnectionClose(); } }
public async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(string requestLine) { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); using (var server = CreateServer(testContext)) using (var connection = server.CreateConnection()) { await connection.Send(requestLine); // Min amount of time between requests that triggers a request headers timeout. testContext.MockSystemClock.UtcNow += RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1); heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); await ReceiveTimeoutResponse(connection, testContext); } }
public async Task TimeoutNotResetOnEachRequestLineCharacterReceived() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); using (var server = CreateServer(testContext)) using (var connection = server.CreateConnection()) { foreach (var ch in "POST / HTTP/1.1\r\nHost:\r\n\r\n") { await connection.Send(ch.ToString()); testContext.MockSystemClock.UtcNow += ShortDelay; heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); } await ReceiveTimeoutResponse(connection, testContext); await connection.WaitForConnectionClose(); } }
public async Task TimeoutNotResetOnEachRequestLineCharacterReceived() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); using (var server = CreateServer(testContext)) using (var connection = server.CreateConnection()) { // When the in-memory connection is aborted, the input PipeWriter is completed behind the scenes // so eventually connection.Send() throws an InvalidOperationException. await Assert.ThrowsAsync <InvalidOperationException>(async() => { foreach (var ch in "POST / HTTP/1.1\r\nHost:\r\n\r\n") { await connection.Send(ch.ToString()); testContext.MockSystemClock.UtcNow += ShortDelay; heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); } }); await ReceiveTimeoutResponse(connection, testContext); } }
public async Task RequestTimesOutWhenRequestBodyNotReceivedAtSpecifiedMinimumRate() { var gracePeriod = TimeSpan.FromSeconds(5); var serviceContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(serviceContext.ConnectionManager); var appRunningEvent = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); using (var server = new TestServer(context => { context.Features.Get <IHttpMinRequestBodyDataRateFeature>().MinDataRate = new MinDataRate(bytesPerSecond: 1, gracePeriod: gracePeriod); // The server must call Request.Body.ReadAsync() *before* the test sets systemClock.UtcNow (which is triggered by the // server calling appRunningEvent.SetResult(null)). If systemClock.UtcNow is set first, it's possible for the test to fail // due to the following race condition: // // 1. [test] systemClock.UtcNow += gracePeriod + TimeSpan.FromSeconds(1); // 2. [server] Heartbeat._timer is triggered, which calls HttpConnection.Tick() // 3. [server] HttpConnection.Tick() calls HttpConnection.CheckForReadDataRateTimeout() // 4. [server] HttpConnection.CheckForReadDataRateTimeout() is a no-op, since _readTimingEnabled is false, // since Request.Body.ReadAsync() has not been called yet // 5. [server] HttpConnection.Tick() sets _lastTimestamp = timestamp // 6. [server] Request.Body.ReadAsync() is called // 6. [test] systemClock.UtcNow is never updated again, so server timestamp is never updated, // so HttpConnection.CheckForReadDataRateTimeout() is always a no-op until test fails // // This is a pretty tight race, since the once-per-second Heartbeat._timer needs to fire between the test updating // systemClock.UtcNow and the server calling Request.Body.ReadAsync(). But it happened often enough to cause // test flakiness in our CI (https://github.com/aspnet/KestrelHttpServer/issues/2539). // // For verification, I was able to induce the race by adding a sleep in the RequestDelegate: // appRunningEvent.SetResult(null); // Thread.Sleep(5000); // return context.Request.Body.ReadAsync(new byte[1], 0, 1); var readTask = context.Request.Body.ReadAsync(new byte[1], 0, 1); appRunningEvent.SetResult(null); return(readTask); }, serviceContext)) { using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.1", "Host:", "Content-Length: 1", "", ""); await appRunningEvent.Task.DefaultTimeout(); serviceContext.MockSystemClock.UtcNow += gracePeriod + TimeSpan.FromSeconds(1); heartbeatManager.OnHeartbeat(serviceContext.SystemClock.UtcNow); await connection.Receive( "HTTP/1.1 408 Request Timeout", ""); await connection.ReceiveForcedEnd( "Connection: close", $"Date: {serviceContext.DateHeaderValue}", "Content-Length: 0", "", ""); } } }
public async Task ConnectionClosedEvenIfAppSwallowsException() { var gracePeriod = TimeSpan.FromSeconds(5); var serviceContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HeartbeatManager(serviceContext.ConnectionManager); var appRunningTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var exceptionSwallowedTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); using (var server = new TestServer(async context => { context.Features.Get <IHttpMinRequestBodyDataRateFeature>().MinDataRate = new MinDataRate(bytesPerSecond: 1, gracePeriod: gracePeriod); // See comment in RequestTimesOutWhenRequestBodyNotReceivedAtSpecifiedMinimumRate for // why we call ReadAsync before setting the appRunningEvent. var readTask = context.Request.Body.ReadAsync(new byte[1], 0, 1); appRunningTcs.SetResult(null); try { await readTask; } catch (BadHttpRequestException ex) when(ex.StatusCode == 408) { exceptionSwallowedTcs.SetResult(null); } catch (Exception ex) { exceptionSwallowedTcs.SetException(ex); } var response = "hello, world"; context.Response.ContentLength = response.Length; await context.Response.WriteAsync("hello, world"); }, serviceContext)) { using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.1", "Host:", "Content-Length: 1", "", ""); await appRunningTcs.Task.DefaultTimeout(); serviceContext.MockSystemClock.UtcNow += gracePeriod + TimeSpan.FromSeconds(1); heartbeatManager.OnHeartbeat(serviceContext.SystemClock.UtcNow); await exceptionSwallowedTcs.Task.DefaultTimeout(); await connection.Receive( "HTTP/1.1 200 OK", ""); await connection.ReceiveForcedEnd( $"Date: {serviceContext.DateHeaderValue}", "Content-Length: 12", "", "hello, world"); } } }