示例#1
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();
            }
        }
示例#2
0
        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 <KestrelTrace>(Logger)
            {
                CallBase = true
            };
            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)
            {
                Log           = 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);
        }
示例#3
0
        public async Task NonListenerPipeConnectionsAreLoggedAndIgnored()
        {
            var libuv         = new LibuvFunctions();
            var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
            var logger        = new TestApplicationErrorLogger();

            var serviceContextPrimary = new TestServiceContext();
            var builderPrimary        = new ConnectionBuilder();

            builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1);
            var transportContextPrimary = new TestLibuvTransportContext()
            {
                Log = new LibuvTrace(logger)
            };

            transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build());

            var serviceContextSecondary = new TestServiceContext
            {
                DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager,
                ServerOptions          = serviceContextPrimary.ServerOptions,
                Scheduler  = serviceContextPrimary.Scheduler,
                HttpParser = serviceContextPrimary.HttpParser,
            };
            var builderSecondary = new ConnectionBuilder();

            builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1);
            var transportContextSecondary = new TestLibuvTransportContext();

            transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build());

            var libuvTransport = new LibuvTransport(libuv, transportContextPrimary, listenOptions);

            var pipeName    = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
            var pipeMessage = Guid.NewGuid().ToByteArray();

            // Start primary listener
            var libuvThreadPrimary = new LibuvThread(libuvTransport);
            await libuvThreadPrimary.StartAsync();

            var listenerPrimary = new ListenerPrimary(transportContextPrimary);
            await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary);

            var address = GetUri(listenOptions);

            // Add secondary listener
            var libuvThreadSecondary = new LibuvThread(libuvTransport);
            await libuvThreadSecondary.StartAsync();

            var listenerSecondary = new ListenerSecondary(transportContextSecondary);
            await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary);

            // TCP Connections get round-robined
            await AssertResponseEventually(address, "Secondary", allowed : new[] { "Primary" });

            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));

            // Create a pipe connection and keep it open without sending any data
            var connectTcs      = new TaskCompletionSource <object>();
            var connectionTrace = new LibuvTrace(new TestApplicationErrorLogger());
            var pipe            = new UvPipeHandle(connectionTrace);

            libuvThreadPrimary.Post(_ =>
            {
                var connectReq = new UvConnectRequest(connectionTrace);

                pipe.Init(libuvThreadPrimary.Loop, libuvThreadPrimary.QueueCloseHandle);
                connectReq.Init(libuvThreadPrimary);

                connectReq.Connect(
                    pipe,
                    pipeName,
                    (req, status, ex, __) =>
                {
                    req.Dispose();

                    if (ex != null)
                    {
                        connectTcs.SetException(ex);
                    }
                    else
                    {
                        connectTcs.SetResult(null);
                    }
                },
                    null);
            }, (object)null);

            await connectTcs.Task;

            // TCP connections will still get round-robined between only the two listeners
            Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));

            await libuvThreadPrimary.PostAsync(_ => pipe.Dispose(), (object)null);

            // Wait up to 10 seconds for error to be logged
            for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++)
            {
                await Task.Delay(100);
            }

            // Same for after the non-listener pipe connection is closed
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));

            await listenerSecondary.DisposeAsync();

            await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5));

            await listenerPrimary.DisposeAsync();

            await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5));

            Assert.Equal(1, logger.TotalErrorsLogged);
            var errorMessage = logger.Messages.First(m => m.LogLevel == LogLevel.Error);

            Assert.Equal(TestConstants.EOF, Assert.IsType <UvException>(errorMessage.Exception).StatusCode);
        }
