Add keep-alive timeout (#464).
This commit is contained in:
parent
19f8958fa8
commit
f2085b1968
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -156,6 +155,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
_socketClosedTcs.TrySetResult(null);
|
||||
}
|
||||
|
||||
// Called on Libuv thread
|
||||
public void Tick()
|
||||
{
|
||||
_frame.Tick();
|
||||
}
|
||||
|
||||
private void ApplyConnectionFilter()
|
||||
{
|
||||
if (_filterContext.Connection != _libuvStream)
|
||||
|
|
@ -277,6 +282,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
void IConnectionControl.Stop()
|
||||
{
|
||||
StopAsync();
|
||||
}
|
||||
|
||||
private static unsafe string GenerateConnectionId(long id)
|
||||
{
|
||||
// The following routine is ~310% faster than calling long.ToString() on x64
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
private CancellationTokenSource _abortedCts;
|
||||
private CancellationToken? _manuallySetRequestAbortToken;
|
||||
|
||||
protected RequestProcessingStatus _requestProcessingStatus;
|
||||
private RequestProcessingStatus _requestProcessingStatus;
|
||||
protected bool _keepAlive;
|
||||
private bool _autoChunk;
|
||||
protected Exception _applicationException;
|
||||
|
|
@ -68,6 +68,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
private int _remainingRequestHeadersBytesAllowed;
|
||||
private int _requestHeadersParsed;
|
||||
|
||||
private int _secondsSinceLastRequest;
|
||||
|
||||
public Frame(ConnectionContext context)
|
||||
: base(context)
|
||||
{
|
||||
|
|
@ -213,10 +215,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
public bool HasResponseStarted
|
||||
{
|
||||
get { return _requestProcessingStatus == RequestProcessingStatus.ResponseStarted; }
|
||||
}
|
||||
public bool HasResponseStarted => _requestProcessingStatus == RequestProcessingStatus.ResponseStarted;
|
||||
|
||||
protected FrameRequestHeaders FrameRequestHeaders { get; private set; }
|
||||
|
||||
|
|
@ -1267,6 +1266,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
Log.ApplicationError(ConnectionId, ex);
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
// we're in between requests and not about to start processing a new one
|
||||
if (_requestProcessingStatus == RequestProcessingStatus.RequestPending && !SocketInput.IsCompleted)
|
||||
{
|
||||
if (_secondsSinceLastRequest > ServerOptions.Limits.KeepAliveTimeout.TotalSeconds)
|
||||
{
|
||||
ConnectionControl.Stop();
|
||||
}
|
||||
|
||||
_secondsSinceLastRequest++;
|
||||
}
|
||||
}
|
||||
|
||||
public void RequestFinished()
|
||||
{
|
||||
_secondsSinceLastRequest = 0;
|
||||
}
|
||||
|
||||
protected enum RequestLineStatus
|
||||
{
|
||||
Empty,
|
||||
|
|
@ -1277,7 +1295,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
Done
|
||||
}
|
||||
|
||||
protected enum RequestProcessingStatus
|
||||
private enum RequestProcessingStatus
|
||||
{
|
||||
RequestPending,
|
||||
RequestStarted,
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
void Pause();
|
||||
void Resume();
|
||||
void End(ProduceEndType endType);
|
||||
void Stop();
|
||||
}
|
||||
}
|
||||
|
|
@ -172,6 +172,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
var limit = buffer.Array == null ? inputLengthLimit : Math.Min(buffer.Count, inputLengthLimit);
|
||||
if (limit == 0)
|
||||
{
|
||||
_context.RequestFinished();
|
||||
return new ValueTask<int>(0);
|
||||
}
|
||||
|
||||
|
|
@ -182,10 +183,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
// .GetAwaiter().GetResult() done by ValueTask if needed
|
||||
var actual = task.Result;
|
||||
_inputLength -= actual;
|
||||
|
||||
if (actual == 0)
|
||||
{
|
||||
_context.RejectRequest(RequestRejectionReason.UnexpectedEndOfRequestContent);
|
||||
}
|
||||
|
||||
if (_inputLength == 0)
|
||||
{
|
||||
_context.RequestFinished();
|
||||
}
|
||||
|
||||
return new ValueTask<int>(actual);
|
||||
}
|
||||
else
|
||||
|
|
@ -198,11 +206,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
var actual = await task;
|
||||
_inputLength -= actual;
|
||||
|
||||
if (actual == 0)
|
||||
{
|
||||
_context.RejectRequest(RequestRejectionReason.UnexpectedEndOfRequestContent);
|
||||
}
|
||||
|
||||
if (_inputLength == 0)
|
||||
{
|
||||
_context.RequestFinished();
|
||||
}
|
||||
|
||||
return actual;
|
||||
}
|
||||
}
|
||||
|
|
@ -354,6 +368,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
_mode = Mode.Complete;
|
||||
}
|
||||
|
||||
_context.RequestFinished();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,12 +27,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
// otherwise it needs to wait till the next pass of the libuv loop
|
||||
private readonly int _maxLoops = 8;
|
||||
|
||||
// how often the heartbeat timer will tick connections
|
||||
private const int _heartbeatMilliseconds = 1000;
|
||||
|
||||
private readonly KestrelEngine _engine;
|
||||
private readonly IApplicationLifetime _appLifetime;
|
||||
private readonly Thread _thread;
|
||||
private readonly TaskCompletionSource<object> _threadTcs = new TaskCompletionSource<object>();
|
||||
private readonly UvLoopHandle _loop;
|
||||
private readonly UvAsyncHandle _post;
|
||||
private readonly UvTimerHandle _heartbeatTimer;
|
||||
private Queue<Work> _workAdding = new Queue<Work>(1024);
|
||||
private Queue<Work> _workRunning = new Queue<Work>(1024);
|
||||
private Queue<CloseHandle> _closeHandleAdding = new Queue<CloseHandle>(256);
|
||||
|
|
@ -57,6 +61,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
_post = new UvAsyncHandle(_log);
|
||||
_thread = new Thread(ThreadStart);
|
||||
_thread.Name = "KestrelThread - libuv";
|
||||
_heartbeatTimer = new UvTimerHandle(_log);
|
||||
#if !DEBUG
|
||||
// Mark the thread as being as unimportant to keeping the process alive.
|
||||
// Don't do this for debug builds, so we know if the thread isn't terminating.
|
||||
|
|
@ -176,9 +181,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private void AllowStop()
|
||||
{
|
||||
_heartbeatTimer.Stop();
|
||||
_post.Unreference();
|
||||
}
|
||||
|
||||
|
|
@ -274,6 +279,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
{
|
||||
_loop.Init(_engine.Libuv);
|
||||
_post.Init(_loop, OnPost, EnqueueCloseHandle);
|
||||
_heartbeatTimer.Init(_loop, EnqueueCloseHandle);
|
||||
_heartbeatTimer.Start(OnHeartbeat, timeout: 1000, repeat: 1000);
|
||||
_initCompleted = true;
|
||||
tcs.SetResult(0);
|
||||
}
|
||||
|
|
@ -296,6 +303,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
// run the loop one more time to delete the open handles
|
||||
_post.Reference();
|
||||
_post.Dispose();
|
||||
_heartbeatTimer.Dispose();
|
||||
|
||||
// Ensure the Dispose operations complete in the event loop.
|
||||
_loop.Run();
|
||||
|
|
@ -327,6 +335,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
} while (wasWork && loopsRemaining > 0);
|
||||
}
|
||||
|
||||
private void OnHeartbeat(UvTimerHandle timer)
|
||||
{
|
||||
Walk(ptr =>
|
||||
{
|
||||
var handle = UvMemory.FromIntPtr<UvHandle>(ptr);
|
||||
(handle as UvStreamHandle)?.Connection?.Tick();
|
||||
});
|
||||
}
|
||||
|
||||
private bool DoPostWork()
|
||||
{
|
||||
Queue<Work> queue;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
|||
_uv_tcp_getpeername = NativeMethods.uv_tcp_getpeername;
|
||||
_uv_tcp_getsockname = NativeMethods.uv_tcp_getsockname;
|
||||
_uv_walk = NativeMethods.uv_walk;
|
||||
_uv_timer_init = NativeMethods.uv_timer_init;
|
||||
_uv_timer_start = NativeMethods.uv_timer_start;
|
||||
_uv_timer_stop = NativeMethods.uv_timer_stop;
|
||||
}
|
||||
|
||||
// Second ctor that doesn't set any fields only to be used by MockLibuv
|
||||
|
|
@ -407,6 +410,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
|||
_uv_walk(loop, walk_cb, arg);
|
||||
}
|
||||
|
||||
protected Func<UvLoopHandle, UvTimerHandle, int> _uv_timer_init;
|
||||
unsafe public void timer_init(UvLoopHandle loop, UvTimerHandle handle)
|
||||
{
|
||||
loop.Validate();
|
||||
handle.Validate();
|
||||
ThrowIfErrored(_uv_timer_init(loop, handle));
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void uv_timer_cb(IntPtr handle);
|
||||
protected Func<UvTimerHandle, uv_timer_cb, long, long, int> _uv_timer_start;
|
||||
unsafe public void timer_start(UvTimerHandle handle, uv_timer_cb cb, long timeout, long repeat)
|
||||
{
|
||||
handle.Validate();
|
||||
ThrowIfErrored(_uv_timer_start(handle, cb, timeout, repeat));
|
||||
}
|
||||
|
||||
protected Func<UvTimerHandle, int> _uv_timer_stop;
|
||||
unsafe public void timer_stop(UvTimerHandle handle)
|
||||
{
|
||||
handle.Validate();
|
||||
ThrowIfErrored(_uv_timer_stop(handle));
|
||||
}
|
||||
|
||||
public delegate int uv_tcp_getsockname_func(UvTcpHandle handle, out SockAddr addr, ref int namelen);
|
||||
protected uv_tcp_getsockname_func _uv_tcp_getsockname;
|
||||
public void tcp_getsockname(UvTcpHandle handle, out SockAddr addr, ref int namelen)
|
||||
|
|
@ -604,6 +631,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
|||
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int uv_walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg);
|
||||
|
||||
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
|
||||
unsafe public static extern int uv_timer_init(UvLoopHandle loop, UvTimerHandle handle);
|
||||
|
||||
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
|
||||
unsafe public static extern int uv_timer_start(UvTimerHandle handle, uv_timer_cb cb, long timeout, long repeat);
|
||||
|
||||
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
|
||||
unsafe public static extern int uv_timer_stop(UvTimerHandle handle);
|
||||
|
||||
[DllImport("WS2_32.dll", CallingConvention = CallingConvention.Winapi)]
|
||||
unsafe public static extern int WSAIoctl(
|
||||
IntPtr socket,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
||||
{
|
||||
public class UvTimerHandle : UvHandle
|
||||
{
|
||||
private readonly static Libuv.uv_timer_cb _uv_timer_cb = UvTimerCb;
|
||||
|
||||
private Action<UvTimerHandle> _callback;
|
||||
|
||||
public UvTimerHandle(IKestrelTrace logger) : base(logger)
|
||||
{
|
||||
}
|
||||
|
||||
public void Init(UvLoopHandle loop, Action<Action<IntPtr>, IntPtr> queueCloseHandle)
|
||||
{
|
||||
CreateHandle(
|
||||
loop.Libuv,
|
||||
loop.ThreadId,
|
||||
loop.Libuv.handle_size(Libuv.HandleType.TIMER),
|
||||
queueCloseHandle);
|
||||
|
||||
_uv.timer_init(loop, this);
|
||||
}
|
||||
|
||||
public void Start(Action<UvTimerHandle> callback, long timeout, long repeat)
|
||||
{
|
||||
_callback = callback;
|
||||
_uv.timer_start(this, _uv_timer_cb, timeout, repeat);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_uv.timer_stop(this);
|
||||
}
|
||||
|
||||
private static void UvTimerCb(IntPtr handle)
|
||||
{
|
||||
var timer = FromIntPtr<UvTimerHandle>(handle);
|
||||
|
||||
try
|
||||
{
|
||||
timer._callback(timer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
timer._log.LogError(0, ex, nameof(UvTimerCb));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
// Matches the default LimitRequestFields in Apache httpd.
|
||||
private int _maxRequestHeaderCount = 100;
|
||||
|
||||
// Matches the default http.sys keep-alive timouet.
|
||||
private TimeSpan _keepAliveTimeout = TimeSpan.FromMinutes(2);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum size of the response buffer before write
|
||||
/// calls begin to block or return tasks that don't complete until the
|
||||
|
|
@ -138,5 +141,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
_maxRequestHeaderCount = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the keep-alive timeout.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults to 2 minutes. Timeout granularity is in seconds. Sub-second values will be rounded to the next second.
|
||||
/// </remarks>
|
||||
public TimeSpan KeepAliveTimeout
|
||||
{
|
||||
get
|
||||
{
|
||||
return _keepAliveTimeout;
|
||||
}
|
||||
set
|
||||
{
|
||||
_keepAliveTimeout = TimeSpan.FromSeconds(Math.Ceiling(value.TotalSeconds));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
// 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.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
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 int LongDelay = (int)TimeSpan.FromSeconds(30).TotalMilliseconds;
|
||||
private static readonly int ShortDelay = LongDelay / 10;
|
||||
|
||||
[Fact]
|
||||
public async Task TestKeepAliveTimeout()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var tasks = new[]
|
||||
{
|
||||
ConnectionClosedWhenKeepAliveTimeoutExpires(server),
|
||||
ConnectionClosedWhenKeepAliveTimeoutExpiresAfterChunkedRequest(server),
|
||||
KeepAliveTimeoutResetsBetweenContentLengthRequests(server),
|
||||
KeepAliveTimeoutResetsBetweenChunkedRequests(server),
|
||||
KeepAliveTimeoutNotTriggeredMidContentLengthRequest(server),
|
||||
KeepAliveTimeoutNotTriggeredMidChunkedRequest(server),
|
||||
ConnectionTimesOutWhenOpenedButNoRequestSent(server)
|
||||
};
|
||||
|
||||
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 Task.Delay(LongDelay);
|
||||
|
||||
await Assert.ThrowsAsync<IOException>(async () =>
|
||||
{
|
||||
await connection.Send(
|
||||
"GET / HTTP/1.1",
|
||||
"",
|
||||
"");
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectionClosedWhenKeepAliveTimeoutExpiresAfterChunkedRequest(TestServer server)
|
||||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Transfer-Encoding: chunked",
|
||||
"",
|
||||
"5", "hello",
|
||||
"6", " world",
|
||||
"0",
|
||||
"",
|
||||
"");
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
|
||||
await Task.Delay(LongDelay);
|
||||
|
||||
await Assert.ThrowsAsync<IOException>(async () =>
|
||||
{
|
||||
await connection.Send(
|
||||
"GET / HTTP/1.1",
|
||||
"",
|
||||
"");
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async Task KeepAliveTimeoutResetsBetweenContentLengthRequests(TestServer server)
|
||||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
{
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
await connection.Send(
|
||||
"GET / HTTP/1.1",
|
||||
"",
|
||||
"");
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
await Task.Delay(ShortDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task KeepAliveTimeoutResetsBetweenChunkedRequests(TestServer server)
|
||||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
{
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Transfer-Encoding: chunked",
|
||||
"",
|
||||
"5", "hello",
|
||||
"6", " world",
|
||||
"0",
|
||||
"",
|
||||
"");
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
await Task.Delay(ShortDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task KeepAliveTimeoutNotTriggeredMidContentLengthRequest(TestServer server)
|
||||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Content-Length: 8",
|
||||
"",
|
||||
"a");
|
||||
await Task.Delay(LongDelay);
|
||||
await connection.Send("bcdefgh");
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task KeepAliveTimeoutNotTriggeredMidChunkedRequest(TestServer server)
|
||||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Transfer-Encoding: chunked",
|
||||
"",
|
||||
"5", "hello",
|
||||
"");
|
||||
await Task.Delay(LongDelay);
|
||||
await connection.Send(
|
||||
"6", " world",
|
||||
"0",
|
||||
"",
|
||||
"");
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectionTimesOutWhenOpenedButNoRequestSent(TestServer server)
|
||||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
{
|
||||
await Task.Delay(LongDelay);
|
||||
await Assert.ThrowsAsync<IOException>(async () =>
|
||||
{
|
||||
await connection.Send(
|
||||
"GET / HTTP/1.1",
|
||||
"",
|
||||
"");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private TestServer CreateServer()
|
||||
{
|
||||
return new TestServer(App, new TestServiceContext
|
||||
{
|
||||
ServerOptions = new KestrelServerOptions
|
||||
{
|
||||
AddServerHeader = false,
|
||||
Limits =
|
||||
{
|
||||
KeepAliveTimeout = KeepAliveTimeout
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task App(HttpContext httpContext)
|
||||
{
|
||||
const string response = "hello, world";
|
||||
httpContext.Response.ContentLength = response.Length;
|
||||
await httpContext.Response.WriteAsync(response);
|
||||
}
|
||||
|
||||
private async Task ReceiveResponse(TestConnection connection, TestServiceContext testServiceContext)
|
||||
{
|
||||
await connection.Receive(
|
||||
"HTTP/1.1 200 OK",
|
||||
$"Date: {testServiceContext.DateHeaderValue}",
|
||||
"Content-Length: 12",
|
||||
"",
|
||||
"hello, world");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void MaxRequestHeadersDefault()
|
||||
public void MaxRequestHeaderCountDefault()
|
||||
{
|
||||
Assert.Equal(100, (new KestrelServerLimits()).MaxRequestHeaderCount);
|
||||
}
|
||||
|
|
@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[InlineData(int.MinValue)]
|
||||
[InlineData(-1)]
|
||||
[InlineData(0)]
|
||||
public void MaxRequestHeadersInvalid(int value)
|
||||
public void MaxRequestHeaderCountInvalid(int value)
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
|
|
@ -142,11 +142,30 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(int.MaxValue)]
|
||||
public void MaxRequestHeadersValid(int value)
|
||||
public void MaxRequestHeaderCountValid(int value)
|
||||
{
|
||||
var o = new KestrelServerLimits();
|
||||
o.MaxRequestHeaderCount = value;
|
||||
Assert.Equal(value, o.MaxRequestHeaderCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeepAliveTimeoutDefault()
|
||||
{
|
||||
Assert.Equal(TimeSpan.FromMinutes(2), new KestrelServerLimits().KeepAliveTimeout);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(0.5)]
|
||||
[InlineData(2.1)]
|
||||
[InlineData(2.5)]
|
||||
[InlineData(2.9)]
|
||||
public void KeepAliveTimeoutIsRoundedToTheNextSecond(double seconds)
|
||||
{
|
||||
var o = new KestrelServerLimits();
|
||||
o.KeepAliveTimeout = TimeSpan.FromSeconds(seconds);
|
||||
Assert.Equal(Math.Ceiling(seconds), o.KeepAliveTimeout.TotalSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
|
|||
{
|
||||
throw new Exception($"Why is this getting called?{Environment.NewLine}{_stackTrace}");
|
||||
};
|
||||
|
||||
_uv_timer_init = (loop, handle) => 0;
|
||||
_uv_timer_start = (handle, callback, timeout, repeat) => 0;
|
||||
_uv_timer_stop = handle => 0;
|
||||
}
|
||||
|
||||
public Func<UvStreamHandle, int, Action<int>, int> OnWrite { get; set; }
|
||||
|
|
|
|||
|
|
@ -58,6 +58,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
}
|
||||
|
||||
public void End(ProduceEndType endType)
|
||||
{
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
}
|
||||
|
||||
public void Abort()
|
||||
{
|
||||
}
|
||||
|
|
@ -65,9 +73,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
public void Write(ArraySegment<byte> data, Action<Exception, object> callback, object state)
|
||||
{
|
||||
}
|
||||
public void End(ProduceEndType endType)
|
||||
{
|
||||
}
|
||||
|
||||
void IFrameControl.ProduceContinue()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||
{
|
||||
public class UvTimerHandleTests
|
||||
{
|
||||
[Fact]
|
||||
public void TestTimeout()
|
||||
{
|
||||
var trace = new TestKestrelTrace();
|
||||
|
||||
var loop = new UvLoopHandle(trace);
|
||||
loop.Init(new Libuv());
|
||||
|
||||
var timer = new UvTimerHandle(trace);
|
||||
timer.Init(loop, (a, b) => { });
|
||||
|
||||
var callbackInvoked = false;
|
||||
timer.Start(_ =>
|
||||
{
|
||||
callbackInvoked = true;
|
||||
}, 1, 0);
|
||||
loop.Run();
|
||||
|
||||
timer.Dispose();
|
||||
loop.Run();
|
||||
|
||||
loop.Dispose();
|
||||
|
||||
Assert.True(callbackInvoked);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestRepeat()
|
||||
{
|
||||
var trace = new TestKestrelTrace();
|
||||
|
||||
var loop = new UvLoopHandle(trace);
|
||||
loop.Init(new Libuv());
|
||||
|
||||
var timer = new UvTimerHandle(trace);
|
||||
timer.Init(loop, (callback, handle) => { });
|
||||
|
||||
var callbackCount = 0;
|
||||
timer.Start(_ =>
|
||||
{
|
||||
if (callbackCount < 2)
|
||||
{
|
||||
callbackCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
timer.Stop();
|
||||
}
|
||||
}, 1, 1);
|
||||
|
||||
loop.Run();
|
||||
|
||||
timer.Dispose();
|
||||
loop.Run();
|
||||
|
||||
loop.Dispose();
|
||||
|
||||
Assert.Equal(2, callbackCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ namespace Microsoft.AspNetCore.Testing
|
|||
_stream = new NetworkStream(_socket, false);
|
||||
_reader = new StreamReader(_stream, Encoding.ASCII);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stream.Dispose();
|
||||
|
|
@ -97,7 +98,7 @@ namespace Microsoft.AspNetCore.Testing
|
|||
var task = _reader.ReadAsync(actual, offset, actual.Length - offset);
|
||||
if (!Debugger.IsAttached)
|
||||
{
|
||||
Assert.True(await Task.WhenAny(task, Task.Delay(10000)) == task, "TestConnection.Receive timed out.");
|
||||
Assert.True(await Task.WhenAny(task, Task.Delay(TimeSpan.FromMinutes(1))) == task, "TestConnection.Receive timed out.");
|
||||
}
|
||||
var count = await task;
|
||||
if (count == 0)
|
||||
|
|
|
|||
Loading…
Reference in New Issue