Пример #1
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);
        }
Пример #2
0
        public async Task AppCanHandleClientAbortingConnectionMidResponse(ListenOptions listenOptions)
        {
            const int connectionResetEventId = 19;
            const int connectionFinEventId   = 6;
            const int connectionStopEventId  = 2;

            const int responseBodySegmentSize  = 65536;
            const int responseBodySegmentCount = 100;

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

            var scratchBuffer = new byte[responseBodySegmentSize];

            using (var server = new TestServer(async context =>
            {
                context.RequestAborted.Register(() => requestAborted.SetResult(null));

                for (var i = 0; i < responseBodySegmentCount; i++)
                {
                    await context.Response.Body.WriteAsync(scratchBuffer, 0, scratchBuffer.Length);
                    await Task.Delay(10);
                }

                await requestAborted.Task.DefaultTimeout();
                appCompletedTcs.SetResult(null);
            }, new TestServiceContext(LoggerFactory), listenOptions))
            {
                using (var connection = server.CreateConnection())
                {
                    await connection.Send(
                        "GET / HTTP/1.1",
                        "Host:",
                        "",
                        "");

                    // Read just part of the response and close the connection.
                    // https://github.com/aspnet/KestrelHttpServer/issues/2554
                    await connection.Stream.ReadAsync(scratchBuffer, 0, scratchBuffer.Length);

                    connection.Reset();
                }

                await requestAborted.Task.DefaultTimeout();

                // After the RequestAborted token is tripped, the connection reset should be logged.
                // On Linux and macOS, the connection close is still sometimes observed as a FIN despite the LingerState.
                var presShutdownTransportLogs = TestSink.Writes.Where(
                    w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" ||
                    w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets");
                var connectionResetLogs = presShutdownTransportLogs.Where(
                    w => w.EventId == connectionResetEventId ||
                    (!TestPlatformHelper.IsWindows && w.EventId == connectionFinEventId));

                Assert.NotEmpty(connectionResetLogs);

                // On macOS, the default 5 shutdown timeout is insufficient for the write loop to complete, so give it extra time.
                await appCompletedTcs.Task.DefaultTimeout();

                await server.StopAsync();
            }

            var coreLogs = TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel");

            Assert.Single(coreLogs.Where(w => w.EventId == connectionStopEventId));

            var transportLogs = TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel" ||
                                                      w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" ||
                                                      w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets");

            Assert.Empty(transportLogs.Where(w => w.LogLevel > LogLevel.Debug));
        }
Пример #3
0
        public async Task ThrowsOnWriteWithRequestAbortedTokenAfterRequestIsAborted(ListenOptions listenOptions)
        {
            // This should match _maxBytesPreCompleted in SocketOutput
            var maxBytesPreCompleted = 65536;

            // Ensure string is long enough to disable write-behind buffering
            var largeString = new string('a', maxBytesPreCompleted + 1);

            var writeTcs         = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);
            var requestAbortedWh = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);
            var requestStartWh   = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);

            using (var server = new TestServer(async httpContext =>
            {
                requestStartWh.SetResult(null);

                var response = httpContext.Response;
                var request = httpContext.Request;
                var lifetime = httpContext.Features.Get <IHttpRequestLifetimeFeature>();

                lifetime.RequestAborted.Register(() => requestAbortedWh.SetResult(null));
                await requestAbortedWh.Task.DefaultTimeout();

                try
                {
                    await response.WriteAsync(largeString, cancellationToken: lifetime.RequestAborted);
                }
                catch (Exception ex)
                {
                    writeTcs.SetException(ex);
                    throw;
                }
                finally
                {
                    await requestAbortedWh.Task.DefaultTimeout();
                }

                writeTcs.SetException(new Exception("This shouldn't be reached."));
            }, new TestServiceContext(LoggerFactory), listenOptions))
            {
                using (var connection = server.CreateConnection())
                {
                    await connection.Send(
                        "POST / HTTP/1.1",
                        "Host:",
                        "Content-Length: 0",
                        "",
                        "");

                    await requestStartWh.Task.DefaultTimeout();
                }

                // Write failed - can throw TaskCanceledException or OperationCanceledException,
                // depending on how far the canceled write goes.
                await Assert.ThrowsAnyAsync <OperationCanceledException>(async() => await writeTcs.Task).DefaultTimeout();

                // RequestAborted tripped
                await requestAbortedWh.Task.DefaultTimeout();

                await server.StopAsync();
            }
        }