예제 #1
0
        public async Task GracefulShutdownWaitsForRequestsToFinish()
        {
            var requestStarted   = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);
            var requestUnblocked = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);
            var requestStopping  = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);
            var mockKestrelTrace = new Mock <KestrelTrace>(TestApplicationErrorLogger)
            {
                CallBase = true
            };

            mockKestrelTrace
            .Setup(m => m.Http2ConnectionClosing(It.IsAny <string>()))
            .Callback(() => requestStopping.SetResult(null));

            var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object)
            {
                ExpectedConnectionMiddlewareCount = 1
            };

            testContext.InitializeHeartbeat();

            using (var server = new TestServer(async context =>
            {
                requestStarted.SetResult(null);
                await requestUnblocked.Task.DefaultTimeout();
                await context.Response.WriteAsync("hello world " + context.Request.Protocol);
            },
                                               testContext,
                                               kestrelOptions =>
            {
                kestrelOptions.Listen(IPAddress.Loopback, 0, listenOptions =>
                {
                    listenOptions.Protocols = HttpProtocols.Http2;
                    listenOptions.UseHttps(_x509Certificate2);
                });
            }))
            {
                var requestTask = Client.GetStringAsync($"https://localhost:{server.Port}/");
                Assert.False(requestTask.IsCompleted);

                await requestStarted.Task.DefaultTimeout();

                var stopTask = server.StopAsync();

                await requestStopping.Task.DefaultTimeout();

                // Unblock the request
                requestUnblocked.SetResult(null);

                Assert.Equal("hello world HTTP/2", await requestTask);
                await stopTask.DefaultTimeout();
            }

            Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("Request finished in"));
            Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("is closing."));
            Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("is closed. The last processed stream ID was 1."));
        }
예제 #2
0
        public async Task CriticalErrorLoggedIfApplicationDoesntComplete()
        {
            ////////////////////////////////////////////////////////////////////////////////////////
            // WARNING: This test will fail under a debugger because Task.s_currentActiveTasks    //
            //          roots HttpConnection.                                                     //
            ////////////////////////////////////////////////////////////////////////////////////////

            var logWh        = new SemaphoreSlim(0);
            var appStartedWh = new SemaphoreSlim(0);

            var mockTrace = new Mock <KestrelTrace>(Logger)
            {
                CallBase = true
            };

            mockTrace
            .Setup(trace => trace.ApplicationNeverCompleted(It.IsAny <string>()))
            .Callback(() =>
            {
                logWh.Release();
            });

            var testContext = new TestServiceContext(new LoggerFactory(), mockTrace.Object);

            testContext.InitializeHeartbeat();

            using (var server = new TestServer(context =>
            {
                appStartedWh.Release();
                var tcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);
                return(tcs.Task);
            },
                                               testContext))
            {
                using (var connection = server.CreateConnection())
                {
                    await connection.SendEmptyGet();

                    Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));

                    // Close connection without waiting for a response
                }

                var logWaitAttempts = 0;

                for (; !await logWh.WaitAsync(TimeSpan.FromSeconds(1)) && logWaitAttempts < 30; logWaitAttempts++)
                {
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                }

                Assert.True(logWaitAttempts < 10);

                await server.StopAsync();
            }
        }
    public async Task CriticalErrorLoggedIfApplicationDoesntComplete()
    {
        ////////////////////////////////////////////////////////////////////////////////////////
        // WARNING: This test will fail under a debugger because Task.s_currentActiveTasks    //
        //          roots HttpConnection.                                                     //
        ////////////////////////////////////////////////////////////////////////////////////////

        var logWh        = new SemaphoreSlim(0);
        var appStartedWh = new SemaphoreSlim(0);

        var factory = new LoggerFactory();

        // Use a custom logger for callback instead of TestSink because TestSink keeps references
        // to types when logging, prevents garbage collection, and makes the test fail.
        factory.AddProvider(new CallbackLoggerProvider(eventId =>
        {
            if (eventId.Name == "ApplicationNeverCompleted")
            {
                Logger.LogInformation("Releasing ApplicationNeverCompleted log wait handle.");
                logWh.Release();
            }
        }));

        var testContext = new TestServiceContext(factory);

        testContext.InitializeHeartbeat();

        await using (var server = new TestServer(context =>
        {
            appStartedWh.Release();
            var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
            return(tcs.Task);
        },
                                                 testContext))
        {
            using (var connection = server.CreateConnection())
            {
                await connection.SendEmptyGet();

                Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));

                // Close connection without waiting for a response
            }

            var logWaitAttempts = 0;

            for (; !await logWh.WaitAsync(TimeSpan.FromSeconds(1)) && logWaitAttempts < 30; logWaitAttempts++)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }

            Assert.True(logWaitAttempts < 10);
        }
    }