示例#4
0
        public async Task ConnectionsGetRoundRobinedToSecondaryListeners()
        {
            var libuv = new LibuvFunctions();

            var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));

            var serviceContextPrimary   = new TestServiceContext();
            var transportContextPrimary = new TestLibuvTransportContext();
            var builderPrimary          = new ConnectionBuilder();

            builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1);
            transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build());

            var serviceContextSecondary = new TestServiceContext();
            var builderSecondary        = new ConnectionBuilder();

            builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1);
            var transportContextSecondary = new TestLibuvTransportContext();

            transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build());

            var libuvTransport = new LibuvTransport(libuv, transportContextPrimary, listenOptions);

            var pipeName    = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
            var pipeMessage = Guid.NewGuid().ToByteArray();

            // Start primary listener
            var libuvThreadPrimary = new LibuvThread(libuvTransport);
            await libuvThreadPrimary.StartAsync();

            var listenerPrimary = new ListenerPrimary(transportContextPrimary);
            await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary);

            var address = GetUri(listenOptions);

            // Until a secondary listener is added, TCP connections get dispatched directly
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));

            var listenerCount = listenerPrimary.UvPipeCount;
            // Add secondary listener
            var libuvThreadSecondary = new LibuvThread(libuvTransport);
            await libuvThreadSecondary.StartAsync();

            var listenerSecondary = new ListenerSecondary(transportContextSecondary);
            await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadSecondary);

            var maxWait = Task.Delay(TestConstants.DefaultTimeout);

            // wait for ListenerPrimary.ReadCallback to add the secondary pipe
            while (listenerPrimary.UvPipeCount == listenerCount)
            {
                var completed = await Task.WhenAny(maxWait, Task.Delay(100));

                if (ReferenceEquals(completed, maxWait))
                {
                    throw new TimeoutException("Timed out waiting for secondary listener to become available");
                }
            }

            // Once a secondary listener is added, TCP connections start getting dispatched to it
            await AssertResponseEventually(address, "Secondary", allowed : new[] { "Primary" });

            // TCP connections will still get round-robined to the primary listener
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));

            await listenerSecondary.DisposeAsync();

            await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5));

            await listenerPrimary.DisposeAsync();

            await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5));
        }
示例#5
0
 public TestServer(RequestDelegate app, TestServiceContext context)
     : this(app, context, new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)))
 {
     // The endpoint is ignored, but this ensures no cert loading happens for HTTPS endpoints.
 }
示例#6
0
        public async Task RequestsCanBeAbortedMidRead(ListenOptions listenOptions)
        {
            // This needs a timeout.
            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;
                    }
                    finally
                    {
                        await registrationTcs.Task.DefaultTimeout();
                    }

                    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 server.StopAsync();
            }

            await Assert.ThrowsAsync <TaskCanceledException>(async() => await readTcs.Task);

            // The cancellation token for only the last request should be triggered.
            var abortedRequestId = await registrationTcs.Task.DefaultTimeout();

            Assert.Equal(2, abortedRequestId);

            Assert.Single(TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel" &&
                                                w.EventId == applicationAbortedConnectionId));
        }
        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);
                }
            }
        }
示例#8
0
        public async Task ConnectionClosedEvenIfAppSwallowsException()
        {
            var gracePeriod      = TimeSpan.FromSeconds(5);
            var serviceContext   = new TestServiceContext(LoggerFactory);
            var heartbeatManager = new HttpHeartbeatManager(serviceContext.ConnectionManager);

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

            using (var server = new TestServer(async context =>
            {
                context.Features.Get <IHttpMinRequestBodyDataRateFeature>().MinDataRate =
                    new MinDataRate(bytesPerSecond: 1, gracePeriod: gracePeriod);

                // See comment in RequestTimesOutWhenRequestBodyNotReceivedAtSpecifiedMinimumRate for
                // why we call ReadAsync before setting the appRunningEvent.
                var readTask = context.Request.Body.ReadAsync(new byte[1], 0, 1);
                appRunningTcs.SetResult(null);

                try
                {
                    await readTask;
                }
                catch (BadHttpRequestException ex) when(ex.StatusCode == 408)
                {
                    exceptionSwallowedTcs.SetResult(null);
                }
                catch (Exception ex)
                {
                    exceptionSwallowedTcs.SetException(ex);
                }

                var response = "hello, world";
                context.Response.ContentLength = response.Length;
                await context.Response.WriteAsync("hello, world");
            }, serviceContext))
            {
                using (var connection = server.CreateConnection())
                {
                    await connection.Send(
                        "POST / HTTP/1.1",
                        "Host:",
                        "Content-Length: 1",
                        "",
                        "");

                    await appRunningTcs.Task.DefaultTimeout();

                    serviceContext.MockSystemClock.UtcNow += gracePeriod + TimeSpan.FromSeconds(1);
                    heartbeatManager.OnHeartbeat(serviceContext.SystemClock.UtcNow);

                    await exceptionSwallowedTcs.Task.DefaultTimeout();

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

                    await connection.ReceiveForcedEnd(
                        $"Date: {serviceContext.DateHeaderValue}",
                        "Content-Length: 12",
                        "",
                        "hello, world");
                }
            }
        }
        public async Task HeadersAndStreamsAreReusedAcrossRequests()
        {
            var               testContext          = new TestServiceContext(LoggerFactory);
            var               streamCount          = 0;
            var               requestHeadersCount  = 0;
            var               responseHeadersCount = 0;
            var               loopCount            = 20;
            Stream            lastStream           = null;
            IHeaderDictionary lastRequestHeaders   = null;
            IHeaderDictionary lastResponseHeaders  = null;

            using (var server = new TestServer(async context =>
            {
                if (context.Request.Body != lastStream)
                {
                    lastStream = context.Request.Body;
                    streamCount++;
                }
                if (context.Request.Headers != lastRequestHeaders)
                {
                    lastRequestHeaders = context.Request.Headers;
                    requestHeadersCount++;
                }
                if (context.Response.Headers != lastResponseHeaders)
                {
                    lastResponseHeaders = context.Response.Headers;
                    responseHeadersCount++;
                }

                var ms = new MemoryStream();
                await context.Request.Body.CopyToAsync(ms);
                var request = ms.ToArray();

                context.Response.ContentLength = request.Length;

                await context.Response.Body.WriteAsync(request, 0, request.Length);
            }, testContext))
            {
                using (var connection = server.CreateConnection())
                {
                    var requestData =
                        Enumerable.Repeat("GET / HTTP/1.1\r\nHost:\r\n", loopCount)
                        .Concat(new[] { "GET / HTTP/1.1\r\nHost:\r\nContent-Length: 7\r\nConnection: close\r\n\r\nGoodbye" });

                    var response = string.Join("\r\n", new string[] {
                        "HTTP/1.1 200 OK",
                        $"Date: {testContext.DateHeaderValue}",
                        "Content-Length: 0",
                        ""
                    });

                    var lastResponse = string.Join("\r\n", new string[]
                    {
                        "HTTP/1.1 200 OK",
                        "Connection: close",
                        $"Date: {testContext.DateHeaderValue}",
                        "Content-Length: 7",
                        "",
                        "Goodbye"
                    });

                    var responseData =
                        Enumerable.Repeat(response, loopCount)
                        .Concat(new[] { lastResponse });

                    await connection.Send(requestData.ToArray());

                    await connection.ReceiveEnd(responseData.ToArray());
                }

                Assert.Equal(1, streamCount);
                Assert.Equal(1, requestHeadersCount);
                Assert.Equal(1, responseHeadersCount);
            }
        }
示例#10
0
        public async Task DoesNotEnforceRequestBodyMinimumDataRateOnUpgradedRequest()
        {
            var appEvent         = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);
            var delayEvent       = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);
            var serviceContext   = new TestServiceContext(LoggerFactory);
            var heartbeatManager = new HeartbeatManager(serviceContext.ConnectionManager);

            using (var server = new TestServer(async context =>
            {
                context.Features.Get <IHttpMinRequestBodyDataRateFeature>().MinDataRate =
                    new MinDataRate(bytesPerSecond: double.MaxValue, gracePeriod: Heartbeat.Interval + TimeSpan.FromTicks(1));

                using (var stream = await context.Features.Get <IHttpUpgradeFeature>().UpgradeAsync())
                {
                    appEvent.SetResult(null);

                    // Read once to go through one set of TryPauseTimingReads()/TryResumeTimingReads() calls
                    await stream.ReadAsync(new byte[1], 0, 1);

                    await delayEvent.Task.DefaultTimeout();

                    // Read again to check that the connection is still alive
                    await stream.ReadAsync(new byte[1], 0, 1);

                    // Send a response to distinguish from the timeout case where the 101 is still received, but without any content
                    var response = Encoding.ASCII.GetBytes("hello");
                    await stream.WriteAsync(response, 0, response.Length);
                }
            }, serviceContext))
            {
                using (var connection = server.CreateConnection())
                {
                    await connection.Send(
                        "GET / HTTP/1.1",
                        "Host:",
                        "Connection: upgrade",
                        "",
                        "a");

                    await appEvent.Task.DefaultTimeout();

                    serviceContext.MockSystemClock.UtcNow += TimeSpan.FromSeconds(5);
                    heartbeatManager.OnHeartbeat(serviceContext.SystemClock.UtcNow);

                    delayEvent.SetResult(null);

                    await connection.Send("b");

                    await connection.Receive(
                        "HTTP/1.1 101 Switching Protocols",
                        "Connection: Upgrade",
                        "");

                    await connection.ReceiveStartsWith(
                        $"Date: ");

                    await connection.ReceiveForcedEnd(
                        "",
                        "hello");
                }
            }
        }
