Improve keep-alive timeout.
- Track time more accurately - Control timeout in Connection instead of Frame
This commit is contained in:
parent
84efe624a6
commit
1a273f5a34
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -38,6 +39,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
private TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>();
|
private TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>();
|
||||||
private BufferSizeControl _bufferSizeControl;
|
private BufferSizeControl _bufferSizeControl;
|
||||||
|
|
||||||
|
private long _lastTimestamp;
|
||||||
|
private long _timeoutTimestamp = long.MaxValue;
|
||||||
|
|
||||||
public Connection(ListenerContext context, UvStreamHandle socket) : base(context)
|
public Connection(ListenerContext context, UvStreamHandle socket) : base(context)
|
||||||
{
|
{
|
||||||
_socket = socket;
|
_socket = socket;
|
||||||
|
|
@ -62,6 +66,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
_frame = FrameFactory(this);
|
_frame = FrameFactory(this);
|
||||||
|
|
||||||
|
_lastTimestamp = Thread.Loop.Now();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
|
|
@ -156,9 +162,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called on Libuv thread
|
// Called on Libuv thread
|
||||||
public void Tick()
|
public void Tick(long timestamp)
|
||||||
{
|
{
|
||||||
_frame.Tick();
|
if (timestamp > _timeoutTimestamp)
|
||||||
|
{
|
||||||
|
StopAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
Interlocked.Exchange(ref _lastTimestamp, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyConnectionFilter()
|
private void ApplyConnectionFilter()
|
||||||
|
|
@ -282,9 +293,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IConnectionControl.Stop()
|
void IConnectionControl.SetTimeout(long milliseconds)
|
||||||
{
|
{
|
||||||
StopAsync();
|
Debug.Assert(_timeoutTimestamp == long.MaxValue, "Concurrent timeouts are not supported");
|
||||||
|
|
||||||
|
// Add KestrelThread.HeartbeatMilliseconds extra milliseconds since this can be called right before the next heartbeat.
|
||||||
|
Interlocked.Exchange(ref _timeoutTimestamp, _lastTimestamp + milliseconds + KestrelThread.HeartbeatMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IConnectionControl.CancelTimeout()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _timeoutTimestamp, long.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe string GenerateConnectionId(long id)
|
private static unsafe string GenerateConnectionId(long id)
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
private int _remainingRequestHeadersBytesAllowed;
|
private int _remainingRequestHeadersBytesAllowed;
|
||||||
private int _requestHeadersParsed;
|
private int _requestHeadersParsed;
|
||||||
|
|
||||||
private int _secondsSinceLastRequest;
|
protected readonly long _keepAliveMilliseconds;
|
||||||
|
|
||||||
public Frame(ConnectionContext context)
|
public Frame(ConnectionContext context)
|
||||||
: base(context)
|
: base(context)
|
||||||
|
|
@ -77,6 +77,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
_pathBase = context.ServerAddress.PathBase;
|
_pathBase = context.ServerAddress.PathBase;
|
||||||
|
|
||||||
FrameControl = this;
|
FrameControl = this;
|
||||||
|
_keepAliveMilliseconds = (long)ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ConnectionIdFeature { get; set; }
|
public string ConnectionIdFeature { get; set; }
|
||||||
|
|
@ -805,6 +806,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
return RequestLineStatus.Empty;
|
return RequestLineStatus.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConnectionControl.CancelTimeout();
|
||||||
|
|
||||||
_requestProcessingStatus = RequestProcessingStatus.RequestStarted;
|
_requestProcessingStatus = RequestProcessingStatus.RequestStarted;
|
||||||
|
|
||||||
int bytesScanned;
|
int bytesScanned;
|
||||||
|
|
@ -1269,25 +1272,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
Log.ApplicationError(ConnectionId, ex);
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum RequestLineStatus
|
public enum RequestLineStatus
|
||||||
{
|
{
|
||||||
Empty,
|
Empty,
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
{
|
{
|
||||||
while (!_requestProcessingStopping)
|
while (!_requestProcessingStopping)
|
||||||
{
|
{
|
||||||
|
ConnectionControl.SetTimeout(_keepAliveMilliseconds);
|
||||||
|
|
||||||
while (!_requestProcessingStopping && TakeStartLine(SocketInput) != RequestLineStatus.Done)
|
while (!_requestProcessingStopping && TakeStartLine(SocketInput) != RequestLineStatus.Done)
|
||||||
{
|
{
|
||||||
if (SocketInput.CheckFinOrThrow())
|
if (SocketInput.CheckFinOrThrow())
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
void Pause();
|
void Pause();
|
||||||
void Resume();
|
void Resume();
|
||||||
void End(ProduceEndType endType);
|
void End(ProduceEndType endType);
|
||||||
void Stop();
|
void SetTimeout(long milliseconds);
|
||||||
|
void CancelTimeout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +172,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
var limit = buffer.Array == null ? inputLengthLimit : Math.Min(buffer.Count, inputLengthLimit);
|
var limit = buffer.Array == null ? inputLengthLimit : Math.Min(buffer.Count, inputLengthLimit);
|
||||||
if (limit == 0)
|
if (limit == 0)
|
||||||
{
|
{
|
||||||
_context.RequestFinished();
|
|
||||||
return new ValueTask<int>(0);
|
return new ValueTask<int>(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,11 +188,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
_context.RejectRequest(RequestRejectionReason.UnexpectedEndOfRequestContent);
|
_context.RejectRequest(RequestRejectionReason.UnexpectedEndOfRequestContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_inputLength == 0)
|
|
||||||
{
|
|
||||||
_context.RequestFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ValueTask<int>(actual);
|
return new ValueTask<int>(actual);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -212,11 +206,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
_context.RejectRequest(RequestRejectionReason.UnexpectedEndOfRequestContent);
|
_context.RejectRequest(RequestRejectionReason.UnexpectedEndOfRequestContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_inputLength == 0)
|
|
||||||
{
|
|
||||||
_context.RequestFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
return actual;
|
return actual;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -368,8 +357,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
_mode = Mode.Complete;
|
_mode = Mode.Complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
_context.RequestFinished();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class KestrelThread
|
public class KestrelThread
|
||||||
{
|
{
|
||||||
|
public const long HeartbeatMilliseconds = 1000;
|
||||||
|
|
||||||
private static readonly Action<object, object> _postCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state);
|
private static readonly Action<object, object> _postCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state);
|
||||||
private static readonly Action<object, object> _postAsyncCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state);
|
private static readonly Action<object, object> _postAsyncCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state);
|
||||||
|
|
||||||
|
|
@ -27,9 +29,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
||||||
// otherwise it needs to wait till the next pass of the libuv loop
|
// otherwise it needs to wait till the next pass of the libuv loop
|
||||||
private readonly int _maxLoops = 8;
|
private readonly int _maxLoops = 8;
|
||||||
|
|
||||||
// how often the heartbeat timer will tick connections
|
|
||||||
private const int _heartbeatMilliseconds = 1000;
|
|
||||||
|
|
||||||
private readonly KestrelEngine _engine;
|
private readonly KestrelEngine _engine;
|
||||||
private readonly IApplicationLifetime _appLifetime;
|
private readonly IApplicationLifetime _appLifetime;
|
||||||
private readonly Thread _thread;
|
private readonly Thread _thread;
|
||||||
|
|
@ -280,7 +279,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
||||||
_loop.Init(_engine.Libuv);
|
_loop.Init(_engine.Libuv);
|
||||||
_post.Init(_loop, OnPost, EnqueueCloseHandle);
|
_post.Init(_loop, OnPost, EnqueueCloseHandle);
|
||||||
_heartbeatTimer.Init(_loop, EnqueueCloseHandle);
|
_heartbeatTimer.Init(_loop, EnqueueCloseHandle);
|
||||||
_heartbeatTimer.Start(OnHeartbeat, timeout: 1000, repeat: 1000);
|
_heartbeatTimer.Start(OnHeartbeat, timeout: HeartbeatMilliseconds, repeat: HeartbeatMilliseconds);
|
||||||
_initCompleted = true;
|
_initCompleted = true;
|
||||||
tcs.SetResult(0);
|
tcs.SetResult(0);
|
||||||
}
|
}
|
||||||
|
|
@ -337,10 +336,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
||||||
|
|
||||||
private void OnHeartbeat(UvTimerHandle timer)
|
private void OnHeartbeat(UvTimerHandle timer)
|
||||||
{
|
{
|
||||||
|
var now = Loop.Now();
|
||||||
|
|
||||||
Walk(ptr =>
|
Walk(ptr =>
|
||||||
{
|
{
|
||||||
var handle = UvMemory.FromIntPtr<UvHandle>(ptr);
|
var handle = UvMemory.FromIntPtr<UvHandle>(ptr);
|
||||||
(handle as UvStreamHandle)?.Connection?.Tick();
|
(handle as UvStreamHandle)?.Connection?.Tick(now);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
||||||
_uv_timer_init = NativeMethods.uv_timer_init;
|
_uv_timer_init = NativeMethods.uv_timer_init;
|
||||||
_uv_timer_start = NativeMethods.uv_timer_start;
|
_uv_timer_start = NativeMethods.uv_timer_start;
|
||||||
_uv_timer_stop = NativeMethods.uv_timer_stop;
|
_uv_timer_stop = NativeMethods.uv_timer_stop;
|
||||||
|
_uv_now = NativeMethods.uv_now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second ctor that doesn't set any fields only to be used by MockLibuv
|
// Second ctor that doesn't set any fields only to be used by MockLibuv
|
||||||
|
|
@ -434,6 +435,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
||||||
ThrowIfErrored(_uv_timer_stop(handle));
|
ThrowIfErrored(_uv_timer_stop(handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Func<UvLoopHandle, long> _uv_now;
|
||||||
|
unsafe public long now(UvLoopHandle loop)
|
||||||
|
{
|
||||||
|
loop.Validate();
|
||||||
|
return _uv_now(loop);
|
||||||
|
}
|
||||||
|
|
||||||
public delegate int uv_tcp_getsockname_func(UvTcpHandle handle, out SockAddr addr, ref int namelen);
|
public delegate int uv_tcp_getsockname_func(UvTcpHandle handle, out SockAddr addr, ref int namelen);
|
||||||
protected uv_tcp_getsockname_func _uv_tcp_getsockname;
|
protected uv_tcp_getsockname_func _uv_tcp_getsockname;
|
||||||
public void tcp_getsockname(UvTcpHandle handle, out SockAddr addr, ref int namelen)
|
public void tcp_getsockname(UvTcpHandle handle, out SockAddr addr, ref int namelen)
|
||||||
|
|
@ -640,6 +648,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
||||||
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
|
||||||
unsafe public static extern int uv_timer_stop(UvTimerHandle handle);
|
unsafe public static extern int uv_timer_stop(UvTimerHandle handle);
|
||||||
|
|
||||||
|
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
unsafe public static extern long uv_now(UvLoopHandle loop);
|
||||||
|
|
||||||
[DllImport("WS2_32.dll", CallingConvention = CallingConvention.Winapi)]
|
[DllImport("WS2_32.dll", CallingConvention = CallingConvention.Winapi)]
|
||||||
unsafe public static extern int WSAIoctl(
|
unsafe public static extern int WSAIoctl(
|
||||||
IntPtr socket,
|
IntPtr socket,
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
||||||
_uv.stop(this);
|
_uv.stop(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long Now()
|
||||||
|
{
|
||||||
|
return _uv.now(this);
|
||||||
|
}
|
||||||
|
|
||||||
unsafe protected override bool ReleaseHandle()
|
unsafe protected override bool ReleaseHandle()
|
||||||
{
|
{
|
||||||
var memory = handle;
|
var memory = handle;
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
// Matches the default LimitRequestFields in Apache httpd.
|
// Matches the default LimitRequestFields in Apache httpd.
|
||||||
private int _maxRequestHeaderCount = 100;
|
private int _maxRequestHeaderCount = 100;
|
||||||
|
|
||||||
// Matches the default http.sys keep-alive timouet.
|
// Matches the default http.sys connection timeout.
|
||||||
private TimeSpan _keepAliveTimeout = TimeSpan.FromMinutes(2);
|
private TimeSpan _connectionTimeout = TimeSpan.FromMinutes(2);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the maximum size of the response buffer before write
|
/// Gets or sets the maximum size of the response buffer before write
|
||||||
|
|
@ -146,17 +146,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
/// Gets or sets the keep-alive timeout.
|
/// Gets or sets the keep-alive timeout.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Defaults to 2 minutes. Timeout granularity is in seconds. Sub-second values will be rounded to the next second.
|
/// Defaults to 2 minutes.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public TimeSpan KeepAliveTimeout
|
public TimeSpan KeepAliveTimeout
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _keepAliveTimeout;
|
return _connectionTimeout;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_keepAliveTimeout = TimeSpan.FromSeconds(Math.Ceiling(value.TotalSeconds));
|
_connectionTimeout = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,11 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -13,23 +16,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
public class KeepAliveTimeoutTests
|
public class KeepAliveTimeoutTests
|
||||||
{
|
{
|
||||||
private static readonly TimeSpan KeepAliveTimeout = TimeSpan.FromSeconds(10);
|
private static readonly TimeSpan KeepAliveTimeout = TimeSpan.FromSeconds(10);
|
||||||
private static readonly int LongDelay = (int)TimeSpan.FromSeconds(30).TotalMilliseconds;
|
private static readonly TimeSpan LongDelay = TimeSpan.FromSeconds(30);
|
||||||
private static readonly int ShortDelay = LongDelay / 10;
|
private static readonly TimeSpan ShortDelay = TimeSpan.FromSeconds(LongDelay.TotalSeconds / 10);
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TestKeepAliveTimeout()
|
public async Task TestKeepAliveTimeout()
|
||||||
{
|
{
|
||||||
using (var server = CreateServer())
|
var longRunningCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
var upgradeCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
using (var server = CreateServer(longRunningCancellationTokenSource.Token, upgradeCancellationTokenSource.Token))
|
||||||
{
|
{
|
||||||
var tasks = new[]
|
var tasks = new[]
|
||||||
{
|
{
|
||||||
ConnectionClosedWhenKeepAliveTimeoutExpires(server),
|
ConnectionClosedWhenKeepAliveTimeoutExpires(server),
|
||||||
ConnectionClosedWhenKeepAliveTimeoutExpiresAfterChunkedRequest(server),
|
ConnectionKeptAliveBetweenRequests(server),
|
||||||
KeepAliveTimeoutResetsBetweenContentLengthRequests(server),
|
ConnectionNotTimedOutWhileRequestBeingSent(server),
|
||||||
KeepAliveTimeoutResetsBetweenChunkedRequests(server),
|
ConnectionNotTimedOutWhileAppIsRunning(server, longRunningCancellationTokenSource),
|
||||||
KeepAliveTimeoutNotTriggeredMidContentLengthRequest(server),
|
ConnectionTimesOutWhenOpenedButNoRequestSent(server),
|
||||||
KeepAliveTimeoutNotTriggeredMidChunkedRequest(server),
|
KeepAliveTimeoutDoesNotApplyToUpgradedConnections(server, upgradeCancellationTokenSource)
|
||||||
ConnectionTimesOutWhenOpenedButNoRequestSent(server)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
|
|
@ -59,110 +64,76 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ConnectionClosedWhenKeepAliveTimeoutExpiresAfterChunkedRequest(TestServer server)
|
private async Task ConnectionKeptAliveBetweenRequests(TestServer server)
|
||||||
{
|
{
|
||||||
using (var connection = new TestConnection(server.Port))
|
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(
|
await connection.Send(
|
||||||
"POST / HTTP/1.1",
|
"POST / HTTP/1.1",
|
||||||
"Transfer-Encoding: chunked",
|
"Transfer-Encoding: chunked",
|
||||||
"",
|
"",
|
||||||
"5", "hello",
|
"");
|
||||||
"6", " world",
|
|
||||||
|
while (!cts.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"1",
|
||||||
|
"a",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
|
||||||
|
await connection.Send(
|
||||||
"0",
|
"0",
|
||||||
"",
|
"",
|
||||||
"");
|
"");
|
||||||
await ReceiveResponse(connection, server.Context);
|
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)
|
private async Task ConnectionNotTimedOutWhileAppIsRunning(TestServer server, CancellationTokenSource cts)
|
||||||
{
|
|
||||||
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 KeepAliveTimeoutResetsBetweenChunkedRequests(TestServer server)
|
|
||||||
{
|
|
||||||
using (var connection = new TestConnection(server.Port))
|
|
||||||
{
|
|
||||||
for (var i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
await connection.Send(
|
|
||||||
"POST / HTTP/1.1",
|
|
||||||
"Transfer-Encoding: chunked",
|
|
||||||
"",
|
|
||||||
"5", "hello",
|
|
||||||
"6", " world",
|
|
||||||
"0",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
await Task.Delay(ShortDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
await ReceiveResponse(connection, server.Context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task KeepAliveTimeoutNotTriggeredMidContentLengthRequest(TestServer server)
|
|
||||||
{
|
{
|
||||||
using (var connection = new TestConnection(server.Port))
|
using (var connection = new TestConnection(server.Port))
|
||||||
{
|
{
|
||||||
await connection.Send(
|
await connection.Send(
|
||||||
"POST / HTTP/1.1",
|
"GET /longrunning HTTP/1.1",
|
||||||
"Content-Length: 8",
|
|
||||||
"",
|
"",
|
||||||
"a");
|
"");
|
||||||
await Task.Delay(LongDelay);
|
cts.CancelAfter(LongDelay);
|
||||||
await connection.Send("bcdefgh");
|
|
||||||
await ReceiveResponse(connection, server.Context);
|
while (!cts.IsCancellationRequested)
|
||||||
}
|
{
|
||||||
}
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
await ReceiveResponse(connection, server.Context);
|
||||||
|
|
||||||
private async Task KeepAliveTimeoutNotTriggeredMidChunkedRequest(TestServer server)
|
|
||||||
{
|
|
||||||
using (var connection = new TestConnection(server.Port))
|
|
||||||
{
|
|
||||||
await connection.Send(
|
await connection.Send(
|
||||||
"POST / HTTP/1.1",
|
"GET / HTTP/1.1",
|
||||||
"Transfer-Encoding: chunked",
|
"",
|
||||||
"",
|
"");
|
||||||
"5", "hello",
|
|
||||||
"");
|
|
||||||
await Task.Delay(LongDelay);
|
|
||||||
await connection.Send(
|
|
||||||
"6", " world",
|
|
||||||
"0",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
await ReceiveResponse(connection, server.Context);
|
await ReceiveResponse(connection, server.Context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -182,9 +153,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestServer CreateServer()
|
private async Task KeepAliveTimeoutDoesNotApplyToUpgradedConnections(TestServer server, CancellationTokenSource cts)
|
||||||
{
|
{
|
||||||
return new TestServer(App, new TestServiceContext
|
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
|
ServerOptions = new KestrelServerOptions
|
||||||
{
|
{
|
||||||
|
|
@ -197,11 +193,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task App(HttpContext httpContext)
|
private async Task App(HttpContext httpContext, CancellationToken longRunningCt, CancellationToken upgradeCt)
|
||||||
{
|
{
|
||||||
const string response = "hello, world";
|
var ct = httpContext.RequestAborted;
|
||||||
httpContext.Response.ContentLength = response.Length;
|
|
||||||
await httpContext.Response.WriteAsync(response);
|
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)
|
private async Task ReceiveResponse(TestConnection connection, TestServiceContext testServiceContext)
|
||||||
|
|
@ -209,9 +230,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
await connection.Receive(
|
await connection.Receive(
|
||||||
"HTTP/1.1 200 OK",
|
"HTTP/1.1 200 OK",
|
||||||
$"Date: {testServiceContext.DateHeaderValue}",
|
$"Date: {testServiceContext.DateHeaderValue}",
|
||||||
"Content-Length: 12",
|
"Transfer-Encoding: chunked",
|
||||||
"",
|
"",
|
||||||
"hello, world");
|
"c",
|
||||||
|
"hello, world",
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
using System.Threading;
|
// 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.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel;
|
using Microsoft.AspNetCore.Server.Kestrel;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Internal;
|
||||||
|
|
@ -14,7 +18,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
public class ConnectionTests
|
public class ConnectionTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DoesNotEndConnectionOnZeroRead()
|
public async Task DoesNotEndConnectionOnZeroRead()
|
||||||
{
|
{
|
||||||
var mockLibuv = new MockLibuv();
|
var mockLibuv = new MockLibuv();
|
||||||
|
|
||||||
|
|
@ -30,14 +34,19 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
ServerAddress = ServerAddress.FromUrl("http://127.0.0.1:0"),
|
ServerAddress = ServerAddress.FromUrl("http://127.0.0.1:0"),
|
||||||
Thread = engine.Threads[0]
|
Thread = engine.Threads[0]
|
||||||
};
|
};
|
||||||
var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, trace);
|
|
||||||
var connection = new Connection(context, socket);
|
|
||||||
connection.Start();
|
|
||||||
|
|
||||||
Libuv.uv_buf_t ignored;
|
Connection connection = null;
|
||||||
mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out ignored);
|
await context.Thread.PostAsync(_ =>
|
||||||
mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored);
|
{
|
||||||
Assert.False(connection.SocketInput.CheckFinOrThrow());
|
var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, trace);
|
||||||
|
connection = new Connection(context, socket);
|
||||||
|
connection.Start();
|
||||||
|
|
||||||
|
Libuv.uv_buf_t ignored;
|
||||||
|
mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out ignored);
|
||||||
|
mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored);
|
||||||
|
Assert.False(connection.SocketInput.CheckFinOrThrow());
|
||||||
|
}, null);
|
||||||
|
|
||||||
connection.ConnectionControl.End(ProduceEndType.SocketDisconnect);
|
connection.ConnectionControl.End(ProduceEndType.SocketDisconnect);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
|
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
|
@ -724,6 +725,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
var connectionContext = new ConnectionContext()
|
var connectionContext = new ConnectionContext()
|
||||||
{
|
{
|
||||||
|
ConnectionControl = new Mock<IConnectionControl>().Object,
|
||||||
DateHeaderValueManager = new DateHeaderValueManager(),
|
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
||||||
ServerOptions = new KestrelServerOptions(),
|
ServerOptions = new KestrelServerOptions(),
|
||||||
|
|
@ -770,6 +772,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
var connectionContext = new ConnectionContext()
|
var connectionContext = new ConnectionContext()
|
||||||
{
|
{
|
||||||
|
ConnectionControl = new Mock<IConnectionControl>().Object,
|
||||||
DateHeaderValueManager = new DateHeaderValueManager(),
|
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
||||||
ServerOptions = new KestrelServerOptions(),
|
ServerOptions = new KestrelServerOptions(),
|
||||||
|
|
@ -786,6 +789,59 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TakeStartLineDisablesKeepAliveTimeoutOnFirstByteAvailable()
|
||||||
|
{
|
||||||
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
|
var ltp = new LoggingThreadPool(trace);
|
||||||
|
using (var pool = new MemoryPool())
|
||||||
|
using (var socketInput = new SocketInput(pool, ltp))
|
||||||
|
{
|
||||||
|
var connectionControl = new Mock<IConnectionControl>();
|
||||||
|
var connectionContext = new ConnectionContext()
|
||||||
|
{
|
||||||
|
ConnectionControl = connectionControl.Object,
|
||||||
|
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||||
|
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
||||||
|
ServerOptions = new KestrelServerOptions(),
|
||||||
|
Log = trace
|
||||||
|
};
|
||||||
|
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||||
|
frame.Reset();
|
||||||
|
|
||||||
|
var requestLineBytes = Encoding.ASCII.GetBytes("G");
|
||||||
|
socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
|
||||||
|
|
||||||
|
frame.TakeStartLine(socketInput);
|
||||||
|
connectionControl.Verify(cc => cc.CancelTimeout());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TakeStartLineDoesNotDisableKeepAliveTimeoutIfNoDataAvailable()
|
||||||
|
{
|
||||||
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
|
var ltp = new LoggingThreadPool(trace);
|
||||||
|
using (var pool = new MemoryPool())
|
||||||
|
using (var socketInput = new SocketInput(pool, ltp))
|
||||||
|
{
|
||||||
|
var connectionControl = new Mock<IConnectionControl>();
|
||||||
|
var connectionContext = new ConnectionContext()
|
||||||
|
{
|
||||||
|
ConnectionControl = connectionControl.Object,
|
||||||
|
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||||
|
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
||||||
|
ServerOptions = new KestrelServerOptions(),
|
||||||
|
Log = trace
|
||||||
|
};
|
||||||
|
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||||
|
frame.Reset();
|
||||||
|
|
||||||
|
frame.TakeStartLine(socketInput);
|
||||||
|
connectionControl.Verify(cc => cc.CancelTimeout(), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TakeMessageHeadersCallsConsumingCompleteWithFurthestExamined()
|
public void TakeMessageHeadersCallsConsumingCompleteWithFurthestExamined()
|
||||||
{
|
{
|
||||||
|
|
@ -864,5 +920,35 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
Assert.Equal(false, frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
|
Assert.Equal(false, frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RequestProcessingAsyncEnablesKeepAliveTimeout()
|
||||||
|
{
|
||||||
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
|
var ltp = new LoggingThreadPool(trace);
|
||||||
|
using (var pool = new MemoryPool())
|
||||||
|
using (var socketInput = new SocketInput(pool, ltp))
|
||||||
|
{
|
||||||
|
var connectionControl = new Mock<IConnectionControl>();
|
||||||
|
var connectionContext = new ConnectionContext()
|
||||||
|
{
|
||||||
|
ConnectionControl = connectionControl.Object,
|
||||||
|
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||||
|
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
||||||
|
ServerOptions = new KestrelServerOptions(),
|
||||||
|
Log = trace
|
||||||
|
};
|
||||||
|
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||||
|
frame.Reset();
|
||||||
|
|
||||||
|
var requestProcessingTask = frame.RequestProcessingAsync();
|
||||||
|
connectionControl.Verify(cc => cc.SetTimeout((long)connectionContext.ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds));
|
||||||
|
|
||||||
|
frame.Stop();
|
||||||
|
socketInput.IncomingFin();
|
||||||
|
|
||||||
|
requestProcessingTask.Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -161,11 +161,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[InlineData(2.1)]
|
[InlineData(2.1)]
|
||||||
[InlineData(2.5)]
|
[InlineData(2.5)]
|
||||||
[InlineData(2.9)]
|
[InlineData(2.9)]
|
||||||
public void KeepAliveTimeoutIsRoundedToTheNextSecond(double seconds)
|
public void KeepAliveTimeoutValid(double seconds)
|
||||||
{
|
{
|
||||||
var o = new KestrelServerLimits();
|
var o = new KestrelServerLimits();
|
||||||
o.KeepAliveTimeout = TimeSpan.FromSeconds(seconds);
|
o.KeepAliveTimeout = TimeSpan.FromSeconds(seconds);
|
||||||
Assert.Equal(Math.Ceiling(seconds), o.KeepAliveTimeout.TotalSeconds);
|
Assert.Equal(seconds, o.KeepAliveTimeout.TotalSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
|
||||||
|
|
||||||
public MockConnection(KestrelServerOptions options)
|
public MockConnection(KestrelServerOptions options)
|
||||||
{
|
{
|
||||||
|
ConnectionControl = this;
|
||||||
RequestAbortedSource = new CancellationTokenSource();
|
RequestAbortedSource = new CancellationTokenSource();
|
||||||
ServerOptions = options;
|
ServerOptions = options;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
|
||||||
_uv_timer_init = (loop, handle) => 0;
|
_uv_timer_init = (loop, handle) => 0;
|
||||||
_uv_timer_start = (handle, callback, timeout, repeat) => 0;
|
_uv_timer_start = (handle, callback, timeout, repeat) => 0;
|
||||||
_uv_timer_stop = handle => 0;
|
_uv_timer_stop = handle => 0;
|
||||||
|
_uv_now = (loop) => DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<UvStreamHandle, int, Action<int>, int> OnWrite { get; set; }
|
public Func<UvStreamHandle, int, Action<int>, int> OnWrite { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
var trace = new KestrelTrace(new TestKestrelTrace());
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
var ltp = new LoggingThreadPool(trace);
|
var ltp = new LoggingThreadPool(trace);
|
||||||
var context = new Frame<object>(null, new ConnectionContext() { ServerAddress = new ServerAddress() })
|
var connectionContext = new ConnectionContext()
|
||||||
|
{
|
||||||
|
ServerAddress = new ServerAddress(),
|
||||||
|
ServerOptions = new KestrelServerOptions()
|
||||||
|
};
|
||||||
|
var context = new Frame<object>(null, connectionContext)
|
||||||
{
|
{
|
||||||
DateHeaderValueManager = new DateHeaderValueManager(),
|
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
||||||
|
|
@ -63,7 +68,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void SetTimeout(long milliseconds)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelTimeout()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue