File: ResponseDrainingTests.cs
Web Access
Project: src\src\Servers\Kestrel\test\InMemory.FunctionalTests\InMemory.FunctionalTests.csproj (InMemory.FunctionalTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Net;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests;
 
public class ResponseDrainingTests : TestApplicationErrorLoggerLoggedTest
{
    public static TheoryData<ListenOptions> ConnectionMiddlewareData => new TheoryData<ListenOptions>
        {
            new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)),
            new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)).UsePassThrough()
        };
 
    [Theory]
    [MemberData(nameof(ConnectionMiddlewareData))]
    public async Task ConnectionClosedWhenResponseNotDrainedAtMinimumDataRate(ListenOptions listenOptions)
    {
        var testContext = new TestServiceContext(LoggerFactory);
        var minRate = new MinDataRate(16384, TimeSpan.FromSeconds(2));
 
        await using (var server = new TestServer(context =>
        {
            context.Features.Get<IHttpMinResponseDataRateFeature>().MinDataRate = minRate;
            return Task.CompletedTask;
        }, testContext, listenOptions))
        {
            using (var connection = server.CreateConnection())
            {
                var transportConnection = connection.TransportConnection;
 
                var outputBufferedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
#pragma warning disable 0618 // TODO: Repalce OnWriterCompleted
                transportConnection.Output.OnWriterCompleted((ex, state) =>
                {
                    ((TaskCompletionSource)state).SetResult();
                },
                outputBufferedTcs);
#pragma warning restore
 
                await connection.Send(
                    "GET / HTTP/1.1",
                    "Host:",
                    "Connection: close",
                    "",
                    "");
 
                // Wait for the drain timeout to be set.
                await outputBufferedTcs.Task.DefaultTimeout();
 
                // Advance the clock to the grace period
                for (var i = 0; i < 2; i++)
                {
                    testContext.FakeTimeProvider.Advance(TimeSpan.FromSeconds(1));
                    testContext.ConnectionManager.OnHeartbeat();
                }
 
                testContext.FakeTimeProvider.Advance(Heartbeat.Interval - TimeSpan.FromSeconds(.5));
                testContext.ConnectionManager.OnHeartbeat();
 
                Assert.Null(transportConnection.AbortReason);
 
                testContext.FakeTimeProvider.Advance(TimeSpan.FromSeconds(1));
                testContext.ConnectionManager.OnHeartbeat();
 
                Assert.NotNull(transportConnection.AbortReason);
                Assert.Equal(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied, transportConnection.AbortReason.Message);
 
                Assert.Single(LogMessages, w => w.EventId.Id == 28 && w.LogLevel <= LogLevel.Debug);
            }
        }
    }
}