private async Task KeepAliveTimeoutDoesNotApplyToUpgradedConnections() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HttpHeartbeatManager(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"); } }
public async Task ConnectionNotTimedOutWhileRequestBeingSent() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HttpHeartbeatManager(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 ConnectionNotTimedOutWhileAppIsRunning() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HttpHeartbeatManager(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 RequestHeadersTimeoutCanceledAfterHeadersReceived() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager); using (var server = CreateServer(testContext)) using (var connection = server.CreateConnection()) { 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); } }
private static ServiceContext CreateServiceContext(IOptions <KestrelServerOptions> options, ILoggerFactory loggerFactory) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (loggerFactory == null) { throw new ArgumentNullException(nameof(loggerFactory)); } var serverOptions = options.Value ?? new KestrelServerOptions(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel"); var trace = new KestrelTrace(logger); var connectionManager = new HttpConnectionManager( trace, serverOptions.Limits.MaxConcurrentUpgradedConnections); var systemClock = new SystemClock(); var dateHeaderValueManager = new DateHeaderValueManager(systemClock); var httpHeartbeatManager = new HttpHeartbeatManager(connectionManager); var heartbeat = new Heartbeat( new IHeartbeatHandler[] { dateHeaderValueManager, httpHeartbeatManager }, systemClock, DebuggerWrapper.Singleton, trace); // TODO: This logic will eventually move into the IConnectionHandler<T> and off // the service context once we get to https://github.com/aspnet/KestrelHttpServer/issues/1662 PipeScheduler scheduler = null; switch (serverOptions.ApplicationSchedulingMode) { case SchedulingMode.Default: case SchedulingMode.ThreadPool: scheduler = PipeScheduler.ThreadPool; break; case SchedulingMode.Inline: scheduler = PipeScheduler.Inline; break; default: throw new NotSupportedException(CoreStrings.FormatUnknownTransportMode(serverOptions.ApplicationSchedulingMode)); } return(new ServiceContext { Log = trace, HttpParser = new HttpParser <Http1ParsingHandler>(trace.IsEnabled(LogLevel.Information)), Scheduler = scheduler, SystemClock = systemClock, DateHeaderValueManager = dateHeaderValueManager, ConnectionManager = connectionManager, Heartbeat = heartbeat, ServerOptions = serverOptions, }); }
public void InitializeHeartbeat() { MockSystemClock = null; SystemClock = new SystemClock(); DateHeaderValueManager = new DateHeaderValueManager(SystemClock); var heartbeatManager = new HttpHeartbeatManager(ConnectionManager); Heartbeat = new Heartbeat( new IHeartbeatHandler[] { DateHeaderValueManager, heartbeatManager }, SystemClock, DebuggerWrapper.Singleton, Log); }
private async Task ConnectionTimesOutWhenOpenedButNoRequestSent() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HttpHeartbeatManager(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 HttpHeartbeatManager(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 HandshakeTimesOutAndIsLoggedAsDebug() { var loggerProvider = new HandshakeErrorLoggerProvider(); LoggerFactory.AddProvider(loggerProvider); var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager); var handshakeStartedTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); TimeSpan handshakeTimeout = default; using (var server = new TestServer(context => Task.CompletedTask, testContext, listenOptions => { listenOptions.UseHttps(o => { o.ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"); o.OnHandshakeStarted = () => 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); }
// For testing internal KestrelServer(ITransportFactory transportFactory, ServiceContext serviceContext) { if (transportFactory == null) { throw new ArgumentNullException(nameof(transportFactory)); } _transportFactory = transportFactory; ServiceContext = serviceContext; var httpHeartbeatManager = new HttpHeartbeatManager(serviceContext.ConnectionManager); _heartbeat = new Heartbeat( new IHeartbeatHandler[] { serviceContext.DateHeaderValueManager, httpHeartbeatManager }, serviceContext.SystemClock, Trace); Features = new FeatureCollection(); _serverAddresses = new ServerAddressesFeature(); Features.Set(_serverAddresses); }
public async Task ConnectionClosedWhenKeepAliveTimeoutExpires() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager); using (var server = CreateServer(testContext)) using (var connection = server.CreateConnection()) { 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 ConnectionKeptAliveBetweenRequests() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager); using (var server = CreateServer(testContext)) using (var connection = server.CreateConnection()) { 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 TimeoutNotResetOnEachRequestLineCharacterReceived() { var testContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HttpHeartbeatManager(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 DoesNotEnforceRequestBodyMinimumDataRateOnUpgradedRequest() { var appEvent = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var delayEvent = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var serviceContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HttpHeartbeatManager(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.Receive( "HTTP/1.1 101 Switching Protocols", "Connection: Upgrade", ""); await connection.ReceiveStartsWith( $"Date: "); await connection.ReceiveForcedEnd( "", "hello"); } } }
public async Task RequestTimesOutWhenRequestBodyNotReceivedAtSpecifiedMinimumRate() { var gracePeriod = TimeSpan.FromSeconds(5); var serviceContext = new TestServiceContext(LoggerFactory); var heartbeatManager = new HttpHeartbeatManager(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 HttpHeartbeatManager(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"); } } }