예제 #4
0
        public async Task GracefulShutdownWaitsForRequestsToFinish()
        {
            var requestStarted   = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
            var requestUnblocked = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
            var requestStopping  = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

            TestSink.MessageLogged += context =>
            {
                if (context.EventId.Name == "Http2ConnectionClosing")
                {
                    requestStopping.SetResult();
                }
            };

            var testContext = new TestServiceContext(LoggerFactory);

            testContext.InitializeHeartbeat();

            await using (var server = new TestServer(async context =>
            {
                requestStarted.SetResult();
                await requestUnblocked.Task.DefaultTimeout();
                await context.Response.WriteAsync("hello world " + context.Request.Protocol);
            },
                                                     testContext,
                                                     kestrelOptions =>
            {
                kestrelOptions.Listen(IPAddress.Loopback, 0, listenOptions =>
                {
                    listenOptions.Protocols = HttpProtocols.Http2;
                    listenOptions.UseHttps(_x509Certificate2);
                });
            }))
            {
                var requestTask = Client.GetStringAsync($"https://localhost:{server.Port}/");
                Assert.False(requestTask.IsCompleted);

                await requestStarted.Task.DefaultTimeout();

                var stopTask = server.StopAsync();

                await requestStopping.Task.DefaultTimeout();

                // Unblock the request
                requestUnblocked.SetResult();

                Assert.Equal("hello world HTTP/2", await requestTask);
                await stopTask.DefaultTimeout();
            }

            Assert.Contains(LogMessages, m => m.Message.Contains("Request finished "));
            Assert.Contains(LogMessages, m => m.Message.Contains("is closing."));
            Assert.Contains(LogMessages, m => m.Message.Contains("is closed. The last processed stream ID was 1."));
        }
        public async Task RequestTimesOutWhenNotDrainedWithinDrainTimeoutPeriod()
        {
            // This test requires a real clock since we can't control when the drain timeout is set
            var serviceContext = new TestServiceContext(LoggerFactory);

            serviceContext.InitializeHeartbeat();

            // Ensure there's still a constant date header value.
            var clock = new MockSystemClock();
            var date  = new DateHeaderValueManager();

            date.OnHeartbeat(clock.UtcNow);
            serviceContext.DateHeaderValueManager = date;

            var appRunningEvent = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);

            using (var server = new TestServer(context =>
            {
                context.Features.Get <IHttpMinRequestBodyDataRateFeature>().MinDataRate = null;

                appRunningEvent.SetResult(null);
                return(Task.CompletedTask);
            }, serviceContext))
            {
                using (var connection = server.CreateConnection())
                {
                    await connection.Send(
                        "POST / HTTP/1.1",
                        "Host:",
                        "Content-Length: 1",
                        "",
                        "");

                    await appRunningEvent.Task.DefaultTimeout();

                    // Disconnects after response completes due to the timeout
                    await connection.ReceiveEnd(
                        "HTTP/1.1 200 OK",
                        $"Date: {serviceContext.DateHeaderValue}",
                        "Content-Length: 0",
                        "",
                        "");
                }
                await server.StopAsync();
            }

            Assert.Contains(TestSink.Writes, w => w.EventId.Id == 32 && w.LogLevel == LogLevel.Information);
            Assert.Contains(TestSink.Writes, w => w.EventId.Id == 33 && w.LogLevel == LogLevel.Information);
        }
        public async Task RequestTimesOutWhenNotDrainedWithinDrainTimeoutPeriod()
        {
            // This test requires a real clock since we can't control when the drain timeout is set
            var serviceContext = new TestServiceContext(LoggerFactory);

            serviceContext.InitializeHeartbeat();

            var appRunningEvent = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);

            using (var server = new TestServer(context =>
            {
                context.Features.Get <IHttpMinRequestBodyDataRateFeature>().MinDataRate = null;

                appRunningEvent.SetResult(null);
                return(Task.CompletedTask);
            }, serviceContext))
            {
                using (var connection = server.CreateConnection())
                {
                    await connection.Send(
                        "POST / HTTP/1.1",
                        "Host:",
                        "Content-Length: 1",
                        "",
                        "");

                    await appRunningEvent.Task.DefaultTimeout();

                    await connection.Receive(
                        "HTTP/1.1 200 OK",
                        "");

                    await connection.ReceiveStartsWith(
                        "Date: ");

                    // Disconnected due to the timeout
                    await connection.ReceiveForcedEnd(
                        "Content-Length: 0",
                        "",
                        "");
                }
            }

            Assert.Contains(TestSink.Writes, w => w.EventId.Id == 32 && w.LogLevel == LogLevel.Information);
            Assert.Contains(TestSink.Writes, w => w.EventId.Id == 33 && w.LogLevel == LogLevel.Information);
        }
예제 #7
0
        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);
        }
예제 #8
0
        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);
        }
예제 #9
0
        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();
            }
        }
예제 #10
0
        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();

            void ConfigureListenOptions(ListenOptions listenOptions)
            {
                listenOptions.UseHttps(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.BodyWriter.WriteAsync(new Memory <byte>(chunkData, 0, chunkData.Length), context.RequestAborted);
                    }
                }
                catch (OperationCanceledException)
                {
                    appFuncCompleted.SetResult(null);
                    throw;
                }
                finally
                {
                    await aborted.Task.DefaultTimeout();
                }
            }, testContext, ConfigureListenOptions))
            {
                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();
            }
        }