示例#11
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 <KestrelTrace>(Logger)
            {
                CallBase = true
            };

            var testContext = new TestServiceContext
            {
                Log           = 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.Body.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.Reader.BaseStream, minTotalOutputSize, targetBytesPerSecond);

                    mockKestrelTrace.Verify(t => t.ResponseMininumDataRateNotSatisfied(It.IsAny <string>(), It.IsAny <string>()), Times.Never());
                    mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny <string>()), Times.Once());
                    Assert.False(requestAborted);
                }
            }
        }
示例#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 <object>(TaskCreationOptions.RunContinuationsAsynchronously);
            var mockKestrelTrace = new Mock <KestrelTrace>(Logger)
            {
                CallBase = true
            };

            var testContext = new TestServiceContext
            {
                Log           = 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.Body.WriteAsync(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.Reader.BaseStream, minTotalOutputSize, targetBytesPerSecond);

                    await appFuncCompleted.Task.DefaultTimeout();

                    mockKestrelTrace.Verify(t => t.ResponseMininumDataRateNotSatisfied(It.IsAny <string>(), It.IsAny <string>()), Times.Never());
                    mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny <string>()), Times.Once());
                    Assert.False(requestAborted);
                }
            }
        }
示例#13
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);
                    }
                }
            }
        }
示例#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 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();
            }

            await 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",
                        "Content-Length: 0",
                        $"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();
                }
            }

            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 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",
                        "",
                        "");
                }
            }
        }
示例#16
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(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();
            }

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

            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);
        }
示例#17
0
        public async Task ConnectionLimitExceeded_EmitsStartAndStopEventsWithActivityIds()
        {
            int    port;
            string connectionId = null;

            var serviceContext = new TestServiceContext(LoggerFactory);

            await using (var server = new TestServer(context => Task.CompletedTask, serviceContext,
                                                     listenOptions =>
            {
                listenOptions.Use(next =>
                {
                    return(connectionContext =>
                    {
                        connectionId = connectionContext.ConnectionId;
                        return next(connectionContext);
                    });
                });

                listenOptions.Use(next =>
                {
                    return(new ConnectionLimitMiddleware <ConnectionContext>(c => next(c), connectionLimit: 0, serviceContext.Log).OnConnectionAsync);
                });
            }))
            {
                port = server.Port;

                using var connection = server.CreateConnection();
                await connection.ReceiveEnd();
            }

            Assert.NotNull(connectionId);

            // Other tests executing in parallel may log events.
            var events     = _listener.EventData.Where(e => e != null && GetProperty(e, "connectionId") == connectionId).ToList();
            var eventIndex = 0;

            var connectionStart = events[eventIndex++];

            Assert.Equal("ConnectionStart", connectionStart.EventName);
            Assert.Equal(1, connectionStart.EventId);
            Assert.All(new[] { "connectionId", "remoteEndPoint", "localEndPoint" }, p => Assert.Contains(p, connectionStart.PayloadNames));
            Assert.Equal($"127.0.0.1:{port}", GetProperty(connectionStart, "localEndPoint"));
            Assert.Same(KestrelEventSource.Log, connectionStart.EventSource);
            Assert.NotEqual(Guid.Empty, connectionStart.ActivityId);

            var connectionRejected = events[eventIndex++];

            Assert.Equal("ConnectionRejected", connectionRejected.EventName);
            Assert.Equal(5, connectionRejected.EventId);
            Assert.All(new[] { "connectionId" }, p => Assert.Contains(p, connectionRejected.PayloadNames));
            Assert.Same(KestrelEventSource.Log, connectionRejected.EventSource);
            Assert.Equal(connectionStart.ActivityId, connectionRejected.ActivityId);
            Assert.Equal(Guid.Empty, connectionRejected.RelatedActivityId);

            var connectionStop = events[eventIndex++];

            Assert.Equal("ConnectionStop", connectionStop.EventName);
            Assert.Equal(2, connectionStop.EventId);
            Assert.All(new[] { "connectionId" }, p => Assert.Contains(p, connectionStop.PayloadNames));
            Assert.Same(KestrelEventSource.Log, connectionStop.EventSource);
            Assert.Equal(connectionStart.ActivityId, connectionStop.ActivityId);
            Assert.Equal(Guid.Empty, connectionStop.RelatedActivityId);

            Assert.Equal(eventIndex, events.Count);
        }
