public async Task RequestBodyIsConsumedAutomaticallyIfAppDoesntConsumeItFully(ListenOptions listenOptions) { var testContext = new TestServiceContext(LoggerFactory); using (var server = new TestServer(async httpContext => { var response = httpContext.Response; var request = httpContext.Request; Assert.Equal("POST", request.Method); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); }, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.1", "Host:", "Content-Length: 5", "", "HelloPOST / HTTP/1.1", "Host:", "Transfer-Encoding: chunked", "", "C", "HelloChunked", "0", "", "POST / HTTP/1.1", "Host:", "Content-Length: 7", "", "Goodbye"); await connection.ReceiveEnd( "HTTP/1.1 200 OK", $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello WorldHTTP/1.1 200 OK", $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello WorldHTTP/1.1 200 OK", $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello World"); } } }
public async Task ClientAbortingConnectionImmediatelyIsNotLoggedHigherThanDebug(ListenOptions listenOptions) { // Attempt multiple connections to be extra sure the resets are consistently logged appropriately. const int numConnections = 10; // There's not guarantee that the app even gets invoked in this test. The connection reset can be observed // as early as accept. var testServiceContext = new TestServiceContext(LoggerFactory); using (var server = new TestServer(context => Task.CompletedTask, testServiceContext, listenOptions)) { for (var i = 0; i < numConnections; i++) { using (var connection = server.CreateConnection()) { await connection.Send( "GET / HTTP/1.1", "Host:", "", ""); connection.Reset(); } } await server.StopAsync(); } var transportLogs = TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" || w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"); // The "Microsoft.AspNetCore.Server.Kestrel" logger may contain info level logs because resetting the connection can cause // partial headers to be read leading to a bad request. var coreLogs = TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel"); Assert.Empty(transportLogs.Where(w => w.LogLevel > LogLevel.Debug)); Assert.Empty(coreLogs.Where(w => w.LogLevel > LogLevel.Information)); }
public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeaders() { var headerSize = 1024 * 1024; // 1 MB for each header value var headerCount = 64; // 64 MB of headers per response var requestCount = 4; // Minimum of 256 MB of total response headers var headerValue = new string('a', headerSize); var headerStringValues = new StringValues(Enumerable.Repeat(headerValue, headerCount).ToArray()); var requestAborted = false; var mockKestrelTrace = new Mock <IKestrelTrace>(); var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { ServerOptions = { Limits = { MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) } } }; testContext.InitializeHeartbeat(); var dateHeaderValueManager = new DateHeaderValueManager(); dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); testContext.DateHeaderValueManager = dateHeaderValueManager; var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); async Task App(HttpContext context) { context.RequestAborted.Register(() => { requestAborted = true; }); context.Response.Headers[$"X-Custom-Header"] = headerStringValues; context.Response.ContentLength = 0; await context.Response.BodyWriter.FlushAsync(); } using (var server = new TestServer(App, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { for (var i = 0; i < requestCount - 1; i++) { await connection.Send( "GET / HTTP/1.1", "Host:", "", ""); } await connection.Send( "GET / HTTP/1.1", "Host:", "", ""); await connection.Receive( "HTTP/1.1 200 OK", $"Date: {dateHeaderValueManager.GetDateHeaderValues().String}"); var minResponseSize = headerSize * headerCount; var minTotalOutputSize = requestCount * minResponseSize; // Make sure consuming a single set of response headers exceeds the 2 second timeout. var targetBytesPerSecond = minResponseSize / 4; // expectedBytes was determined by manual testing. A constant Date header is used, so this shouldn't change unless // the response header writing logic itself changes. await AssertBytesReceivedAtTargetRate(connection.Stream, expectedBytes : 268_439_596, targetBytesPerSecond); connection.ShutdownSend(); await connection.WaitForConnectionClose(); } await server.StopAsync(); } mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny <string>(), It.IsAny <string>()), Times.Never()); mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny <string>()), Times.Once()); Assert.False(requestAborted); }
public async Task ServerCanAbortConnectionAfterUnobservedClose(ListenOptions listenOptions) { const int connectionPausedEventId = 4; const int connectionFinSentEventId = 7; const int maxRequestBufferSize = 4096; var readCallbackUnwired = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var clientClosedConnection = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var serverClosedConnection = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var appFuncCompleted = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var mockLogger = new Mock <ILogger>(); mockLogger .Setup(logger => logger.IsEnabled(It.IsAny <LogLevel>())) .Returns(true); mockLogger .Setup(logger => logger.Log(It.IsAny <LogLevel>(), It.IsAny <EventId>(), It.IsAny <object>(), It.IsAny <Exception>(), It.IsAny <Func <object, Exception, string> >())) .Callback <LogLevel, EventId, object, Exception, Func <object, Exception, string> >((logLevel, eventId, state, exception, formatter) => { if (eventId.Id == connectionPausedEventId) { readCallbackUnwired.TrySetResult(null); } else if (eventId.Id == connectionFinSentEventId) { serverClosedConnection.SetResult(null); } Logger.Log(logLevel, eventId, state, exception, formatter); }); var mockLoggerFactory = new Mock <ILoggerFactory>(); mockLoggerFactory .Setup(factory => factory.CreateLogger(It.IsAny <string>())) .Returns(Logger); mockLoggerFactory .Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv", "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"))) .Returns(mockLogger.Object); var mockKestrelTrace = new Mock <IKestrelTrace>(); var testContext = new TestServiceContext(mockLoggerFactory.Object, mockKestrelTrace.Object) { ServerOptions = { Limits = { MaxRequestBufferSize = maxRequestBufferSize, MaxRequestLineSize = maxRequestBufferSize, MaxRequestHeadersTotalSize = maxRequestBufferSize, } } }; var scratchBuffer = new byte[maxRequestBufferSize * 8]; using (var server = new TestServer(async context => { await clientClosedConnection.Task; context.Abort(); await serverClosedConnection.Task; appFuncCompleted.SetResult(null); }, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.1", "Host:", $"Content-Length: {scratchBuffer.Length}", "", ""); var ignore = connection.Stream.WriteAsync(scratchBuffer, 0, scratchBuffer.Length); // Wait until the read callback is no longer hooked up so that the connection disconnect isn't observed. await readCallbackUnwired.Task.DefaultTimeout(); } clientClosedConnection.SetResult(null); await appFuncCompleted.Task.DefaultTimeout(); } mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny <string>()), Times.Once()); }
public async Task WritingToConnectionAfterUnobservedCloseTriggersRequestAbortedToken(ListenOptions listenOptions) { const int connectionPausedEventId = 4; const int maxRequestBufferSize = 4096; var requestAborted = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var readCallbackUnwired = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var clientClosedConnection = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var writeTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); TestSink.MessageLogged += context => { if (context.LoggerName != "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" && context.LoggerName != "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets") { return; } if (context.EventId.Id == connectionPausedEventId) { readCallbackUnwired.TrySetResult(null); } }; var mockKestrelTrace = new Mock <IKestrelTrace>(); var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { ServerOptions = { Limits = { MaxRequestBufferSize = maxRequestBufferSize, MaxRequestLineSize = maxRequestBufferSize, MaxRequestHeadersTotalSize = maxRequestBufferSize, } } }; var scratchBuffer = new byte[maxRequestBufferSize * 8]; using (var server = new TestServer(async context => { context.RequestAborted.Register(() => requestAborted.SetResult(null)); await clientClosedConnection.Task; try { for (var i = 0; i < 1000; i++) { await context.Response.BodyWriter.WriteAsync(new Memory <byte>(scratchBuffer, 0, scratchBuffer.Length), context.RequestAborted); await Task.Delay(10); } } catch (Exception ex) { writeTcs.SetException(ex); throw; } finally { await requestAborted.Task.DefaultTimeout(); } writeTcs.SetException(new Exception("This shouldn't be reached.")); }, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.1", "Host:", $"Content-Length: {scratchBuffer.Length}", "", ""); var ignore = connection.Stream.WriteAsync(scratchBuffer, 0, scratchBuffer.Length); // Wait until the read callback is no longer hooked up so that the connection disconnect isn't observed. await readCallbackUnwired.Task.DefaultTimeout(); } clientClosedConnection.SetResult(null); await Assert.ThrowsAnyAsync <OperationCanceledException>(() => writeTcs.Task).DefaultTimeout(); await server.StopAsync(); } mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny <string>()), Times.Once()); Assert.True(requestAborted.Task.IsCompleted); }
public static void ServerListenOptions(this ListenOptions listenOptions, string pathCetificate, string password) { listenOptions.UseHttps(pathCetificate, password); listenOptions.Protocols = HttpProtocols.Http1AndHttp2; }
internal EndpointConfiguration(bool isHttps, ListenOptions listenOptions, HttpsConnectionAdapterOptions httpsOptions) { IsHttps = isHttps; ListenOptions = listenOptions ?? throw new ArgumentNullException(nameof(listenOptions)); HttpsOptions = httpsOptions ?? throw new ArgumentNullException(nameof(httpsOptions)); }
protected virtual void ApplySocketOptions(Socket socket, ListenOptions listenOptions, ChannelOptions channelOptions, Dynamic.Core.Log.ILogger logger) { try { if (listenOptions.NoDelay) { socket.NoDelay = true; } } catch (Exception e) { logger.Warn("Failed to set NoDelay for the socket." + e.ToString()); } try { if (channelOptions.ReceiveBufferSize > 0) { socket.ReceiveBufferSize = channelOptions.ReceiveBufferSize; } } catch (Exception e) { logger.Warn(e.ToString() + "Failed to set ReceiveBufferSize for the socket."); } try { if (channelOptions.SendBufferSize > 0) { socket.SendBufferSize = channelOptions.SendBufferSize; } } catch (Exception e) { logger.Warn(e.ToString() + "Failed to set SendBufferSize for the socket."); } try { if (channelOptions.ReceiveTimeout > 0) { socket.ReceiveTimeout = channelOptions.ReceiveTimeout; } } catch (Exception e) { logger.Warn(e.ToString() + "Failed to set ReceiveTimeout for the socket."); } try { if (channelOptions.SendTimeout > 0) { socket.SendTimeout = channelOptions.SendTimeout; } } catch (Exception e) { logger.Warn(e.ToString() + "Failed to set SendTimeout for the socket."); } try { _socketOptionsSetter?.Invoke(socket); } catch (Exception e) { logger.Warn(e.ToString() + "Failed to run socketOptionSetter for the socket."); } }
public EndPointInformation(ListenOptions listenOptions) { }
public async Task ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate() { using (StartLog(out var loggerFactory, "ConnClosedWhenRespDoesNotSatisfyMin")) { var logger = loggerFactory.CreateLogger($"{ typeof(ResponseTests).FullName}.{ nameof(ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate)}"); const int chunkSize = 1024; const int chunks = 256 * 1024; var responseSize = chunks * chunkSize; var chunkData = new byte[chunkSize]; var responseRateTimeoutMessageLogged = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var connectionStopMessageLogged = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var requestAborted = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var appFuncCompleted = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var mockKestrelTrace = new Mock <IKestrelTrace>(); mockKestrelTrace .Setup(trace => trace.ResponseMinimumDataRateNotSatisfied(It.IsAny <string>(), It.IsAny <string>())) .Callback(() => responseRateTimeoutMessageLogged.SetResult(null)); mockKestrelTrace .Setup(trace => trace.ConnectionStop(It.IsAny <string>())) .Callback(() => connectionStopMessageLogged.SetResult(null)); var testContext = new TestServiceContext(loggerFactory, mockKestrelTrace.Object) { ServerOptions = { Limits = { MinResponseDataRate = new MinDataRate(bytesPerSecond: 1024 * 1024, gracePeriod: TimeSpan.FromSeconds(2)) } } }; testContext.InitializeHeartbeat(); var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); listenOptions.ConnectionAdapters.Add(new LoggingConnectionAdapter(loggerFactory.CreateLogger <LoggingConnectionAdapter>())); var appLogger = loggerFactory.CreateLogger("App"); async Task App(HttpContext context) { appLogger.LogInformation("Request received"); context.RequestAborted.Register(() => requestAborted.SetResult(null)); context.Response.ContentLength = responseSize; try { for (var i = 0; i < chunks; i++) { await context.Response.Body.WriteAsync(chunkData, 0, chunkData.Length, context.RequestAborted); appLogger.LogInformation("Wrote chunk of {chunkSize} bytes", chunkSize); } } catch (OperationCanceledException) { appFuncCompleted.SetResult(null); throw; } } using (var server = new TestServer(App, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { logger.LogInformation("Sending request"); await connection.Send( "GET / HTTP/1.1", "Host:", "", ""); logger.LogInformation("Sent request"); var sw = Stopwatch.StartNew(); logger.LogInformation("Waiting for connection to abort."); await requestAborted.Task.DefaultTimeout(); await responseRateTimeoutMessageLogged.Task.DefaultTimeout(); await connectionStopMessageLogged.Task.DefaultTimeout(); await appFuncCompleted.Task.DefaultTimeout(); await AssertStreamAborted(connection.Stream, chunkSize *chunks); sw.Stop(); logger.LogInformation("Connection was aborted after {totalMilliseconds}ms.", sw.ElapsedMilliseconds); } } } }
public TcpChannelCreator(ListenOptions options, Func <Socket, Task <IChannel> > channelFactory, ILogger logger) { Options = options; _channelFactory = channelFactory; _logger = logger; }
public async Task WritingToConnectionAfterUnobservedCloseTriggersRequestAbortedToken(ListenOptions listenOptions) { const int connectionPausedEventId = 4; const int maxRequestBufferSize = 4096; var requestAborted = false; var readCallbackUnwired = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var clientClosedConnection = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var writeTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var mockKestrelTrace = new Mock <IKestrelTrace>(); var mockLogger = new Mock <ILogger>(); mockLogger .Setup(logger => logger.IsEnabled(It.IsAny <LogLevel>())) .Returns(true); mockLogger .Setup(logger => logger.Log(It.IsAny <LogLevel>(), It.IsAny <EventId>(), It.IsAny <object>(), It.IsAny <Exception>(), It.IsAny <Func <object, Exception, string> >())) .Callback <LogLevel, EventId, object, Exception, Func <object, Exception, string> >((logLevel, eventId, state, exception, formatter) => { if (eventId.Id == connectionPausedEventId) { readCallbackUnwired.TrySetResult(null); } Logger.Log(logLevel, eventId, state, exception, formatter); }); var mockLoggerFactory = new Mock <ILoggerFactory>(); mockLoggerFactory .Setup(factory => factory.CreateLogger(It.IsAny <string>())) .Returns(Logger); mockLoggerFactory .Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv", "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"))) .Returns(mockLogger.Object); var testContext = new TestServiceContext(mockLoggerFactory.Object, mockKestrelTrace.Object) { ServerOptions = { Limits = { MaxRequestBufferSize = maxRequestBufferSize, MaxRequestLineSize = maxRequestBufferSize, MaxRequestHeadersTotalSize = maxRequestBufferSize, } } }; var scratchBuffer = new byte[maxRequestBufferSize * 8]; using (var server = new TestServer(async context => { context.RequestAborted.Register(() => { requestAborted = true; }); await clientClosedConnection.Task; try { for (var i = 0; i < 1000; i++) { await context.Response.Body.WriteAsync(scratchBuffer, 0, scratchBuffer.Length, context.RequestAborted); await Task.Delay(10); } } catch (Exception ex) { writeTcs.SetException(ex); throw; } writeTcs.SetException(new Exception("This shouldn't be reached.")); }, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.1", "Host:", $"Content-Length: {scratchBuffer.Length}", "", ""); var ignore = connection.Stream.WriteAsync(scratchBuffer, 0, scratchBuffer.Length); // Wait until the read callback is no longer hooked up so that the connection disconnect isn't observed. await readCallbackUnwired.Task.DefaultTimeout(); } clientClosedConnection.SetResult(null); await Assert.ThrowsAnyAsync <OperationCanceledException>(() => writeTcs.Task).DefaultTimeout(); } mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny <string>()), Times.Once()); Assert.True(requestAborted); }
public async Task ChunkedNotFinalTransferCodingResultsIn400(ListenOptions listenOptions) { var testContext = new TestServiceContext(LoggerFactory); using (var server = new TestServer(httpContext => { return(Task.CompletedTask); }, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { await connection.SendAll( "POST / HTTP/1.1", "Host:", "Transfer-Encoding: not-chunked", "", "C", "hello, world", "0", "", ""); await connection.ReceiveForcedEnd( "HTTP/1.1 400 Bad Request", "Connection: close", $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "", ""); } // Content-Length should not affect this using (var connection = server.CreateConnection()) { await connection.SendAll( "POST / HTTP/1.1", "Host:", "Transfer-Encoding: not-chunked", "Content-Length: 22", "", "C", "hello, world", "0", "", ""); await connection.ReceiveForcedEnd( "HTTP/1.1 400 Bad Request", "Connection: close", $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "", ""); } using (var connection = server.CreateConnection()) { await connection.SendAll( "POST / HTTP/1.1", "Host:", "Transfer-Encoding: chunked, not-chunked", "", "C", "hello, world", "0", "", ""); await connection.ReceiveForcedEnd( "HTTP/1.1 400 Bad Request", "Connection: close", $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "", ""); } // Content-Length should not affect this using (var connection = server.CreateConnection()) { await connection.SendAll( "POST / HTTP/1.1", "Host:", "Transfer-Encoding: chunked, not-chunked", "Content-Length: 22", "", "C", "hello, world", "0", "", ""); await connection.ReceiveForcedEnd( "HTTP/1.1 400 Bad Request", "Connection: close", $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "", ""); } } }
public async Task ExtensionsAreIgnored(ListenOptions listenOptions) { var testContext = new TestServiceContext(LoggerFactory); var requestCount = 10; var requestsReceived = 0; using (var server = new TestServer(async httpContext => { var response = httpContext.Response; var request = httpContext.Request; var buffer = new byte[200]; while (await request.Body.ReadAsync(buffer, 0, buffer.Length) != 0) { ;// read to end } if (requestsReceived < requestCount) { Assert.Equal(new string('a', requestsReceived), request.Headers["X-Trailer-Header"].ToString()); } else { Assert.True(string.IsNullOrEmpty(request.Headers["X-Trailer-Header"])); } requestsReceived++; response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); }, testContext, listenOptions)) { var response = string.Join("\r\n", new string[] { "HTTP/1.1 200 OK", $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello World" }); var expectedFullResponse = string.Join("", Enumerable.Repeat(response, requestCount + 1)); IEnumerable <string> sendSequence = new string[] { "POST / HTTP/1.1", "Host:", "Transfer-Encoding: chunked", "", "C;hello there", "HelloChunked", "0;hello there", "" }; for (var i = 1; i < requestCount; i++) { sendSequence = sendSequence.Concat(new string[] { "POST / HTTP/1.1", "Host:", "Transfer-Encoding: chunked", "", "C;hello there", $"HelloChunk{i:00}", "0;hello there", string.Concat("X-Trailer-Header: ", new string('a', i)), "" }); } sendSequence = sendSequence.Concat(new string[] { "POST / HTTP/1.1", "Host:", "Content-Length: 7", "", "Goodbye" }); var fullRequest = sendSequence.ToArray(); using (var connection = server.CreateConnection()) { await connection.Send(fullRequest); await connection.ReceiveEnd(expectedFullResponse); } } }
public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseChunks() { var chunkSize = 64 * 128 * 1024; var chunkCount = 4; var chunkData = new byte[chunkSize]; var requestAborted = false; var appFuncCompleted = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var mockKestrelTrace = new Mock <IKestrelTrace>(); var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { ServerOptions = { Limits = { MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) } } }; testContext.InitializeHeartbeat(); var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); async Task App(HttpContext context) { context.RequestAborted.Register(() => { requestAborted = true; }); for (var i = 0; i < chunkCount; i++) { await context.Response.BodyPipe.WriteAsync(new Memory <byte>(chunkData, 0, chunkData.Length), context.RequestAborted); } appFuncCompleted.SetResult(null); } using (var server = new TestServer(App, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { // Close the connection with the last request so AssertStreamCompleted actually completes. await connection.Send( "GET / HTTP/1.1", "Host:", "Connection: close", "", ""); var minTotalOutputSize = chunkCount * chunkSize; // Make sure consuming a single chunk exceeds the 2 second timeout. var targetBytesPerSecond = chunkSize / 4; await AssertStreamCompleted(connection.Stream, minTotalOutputSize, targetBytesPerSecond); await appFuncCompleted.Task.DefaultTimeout(); } await server.StopAsync(); } mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny <string>(), It.IsAny <string>()), Times.Never()); mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny <string>()), Times.Once()); Assert.False(requestAborted); }
/// <summary> /// Configure Kestrel to use HTTPS. /// </summary> /// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param> /// <param name="storeName">The certificate store to load the certificate from.</param> /// <param name="subject">The subject name for the certificate to load.</param> /// <param name="allowInvalid">Indicates if invalid certificates should be considered, such as self-signed certificates.</param> /// <param name="location">The store location to load the certificate from.</param> /// <returns>The <see cref="ListenOptions"/>.</returns> public static ListenOptions UseHttps(this ListenOptions listenOptions, StoreName storeName, string subject, bool allowInvalid, StoreLocation location) => listenOptions.UseHttps(storeName, subject, allowInvalid, location, configureOptions: _ => { });
public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeaders() { var headerSize = 1024 * 1024; // 1 MB for each header value var headerCount = 64; // 64 MB of headers per response var requestCount = 4; // Minimum of 256 MB of total response headers var headerValue = new string('a', headerSize); var headerStringValues = new StringValues(Enumerable.Repeat(headerValue, headerCount).ToArray()); var requestAborted = false; var mockKestrelTrace = new Mock <IKestrelTrace>(); var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { ServerOptions = { Limits = { MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) } } }; testContext.InitializeHeartbeat(); var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); async Task App(HttpContext context) { context.RequestAborted.Register(() => { requestAborted = true; }); context.Response.Headers[$"X-Custom-Header"] = headerStringValues; context.Response.ContentLength = 0; await context.Response.BodyPipe.FlushAsync(); } using (var server = new TestServer(App, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { for (var i = 0; i < requestCount - 1; i++) { await connection.Send( "GET / HTTP/1.1", "Host:", "", ""); } // Close the connection with the last request so AssertStreamCompleted actually completes. await connection.Send( "GET / HTTP/1.1", "Host:", "Connection: close", "", ""); var responseSize = headerSize * headerCount; var minTotalOutputSize = requestCount * responseSize; // Make sure consuming a single set of response headers exceeds the 2 second timeout. var targetBytesPerSecond = responseSize / 4; await AssertStreamCompleted(connection.Stream, minTotalOutputSize, targetBytesPerSecond); } await server.StopAsync(); } mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny <string>(), It.IsAny <string>()), Times.Never()); mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny <string>()), Times.Once()); Assert.False(requestAborted); }
/// <summary> /// Configure Kestrel to use HTTPS. /// </summary> /// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param> /// <param name="storeName">The certificate store to load the certificate from.</param> /// <param name="subject">The subject name for the certificate to load.</param> /// <param name="allowInvalid">Indicates if invalid certificates should be considered, such as self-signed certificates.</param> /// <param name="location">The store location to load the certificate from.</param> /// <param name="configureOptions">An Action to configure the <see cref="HttpsConnectionAdapterOptions"/>.</param> /// <returns>The <see cref="ListenOptions"/>.</returns> public static ListenOptions UseHttps(this ListenOptions listenOptions, StoreName storeName, string subject, bool allowInvalid, StoreLocation location, Action <HttpsConnectionAdapterOptions> configureOptions) { return(listenOptions.UseHttps(CertificateLoader.LoadFromStoreCert(subject, storeName.ToString(), location, allowInvalid), configureOptions)); }
public async Task ServerCanAbortConnectionAfterUnobservedClose(ListenOptions listenOptions) { const int connectionPausedEventId = 4; const int connectionFinSentEventId = 7; const int maxRequestBufferSize = 4096; var readCallbackUnwired = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var clientClosedConnection = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var serverClosedConnection = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var appFuncCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); TestSink.MessageLogged += context => { if (context.LoggerName != "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" && context.LoggerName != "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets") { return; } if (context.EventId.Id == connectionPausedEventId) { readCallbackUnwired.TrySetResult(); } else if (context.EventId == connectionFinSentEventId) { serverClosedConnection.SetResult(); } }; var mockKestrelTrace = new Mock <IKestrelTrace>(); var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { ServerOptions = { Limits = { MaxRequestBufferSize = maxRequestBufferSize, MaxRequestLineSize = maxRequestBufferSize, MaxRequestHeadersTotalSize = maxRequestBufferSize, } } }; var scratchBuffer = new byte[maxRequestBufferSize * 8]; await using (var server = new TestServer(async context => { await clientClosedConnection.Task; context.Abort(); await serverClosedConnection.Task; appFuncCompleted.SetResult(); }, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.1", "Host:", $"Content-Length: {scratchBuffer.Length}", "", ""); var ignore = connection.Stream.WriteAsync(scratchBuffer, 0, scratchBuffer.Length); // Wait until the read callback is no longer hooked up so that the connection disconnect isn't observed. await readCallbackUnwired.Task.DefaultTimeout(); } clientClosedConnection.SetResult(); await appFuncCompleted.Task.DefaultTimeout(); } mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny <string>()), Times.Once()); }
/// <summary> /// Configure Kestrel to use HTTPS with the default certificate if available. /// This will throw if no default certificate is configured. /// </summary> /// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param> /// <returns>The <see cref="ListenOptions"/>.</returns> public static ListenOptions UseHttps(this ListenOptions listenOptions) => listenOptions.UseHttps(_ => { });
public async Task RequestsCanBeAbortedMidRead(ListenOptions listenOptions) { const int applicationAbortedConnectionId = 34; var testContext = new TestServiceContext(LoggerFactory); var readTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var registrationTcs = new TaskCompletionSource <int>(TaskCreationOptions.RunContinuationsAsynchronously); var requestId = 0; using (var server = new TestServer(async httpContext => { requestId++; var response = httpContext.Response; var request = httpContext.Request; var lifetime = httpContext.Features.Get <IHttpRequestLifetimeFeature>(); lifetime.RequestAborted.Register(() => registrationTcs.TrySetResult(requestId)); if (requestId == 1) { response.Headers["Content-Length"] = new[] { "5" }; await response.WriteAsync("World"); } else { var readTask = request.Body.CopyToAsync(Stream.Null); lifetime.Abort(); try { await readTask; } catch (Exception ex) { readTcs.SetException(ex); throw; } readTcs.SetException(new Exception("This shouldn't be reached.")); } }, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { // Full request and response await connection.Send( "POST / HTTP/1.1", "Host:", "Content-Length: 5", "", "Hello"); await connection.Receive( "HTTP/1.1 200 OK", $"Date: {testContext.DateHeaderValue}", "Content-Length: 5", "", "World"); // Never send the body so CopyToAsync always fails. await connection.Send("POST / HTTP/1.1", "Host:", "Content-Length: 5", "", ""); await connection.WaitForConnectionClose(); } } await Assert.ThrowsAsync <TaskCanceledException>(async() => await readTcs.Task); // The cancellation token for only the last request should be triggered. var abortedRequestId = await registrationTcs.Task; Assert.Equal(2, abortedRequestId); Assert.Single(TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel" && w.EventId == applicationAbortedConnectionId)); }
/// <summary> /// Configure Kestrel to use HTTPS. /// </summary> /// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param> /// <param name="fileName">The name of a certificate file, relative to the directory that contains the application /// content files.</param> /// <param name="password">The password required to access the X.509 certificate data.</param> /// <returns>The <see cref="ListenOptions"/>.</returns> public static ListenOptions UseHttps(this ListenOptions listenOptions, string fileName, string password) { var env = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService <IHostEnvironment>(); return(listenOptions.UseHttps(new X509Certificate2(Path.Combine(env.ContentRootPath, fileName), password))); }
public async Task ThrowsOnWriteWithRequestAbortedTokenAfterRequestIsAborted(ListenOptions listenOptions) { // This should match _maxBytesPreCompleted in SocketOutput var maxBytesPreCompleted = 65536; // Ensure string is long enough to disable write-behind buffering var largeString = new string('a', maxBytesPreCompleted + 1); var writeTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var requestAbortedWh = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var requestStartWh = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); using (var server = new TestServer(async httpContext => { requestStartWh.SetResult(null); var response = httpContext.Response; var request = httpContext.Request; var lifetime = httpContext.Features.Get <IHttpRequestLifetimeFeature>(); lifetime.RequestAborted.Register(() => requestAbortedWh.SetResult(null)); await requestAbortedWh.Task.DefaultTimeout(); try { await response.WriteAsync(largeString, cancellationToken: lifetime.RequestAborted); } catch (Exception ex) { writeTcs.SetException(ex); throw; } finally { await requestAbortedWh.Task.DefaultTimeout(); } writeTcs.SetException(new Exception("This shouldn't be reached.")); }, new TestServiceContext(LoggerFactory), listenOptions)) { using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.1", "Host:", "Content-Length: 0", "", ""); await requestStartWh.Task.DefaultTimeout(); } // Write failed - can throw TaskCanceledException or OperationCanceledException, // depending on how far the canceled write goes. await Assert.ThrowsAnyAsync <OperationCanceledException>(async() => await writeTcs.Task).DefaultTimeout(); // RequestAborted tripped await requestAbortedWh.Task.DefaultTimeout(); await server.StopAsync(); } }
/// <summary> /// Configure Kestrel to use HTTPS. /// </summary> /// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param> /// <param name="storeName">The certificate store to load the certificate from.</param> /// <param name="subject">The subject name for the certificate to load.</param> /// <returns>The <see cref="ListenOptions"/>.</returns> public static ListenOptions UseHttps(this ListenOptions listenOptions, StoreName storeName, string subject) => listenOptions.UseHttps(storeName, subject, allowInvalid: false);
public async Task AppCanHandleClientAbortingConnectionMidResponse(ListenOptions listenOptions) { const int connectionResetEventId = 19; const int connectionFinEventId = 6; const int connectionStopEventId = 2; const int responseBodySegmentSize = 65536; const int responseBodySegmentCount = 100; var requestAborted = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var appCompletedTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var scratchBuffer = new byte[responseBodySegmentSize]; using (var server = new TestServer(async context => { context.RequestAborted.Register(() => requestAborted.SetResult(null)); for (var i = 0; i < responseBodySegmentCount; i++) { await context.Response.Body.WriteAsync(scratchBuffer, 0, scratchBuffer.Length); await Task.Delay(10); } await requestAborted.Task.DefaultTimeout(); appCompletedTcs.SetResult(null); }, new TestServiceContext(LoggerFactory), listenOptions)) { using (var connection = server.CreateConnection()) { await connection.Send( "GET / HTTP/1.1", "Host:", "", ""); // Read just part of the response and close the connection. // https://github.com/aspnet/KestrelHttpServer/issues/2554 await connection.Stream.ReadAsync(scratchBuffer, 0, scratchBuffer.Length); connection.Reset(); } await requestAborted.Task.DefaultTimeout(); // After the RequestAborted token is tripped, the connection reset should be logged. // On Linux and macOS, the connection close is still sometimes observed as a FIN despite the LingerState. var presShutdownTransportLogs = TestSink.Writes.Where( w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" || w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"); var connectionResetLogs = presShutdownTransportLogs.Where( w => w.EventId == connectionResetEventId || (!TestPlatformHelper.IsWindows && w.EventId == connectionFinEventId)); Assert.NotEmpty(connectionResetLogs); // On macOS, the default 5 shutdown timeout is insufficient for the write loop to complete, so give it extra time. await appCompletedTcs.Task.DefaultTimeout(); await server.StopAsync(); } var coreLogs = TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel"); Assert.Single(coreLogs.Where(w => w.EventId == connectionStopEventId)); var transportLogs = TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel" || w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" || w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"); Assert.Empty(transportLogs.Where(w => w.LogLevel > LogLevel.Debug)); }
/// <summary> /// Configure Kestrel to use HTTPS. /// </summary> /// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param> /// <param name="storeName">The certificate store to load the certificate from.</param> /// <param name="subject">The subject name for the certificate to load.</param> /// <param name="allowInvalid">Indicates if invalid certificates should be considered, such as self-signed certificates.</param> /// <returns>The <see cref="ListenOptions"/>.</returns> public static ListenOptions UseHttps(this ListenOptions listenOptions, StoreName storeName, string subject, bool allowInvalid) => listenOptions.UseHttps(storeName, subject, allowInvalid, StoreLocation.CurrentUser);
public async Task ConnectionClosedWhenBothRequestAndResponseExperienceBackPressure() { const int bufferSize = 65536; const int bufferCount = 100; var responseSize = bufferCount * bufferSize; var buffer = new byte[bufferSize]; var responseRateTimeoutMessageLogged = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var connectionStopMessageLogged = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var requestAborted = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var copyToAsyncCts = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var mockKestrelTrace = new Mock <IKestrelTrace>(); mockKestrelTrace .Setup(trace => trace.ResponseMinimumDataRateNotSatisfied(It.IsAny <string>(), It.IsAny <string>())) .Callback(() => responseRateTimeoutMessageLogged.SetResult(null)); mockKestrelTrace .Setup(trace => trace.ConnectionStop(It.IsAny <string>())) .Callback(() => connectionStopMessageLogged.SetResult(null)); var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { ServerOptions = { Limits = { MinResponseDataRate = new MinDataRate(bytesPerSecond: 1024 * 1024, gracePeriod: TimeSpan.FromSeconds(2)), MaxRequestBodySize = responseSize } } }; testContext.InitializeHeartbeat(); var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); async Task App(HttpContext context) { context.RequestAborted.Register(() => { requestAborted.SetResult(null); }); try { await context.Request.Body.CopyToAsync(context.Response.Body); } catch (Exception ex) { copyToAsyncCts.SetException(ex); throw; } finally { await requestAborted.Task.DefaultTimeout(); } copyToAsyncCts.SetException(new Exception("This shouldn't be reached.")); } using (var server = new TestServer(App, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { // Close the connection with the last request so AssertStreamCompleted actually completes. await connection.Send( "POST / HTTP/1.1", "Host:", $"Content-Length: {responseSize}", "", ""); var sendTask = Task.Run(async() => { for (var i = 0; i < bufferCount; i++) { await connection.Stream.WriteAsync(buffer, 0, buffer.Length); await Task.Delay(10); } }); await requestAborted.Task.DefaultTimeout(); await responseRateTimeoutMessageLogged.Task.DefaultTimeout(); await connectionStopMessageLogged.Task.DefaultTimeout(); // Expect OperationCanceledException instead of IOException because the server initiated the abort due to a response rate timeout. await Assert.ThrowsAnyAsync <OperationCanceledException>(() => copyToAsyncCts.Task).DefaultTimeout(); await AssertStreamAborted(connection.Stream, responseSize); } await server.StopAsync(); } }
public async Task HttpsConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate() { const int chunkSize = 1024; const int chunks = 256 * 1024; var chunkData = new byte[chunkSize]; var certificate = TestResources.GetTestCertificate(); var responseRateTimeoutMessageLogged = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var connectionStopMessageLogged = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var aborted = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var appFuncCompleted = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var mockKestrelTrace = new Mock <IKestrelTrace>(); mockKestrelTrace .Setup(trace => trace.ResponseMinimumDataRateNotSatisfied(It.IsAny <string>(), It.IsAny <string>())) .Callback(() => responseRateTimeoutMessageLogged.SetResult(null)); mockKestrelTrace .Setup(trace => trace.ConnectionStop(It.IsAny <string>())) .Callback(() => connectionStopMessageLogged.SetResult(null)); var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { ServerOptions = { Limits = { MinResponseDataRate = new MinDataRate(bytesPerSecond: 1024 * 1024, gracePeriod: TimeSpan.FromSeconds(2)) } } }; testContext.InitializeHeartbeat(); var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) { ConnectionAdapters = { new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = certificate }) } }; using (var server = new TestServer(async context => { context.RequestAborted.Register(() => { aborted.SetResult(null); }); context.Response.ContentLength = chunks * chunkSize; try { for (var i = 0; i < chunks; i++) { await context.Response.BodyPipe.WriteAsync(new Memory <byte>(chunkData, 0, chunkData.Length), context.RequestAborted); } } catch (OperationCanceledException) { appFuncCompleted.SetResult(null); throw; } finally { await aborted.Task.DefaultTimeout(); } }, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { using (var sslStream = new SslStream(connection.Stream, false, (sender, cert, chain, errors) => true, null)) { await sslStream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false); var request = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n"); await sslStream.WriteAsync(request, 0, request.Length); await aborted.Task.DefaultTimeout(); await responseRateTimeoutMessageLogged.Task.DefaultTimeout(); await connectionStopMessageLogged.Task.DefaultTimeout(); await appFuncCompleted.Task.DefaultTimeout(); await AssertStreamAborted(connection.Stream, chunkSize *chunks); } } await server.StopAsync(); } }
public async Task ClientCanReceiveFullConnectionCloseResponseWithoutErrorAtALowDataRate() { var chunkSize = 64 * 128 * 1024; var chunkCount = 4; var chunkData = new byte[chunkSize]; var requestAborted = false; var appFuncCompleted = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously); var mockKestrelTrace = new Mock <IKestrelTrace>(); var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { ServerOptions = { Limits = { MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) } } }; testContext.InitializeHeartbeat(); var dateHeaderValueManager = new DateHeaderValueManager(); dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); testContext.DateHeaderValueManager = dateHeaderValueManager; var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); async Task App(HttpContext context) { context.RequestAborted.Register(() => { requestAborted = true; }); for (var i = 0; i < chunkCount; i++) { await context.Response.BodyWriter.WriteAsync(new Memory <byte>(chunkData, 0, chunkData.Length), context.RequestAborted); } appFuncCompleted.SetResult(null); } using (var server = new TestServer(App, testContext, listenOptions)) { using (var connection = server.CreateConnection()) { // Close the connection with the last request so AssertStreamCompleted actually completes. await connection.Send( "GET / HTTP/1.1", "Host:", "Connection: close", "", ""); await connection.Receive( "HTTP/1.1 200 OK", "Connection: close", $"Date: {dateHeaderValueManager.GetDateHeaderValues().String}"); // Make sure consuming a single chunk exceeds the 2 second timeout. var targetBytesPerSecond = chunkSize / 4; // expectedBytes was determined by manual testing. A constant Date header is used, so this shouldn't change unless // the response header writing logic or response body chunking logic itself changes. await AssertStreamCompletedAtTargetRate(connection.Stream, expectedBytes : 33_553_556, targetBytesPerSecond); await appFuncCompleted.Task.DefaultTimeout(); } await server.StopAsync(); } mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny <string>(), It.IsAny <string>()), Times.Never()); mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny <string>()), Times.Once()); Assert.False(requestAborted); }
internal void ApplyEndpointDefaults(ListenOptions listenOptions) { listenOptions.HtcServerOptions = this; ConfigurationLoader?.ApplyConfigurationDefaults(listenOptions); EndpointDefaults(listenOptions); }