예제 #11
0
        public async Task ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate()
        {
            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 appLogger = LoggerFactory.CreateLogger("App");

            async Task App(HttpContext context)
            {
                appLogger.LogInformation("Request received");
                context.RequestAborted.Register(() => requestAborted.SetResult(null));

                context.Response.ContentLength = responseSize;

                var i = 0;

                try
                {
                    for (; i < chunks; i++)
                    {
                        await context.Response.BodyWriter.WriteAsync(new Memory <byte>(chunkData, 0, chunkData.Length), context.RequestAborted);

                        await Task.Yield();
                    }

                    appFuncCompleted.SetException(new Exception("This shouldn't be reached."));
                }
                catch (OperationCanceledException)
                {
                    appFuncCompleted.SetResult(null);
                    throw;
                }
                catch (Exception ex)
                {
                    appFuncCompleted.SetException(ex);
                }
                finally
                {
                    appLogger.LogInformation("Wrote {total} bytes", chunkSize * i);
                    await requestAborted.Task.DefaultTimeout();
                }
            }

            using (var server = new TestServer(App, testContext))
            {
                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);
                }
                await server.StopAsync();
            }
        }
예제 #12
0
        public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseChunks()
        {
            var chunkSize  = 64 * 128 * 1024;
            var chunkCount = 4;
            var chunkData  = new byte[chunkSize];

            var requestAborted   = false;
            var appFuncCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

            var testContext = new TestServiceContext(LoggerFactory)
            {
                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();
            }

            await 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:",
                        "",
                        "");

                    await connection.Receive(
                        "HTTP/1.1 200 OK",
                        $"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 AssertBytesReceivedAtTargetRate(connection.Stream, expectedBytes : 33_553_537, targetBytesPerSecond);

                    await appFuncCompleted.Task.DefaultTimeout();

                    connection.ShutdownSend();
                    await connection.WaitForConnectionClose();
                }
            }

            Assert.Equal(0, TestSink.Writes.Count(w => w.EventId.Name == "ResponseMinimumDataRateNotSatisfied"));
            Assert.Equal(1, TestSink.Writes.Count(w => w.EventId.Name == "ConnectionStop"));
            Assert.False(requestAborted);
        }
예제 #13
0
        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(TaskCreationOptions.RunContinuationsAsynchronously);
            var connectionStopMessageLogged      = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
            var aborted          = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
            var appFuncCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

            TestSink.MessageLogged += context =>
            {
                if (context.EventId.Name == "ResponseMinimumDataRateNotSatisfied")
                {
                    responseRateTimeoutMessageLogged.SetResult();
                }
                if (context.EventId.Name == "ConnectionStop")
                {
                    connectionStopMessageLogged.SetResult();
                }
            };

            var testContext = new TestServiceContext(LoggerFactory)
            {
                ServerOptions =
                {
                    Limits                  =
                    {
                        MinResponseDataRate = new MinDataRate(bytesPerSecond: 1024 * 1024, gracePeriod: TimeSpan.FromSeconds(2))
                    }
                }
            };

            testContext.InitializeHeartbeat();

            void ConfigureListenOptions(ListenOptions listenOptions)
            {
                listenOptions.UseHttps(new HttpsConnectionAdapterOptions {
                    ServerCertificate = certificate
                });
            }

            await using (var server = new TestServer(async context =>
            {
                context.RequestAborted.Register(() =>
                {
                    aborted.SetResult();
                });

                context.Response.ContentLength = chunks * chunkSize;

                try
                {
                    for (var i = 0; i < chunks; i++)
                    {
                        await context.Response.BodyWriter.WriteAsync(new Memory <byte>(chunkData, 0, chunkData.Length), context.RequestAborted);
                    }
                }
                catch (OperationCanceledException)
                {
                    appFuncCompleted.SetResult();
                    throw;
                }
                finally
                {
                    await aborted.Task.DefaultTimeout();
                }
            }, testContext, ConfigureListenOptions))
            {
                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.None, false);

                        var request = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n");
                        await sslStream.WriteAsync(request, 0, request.Length);

                        // Don't use the 5 second timeout for debug builds. This can actually take a while.
                        await aborted.Task.DefaultTimeout(TimeSpan.FromSeconds(30));

                        await responseRateTimeoutMessageLogged.Task.DefaultTimeout();

                        await connectionStopMessageLogged.Task.DefaultTimeout();

                        await appFuncCompleted.Task.DefaultTimeout();

                        await AssertStreamAborted(connection.Stream, chunkSize *chunks);
                    }
                }
            }
        }
예제 #14
0
        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.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:",
                            "",
                            "");
                    }

                    // 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);
        }
예제 #15
0
        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.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",
                        "",
                        "");

                    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);
        }
예제 #16
0
        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 <KestrelTrace>(Logger)
                {
                    CallBase = true
                };
                mockKestrelTrace
                .Setup(trace => trace.ResponseMininumDataRateNotSatisfied(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 = loggerFactory,
                    Log           = 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.Reader.BaseStream, chunkSize *chunks);

                        sw.Stop();
                        logger.LogInformation("Connection was aborted after {totalMilliseconds}ms.", sw.ElapsedMilliseconds);
                    }
                }
            }
        }