示例#18
0
        public async Task RequestTimesOutWhenRequestBodyNotReceivedAtSpecifiedMinimumRate()
        {
            var gracePeriod      = TimeSpan.FromSeconds(5);
            var serviceContext   = new TestServiceContext(LoggerFactory);
            var heartbeatManager = new HttpHeartbeatManager(serviceContext.ConnectionManager);

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

            using (var server = new TestServer(context =>
            {
                context.Features.Get <IHttpMinRequestBodyDataRateFeature>().MinDataRate =
                    new MinDataRate(bytesPerSecond: 1, gracePeriod: gracePeriod);

                // The server must call Request.Body.ReadAsync() *before* the test sets systemClock.UtcNow (which is triggered by the
                // server calling appRunningEvent.SetResult(null)).  If systemClock.UtcNow is set first, it's possible for the test to fail
                // due to the following race condition:
                //
                // 1. [test]    systemClock.UtcNow += gracePeriod + TimeSpan.FromSeconds(1);
                // 2. [server]  Heartbeat._timer is triggered, which calls HttpConnection.Tick()
                // 3. [server]  HttpConnection.Tick() calls HttpConnection.CheckForReadDataRateTimeout()
                // 4. [server]  HttpConnection.CheckForReadDataRateTimeout() is a no-op, since _readTimingEnabled is false,
                //              since Request.Body.ReadAsync() has not been called yet
                // 5. [server]  HttpConnection.Tick() sets _lastTimestamp = timestamp
                // 6. [server]  Request.Body.ReadAsync() is called
                // 6. [test]    systemClock.UtcNow is never updated again, so server timestamp is never updated,
                //              so HttpConnection.CheckForReadDataRateTimeout() is always a no-op until test fails
                //
                // This is a pretty tight race, since the once-per-second Heartbeat._timer needs to fire between the test updating
                // systemClock.UtcNow and the server calling Request.Body.ReadAsync().  But it happened often enough to cause
                // test flakiness in our CI (https://github.com/aspnet/KestrelHttpServer/issues/2539).
                //
                // For verification, I was able to induce the race by adding a sleep in the RequestDelegate:
                //     appRunningEvent.SetResult(null);
                //     Thread.Sleep(5000);
                //     return context.Request.Body.ReadAsync(new byte[1], 0, 1);

                var readTask = context.Request.Body.ReadAsync(new byte[1], 0, 1);
                appRunningEvent.SetResult(null);
                return(readTask);
            }, serviceContext))
            {
                using (var connection = server.CreateConnection())
                {
                    await connection.Send(
                        "POST / HTTP/1.1",
                        "Host:",
                        "Content-Length: 1",
                        "",
                        "");

                    await appRunningEvent.Task.DefaultTimeout();

                    serviceContext.MockSystemClock.UtcNow += gracePeriod + TimeSpan.FromSeconds(1);
                    heartbeatManager.OnHeartbeat(serviceContext.SystemClock.UtcNow);

                    await connection.Receive(
                        "HTTP/1.1 408 Request Timeout",
                        "");

                    await connection.ReceiveForcedEnd(
                        "Connection: close",
                        $"Date: {serviceContext.DateHeaderValue}",
                        "Content-Length: 0",
                        "",
                        "");
                }
            }
        }
示例#19
0
 public TestServer(RequestDelegate app, TestServiceContext context)
     : this(app, context, new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)))
 {
 }
示例#20
0
 public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions)
     : this(app, context, options => options.ListenOptions.Add(listenOptions), _ => { })
 {
 }
示例#21
0
 public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions)
     : this(app, context, listenOptions, _ => { })
 {
 }
示例#22
0
        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);

            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);
                }
                else if (context.EventId == connectionFinSentEventId)
                {
                    serverClosedConnection.SetResult(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 =>
            {
                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();

                await server.StopAsync();
            }

            mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny <string>()), Times.Once());
        }
示例#23
0
 public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions, Action <IServiceCollection> configureServices)
     : this(app, context, options => options.ListenOptions.Add(listenOptions), configureServices)
 {
 }
示例#24
0
        public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored()
        {
            var libuv         = new LibuvFunctions();
            var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));

            var logger = new TestApplicationErrorLogger();

            var serviceContextPrimary = new TestServiceContext();
            var builderPrimary        = new ConnectionBuilder();

            builderPrimary.UseHttpServer(serviceContextPrimary, new DummyApplication(c => c.Response.WriteAsync("Primary")), HttpProtocols.Http1);
            var transportContextPrimary = new TestLibuvTransportContext()
            {
                Log = new LibuvTrace(logger)
            };

            transportContextPrimary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextPrimary, builderPrimary.Build());

            var serviceContextSecondary = new TestServiceContext
            {
                DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager,
                ServerOptions          = serviceContextPrimary.ServerOptions,
                Scheduler  = serviceContextPrimary.Scheduler,
                HttpParser = serviceContextPrimary.HttpParser,
            };
            var builderSecondary = new ConnectionBuilder();

            builderSecondary.UseHttpServer(serviceContextSecondary, new DummyApplication(c => c.Response.WriteAsync("Secondary")), HttpProtocols.Http1);
            var transportContextSecondary = new TestLibuvTransportContext();

            transportContextSecondary.ConnectionDispatcher = new ConnectionDispatcher(serviceContextSecondary, builderSecondary.Build());

            var libuvTransport = new LibuvTransport(libuv, transportContextPrimary, listenOptions);

            var pipeName    = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
            var pipeMessage = Guid.NewGuid().ToByteArray();

            // Start primary listener
            var libuvThreadPrimary = new LibuvThread(libuvTransport);
            await libuvThreadPrimary.StartAsync();

            var listenerPrimary = new ListenerPrimary(transportContextPrimary);
            await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary);

            var address = GetUri(listenOptions);

            // Add secondary listener with wrong pipe message
            var libuvThreadSecondary = new LibuvThread(libuvTransport);
            await libuvThreadSecondary.StartAsync();

            var listenerSecondary = new ListenerSecondary(transportContextSecondary);
            await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), listenOptions, libuvThreadSecondary);

            // Wait up to 10 seconds for error to be logged
            for (var i = 0; i < 10 && logger.TotalErrorsLogged == 0; i++)
            {
                await Task.Delay(100);
            }

            // TCP Connections don't get round-robined
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
            Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));

            await listenerSecondary.DisposeAsync();

            await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5));

            await listenerPrimary.DisposeAsync();

            await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5));

            Assert.Equal(1, logger.TotalErrorsLogged);
            var errorMessage = logger.Messages.First(m => m.LogLevel == LogLevel.Error);

            Assert.IsType <IOException>(errorMessage.Exception);
            Assert.Contains("Bad data", errorMessage.Exception.ToString());
        }
示例#25
0
 public TestServer(RequestDelegate app, TestServiceContext context, Action <KestrelServerOptions> configureKestrel)
     : this(app, context, configureKestrel, _ => { })
 {
 }
示例#26
0
        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);
        }
示例#27
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(TaskCreationOptions.RunContinuationsAsynchronously);
            var connectionStopMessageLogged      = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
            var requestAborted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
            var copyToAsyncCts = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

            var mockKestrelTrace = new Mock <IKestrelTrace>();

            mockKestrelTrace
            .Setup(trace => trace.ResponseMinimumDataRateNotSatisfied(It.IsAny <string>(), It.IsAny <string>()))
            .Callback(() => responseRateTimeoutMessageLogged.SetResult());
            mockKestrelTrace
            .Setup(trace => trace.ConnectionStop(It.IsAny <string>()))
            .Callback(() => connectionStopMessageLogged.SetResult());

            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();
                });

                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."));
            }

            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(
                        "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);
                        }
                    });

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

                    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);
                }
            }
        }
示例#28
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();
            }
        }
示例#29
0
        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());
        }