aspnetcore/test/Microsoft.AspNetCore.Server.../KeepAliveTimeoutTests.cs

235 lines
8.4 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public class KeepAliveTimeoutTests
{
private static readonly TimeSpan KeepAliveTimeout = TimeSpan.FromSeconds(10);
private static readonly TimeSpan LongDelay = TimeSpan.FromSeconds(30);
private static readonly TimeSpan ShortDelay = TimeSpan.FromSeconds(LongDelay.TotalSeconds / 10);
[Fact]
public async Task TestKeepAliveTimeout()
{
// Delays in these tests cannot be much longer than expected.
// Call ConfigureAwait(false) to get rid of Xunit's synchronization context,
// otherwise it can cause unexpectedly longer delays when multiple tests
// are running in parallel. These tests becomes flaky on slower
// hardware because the continuations for the delay tasks might take too long to be
// scheduled if running on Xunit's synchronization context.
await Task.Delay(1).ConfigureAwait(false);
var longRunningCancellationTokenSource = new CancellationTokenSource();
var upgradeCancellationTokenSource = new CancellationTokenSource();
using (var server = CreateServer(longRunningCancellationTokenSource.Token, upgradeCancellationTokenSource.Token))
{
var tasks = new[]
{
ConnectionClosedWhenKeepAliveTimeoutExpires(server),
ConnectionKeptAliveBetweenRequests(server),
ConnectionNotTimedOutWhileRequestBeingSent(server),
ConnectionNotTimedOutWhileAppIsRunning(server, longRunningCancellationTokenSource),
ConnectionTimesOutWhenOpenedButNoRequestSent(server),
KeepAliveTimeoutDoesNotApplyToUpgradedConnections(server, upgradeCancellationTokenSource)
};
await Task.WhenAll(tasks);
}
}
private async Task ConnectionClosedWhenKeepAliveTimeoutExpires(TestServer server)
{
using (var connection = new TestConnection(server.Port))
{
await connection.Send(
"GET / HTTP/1.1",
"",
"");
await ReceiveResponse(connection, server.Context);
await connection.WaitForConnectionClose().TimeoutAfter(LongDelay);
}
}
private async Task ConnectionKeptAliveBetweenRequests(TestServer server)
{
using (var connection = new TestConnection(server.Port))
{
for (var i = 0; i < 10; i++)
{
await connection.Send(
"GET / HTTP/1.1",
"",
"");
await Task.Delay(ShortDelay);
}
for (var i = 0; i < 10; i++)
{
await ReceiveResponse(connection, server.Context);
}
}
}
private async Task ConnectionNotTimedOutWhileRequestBeingSent(TestServer server)
{
using (var connection = new TestConnection(server.Port))
{
var cts = new CancellationTokenSource();
cts.CancelAfter(LongDelay);
await connection.Send(
"POST / HTTP/1.1",
"Transfer-Encoding: chunked",
"",
"");
while (!cts.IsCancellationRequested)
{
await connection.Send(
"1",
"a",
"");
await Task.Delay(ShortDelay);
}
await connection.Send(
"0",
"",
"");
await ReceiveResponse(connection, server.Context);
}
}
private async Task ConnectionNotTimedOutWhileAppIsRunning(TestServer server, CancellationTokenSource cts)
{
using (var connection = new TestConnection(server.Port))
{
await connection.Send(
"GET /longrunning HTTP/1.1",
"",
"");
cts.CancelAfter(LongDelay);
while (!cts.IsCancellationRequested)
{
await Task.Delay(1000);
}
await ReceiveResponse(connection, server.Context);
await connection.Send(
"GET / HTTP/1.1",
"",
"");
await ReceiveResponse(connection, server.Context);
}
}
private async Task ConnectionTimesOutWhenOpenedButNoRequestSent(TestServer server)
{
using (var connection = new TestConnection(server.Port))
{
await Task.Delay(LongDelay);
await connection.WaitForConnectionClose().TimeoutAfter(LongDelay);
}
}
private async Task KeepAliveTimeoutDoesNotApplyToUpgradedConnections(TestServer server, CancellationTokenSource cts)
{
using (var connection = new TestConnection(server.Port))
{
await connection.Send(
"GET /upgrade HTTP/1.1",
"",
"");
await connection.Receive(
"HTTP/1.1 101 Switching Protocols",
"Connection: Upgrade",
$"Date: {server.Context.DateHeaderValue}",
"",
"");
cts.CancelAfter(LongDelay);
while (!cts.IsCancellationRequested)
{
await Task.Delay(1000);
}
await connection.Receive("hello, world");
}
}
private TestServer CreateServer(CancellationToken longRunningCt, CancellationToken upgradeCt)
{
return new TestServer(httpContext => App(httpContext, longRunningCt, upgradeCt), new TestServiceContext
{
ServerOptions = new KestrelServerOptions
{
AddServerHeader = false,
Limits =
{
KeepAliveTimeout = KeepAliveTimeout
}
}
});
}
private async Task App(HttpContext httpContext, CancellationToken longRunningCt, CancellationToken upgradeCt)
{
var ct = httpContext.RequestAborted;
if (httpContext.Request.Path == "/longrunning")
{
while (!longRunningCt.IsCancellationRequested)
{
await Task.Delay(1000);
}
await httpContext.Response.WriteAsync("hello, world");
}
else if (httpContext.Request.Path == "/upgrade")
{
using (var stream = await httpContext.Features.Get<IHttpUpgradeFeature>().UpgradeAsync())
{
while (!upgradeCt.IsCancellationRequested)
{
await Task.Delay(LongDelay);
}
var responseBytes = Encoding.ASCII.GetBytes("hello, world");
await stream.WriteAsync(responseBytes, 0, responseBytes.Length);
}
}
else
{
await httpContext.Response.WriteAsync("hello, world");
}
}
private async Task ReceiveResponse(TestConnection connection, TestServiceContext testServiceContext)
{
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {testServiceContext.DateHeaderValue}",
"Transfer-Encoding: chunked",
"",
"c",
"hello, world",
"0",
"",
"");
}
}
}