Add request headers timeout (#1110).
This commit is contained in:
parent
7b2f7b94ab
commit
0312da7df3
|
|
@ -102,6 +102,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
case RequestRejectionReason.TooManyHeaders:
|
||||
ex = new BadHttpRequestException("Request contains too many headers.", 431);
|
||||
break;
|
||||
case RequestRejectionReason.RequestTimeout:
|
||||
ex = new BadHttpRequestException("Request timed out.", 408);
|
||||
break;
|
||||
default:
|
||||
ex = new BadHttpRequestException("Bad request.", 400);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
private long _lastTimestamp;
|
||||
private long _timeoutTimestamp = long.MaxValue;
|
||||
private TimeoutAction _timeoutAction;
|
||||
|
||||
public Connection(ListenerContext context, UvStreamHandle socket) : base(context)
|
||||
{
|
||||
|
|
@ -170,8 +171,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
// Called on Libuv thread
|
||||
public void Tick(long timestamp)
|
||||
{
|
||||
if (timestamp > _timeoutTimestamp)
|
||||
if (timestamp > Interlocked.Read(ref _timeoutTimestamp))
|
||||
{
|
||||
ConnectionControl.CancelTimeout();
|
||||
|
||||
if (_timeoutAction == TimeoutAction.SendTimeoutResponse)
|
||||
{
|
||||
_frame.SetBadRequestState(RequestRejectionReason.RequestTimeout);
|
||||
}
|
||||
|
||||
StopAsync();
|
||||
}
|
||||
|
||||
|
|
@ -299,12 +307,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
void IConnectionControl.SetTimeout(long milliseconds)
|
||||
void IConnectionControl.SetTimeout(long milliseconds, TimeoutAction timeoutAction)
|
||||
{
|
||||
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);
|
||||
AssignTimeout(milliseconds, timeoutAction);
|
||||
}
|
||||
|
||||
void IConnectionControl.ResetTimeout(long milliseconds, TimeoutAction timeoutAction)
|
||||
{
|
||||
AssignTimeout(milliseconds, timeoutAction);
|
||||
}
|
||||
|
||||
void IConnectionControl.CancelTimeout()
|
||||
|
|
@ -312,6 +324,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
Interlocked.Exchange(ref _timeoutTimestamp, long.MaxValue);
|
||||
}
|
||||
|
||||
private void AssignTimeout(long milliseconds, TimeoutAction timeoutAction)
|
||||
{
|
||||
_timeoutAction = timeoutAction;
|
||||
|
||||
// Add KestrelThread.HeartbeatMilliseconds extra milliseconds since this can be called right before the next heartbeat.
|
||||
Interlocked.Exchange(ref _timeoutTimestamp, _lastTimestamp + milliseconds + KestrelThread.HeartbeatMilliseconds);
|
||||
}
|
||||
|
||||
private static unsafe string GenerateConnectionId(long id)
|
||||
{
|
||||
// The following routine is ~310% faster than calling long.ToString() on x64
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
private int _requestHeadersParsed;
|
||||
|
||||
protected readonly long _keepAliveMilliseconds;
|
||||
private readonly long _requestHeadersTimeoutMilliseconds;
|
||||
|
||||
public Frame(ConnectionContext context)
|
||||
{
|
||||
|
|
@ -84,6 +85,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
FrameControl = this;
|
||||
_keepAliveMilliseconds = (long)ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds;
|
||||
_requestHeadersTimeoutMilliseconds = (long)ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds;
|
||||
}
|
||||
|
||||
public ConnectionContext ConnectionContext { get; }
|
||||
|
|
@ -372,6 +374,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
_requestProcessingStopping = true;
|
||||
}
|
||||
|
||||
return _requestProcessingTask ?? TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
@ -648,7 +651,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
protected Task TryProduceInvalidRequestResponse()
|
||||
{
|
||||
if (_requestProcessingStatus == RequestProcessingStatus.RequestStarted && _requestRejected)
|
||||
if (_requestRejected)
|
||||
{
|
||||
if (FrameRequestHeaders == null || FrameResponseHeaders == null)
|
||||
{
|
||||
|
|
@ -833,7 +836,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
return RequestLineStatus.Empty;
|
||||
}
|
||||
|
||||
ConnectionControl.CancelTimeout();
|
||||
if (_requestProcessingStatus == RequestProcessingStatus.RequestPending)
|
||||
{
|
||||
ConnectionControl.ResetTimeout(_requestHeadersTimeoutMilliseconds, TimeoutAction.SendTimeoutResponse);
|
||||
}
|
||||
|
||||
_requestProcessingStatus = RequestProcessingStatus.RequestStarted;
|
||||
|
||||
|
|
@ -1102,6 +1108,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
else if (ch == '\n')
|
||||
{
|
||||
ConnectionControl.CancelTimeout();
|
||||
consumed = end;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1274,12 +1281,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
throw ex;
|
||||
}
|
||||
|
||||
public void SetBadRequestState(RequestRejectionReason reason)
|
||||
{
|
||||
SetBadRequestState(BadHttpRequestException.GetException(reason));
|
||||
}
|
||||
|
||||
public void SetBadRequestState(BadHttpRequestException ex)
|
||||
{
|
||||
StatusCode = ex.StatusCode;
|
||||
// Setting status code will throw if response has already started
|
||||
if (!HasResponseStarted)
|
||||
{
|
||||
StatusCode = ex.StatusCode;
|
||||
}
|
||||
|
||||
_keepAlive = false;
|
||||
_requestProcessingStopping = true;
|
||||
_requestRejected = true;
|
||||
|
||||
Log.ConnectionBadRequest(ConnectionId, ex);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
while (!_requestProcessingStopping)
|
||||
{
|
||||
ConnectionControl.SetTimeout(_keepAliveMilliseconds);
|
||||
ConnectionControl.SetTimeout(_keepAliveMilliseconds, TimeoutAction.CloseConnection);
|
||||
|
||||
while (!_requestProcessingStopping && TakeStartLine(SocketInput) != RequestLineStatus.Done)
|
||||
{
|
||||
|
|
@ -141,7 +141,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
Reset();
|
||||
// Don't lose request rejection state
|
||||
if (!_requestRejected)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (BadHttpRequestException ex)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
void Pause();
|
||||
void Resume();
|
||||
void End(ProduceEndType endType);
|
||||
void SetTimeout(long milliseconds);
|
||||
void SetTimeout(long milliseconds, TimeoutAction timeoutAction);
|
||||
void ResetTimeout(long milliseconds, TimeoutAction timeoutAction);
|
||||
void CancelTimeout();
|
||||
}
|
||||
}
|
||||
|
|
@ -34,5 +34,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
HeadersExceedMaxTotalSize,
|
||||
MissingCRInHeaderLine,
|
||||
TooManyHeaders,
|
||||
RequestTimeout,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
public enum TimeoutAction
|
||||
{
|
||||
CloseConnection,
|
||||
SendTimeoutResponse
|
||||
}
|
||||
}
|
||||
|
|
@ -23,8 +23,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
// Matches the default LimitRequestFields in Apache httpd.
|
||||
private int _maxRequestHeaderCount = 100;
|
||||
|
||||
// Matches the default http.sys connection timeout.
|
||||
private TimeSpan _connectionTimeout = TimeSpan.FromMinutes(2);
|
||||
// Matches the default http.sys connectionTimeout.
|
||||
private TimeSpan _keepAliveTimeout = TimeSpan.FromMinutes(2);
|
||||
|
||||
private TimeSpan _requestHeadersTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum size of the response buffer before write
|
||||
|
|
@ -152,11 +154,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
{
|
||||
get
|
||||
{
|
||||
return _connectionTimeout;
|
||||
return _keepAliveTimeout;
|
||||
}
|
||||
set
|
||||
{
|
||||
_connectionTimeout = value;
|
||||
_keepAliveTimeout = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum amount of time the server will spend receiving request headers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults to 30 seconds.
|
||||
/// </remarks>
|
||||
public TimeSpan RequestHeadersTimeout
|
||||
{
|
||||
get
|
||||
{
|
||||
return _requestHeadersTimeout;
|
||||
}
|
||||
set
|
||||
{
|
||||
_requestHeadersTimeout = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -50,17 +49,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
"",
|
||||
"");
|
||||
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);
|
||||
});
|
||||
await connection.WaitForConnectionClose().TimeoutAfter(LongDelay);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -143,13 +132,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
using (var connection = new TestConnection(server.Port))
|
||||
{
|
||||
await Task.Delay(LongDelay);
|
||||
await Assert.ThrowsAsync<IOException>(async () =>
|
||||
{
|
||||
await connection.Send(
|
||||
"GET / HTTP/1.1",
|
||||
"",
|
||||
"");
|
||||
});
|
||||
await connection.WaitForConnectionClose().TimeoutAfter(LongDelay);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,135 @@
|
|||
// 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 RequestHeadersTimeoutTests
|
||||
{
|
||||
private static readonly TimeSpan RequestHeadersTimeout = 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 TestRequestHeadersTimeout()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var tasks = new[]
|
||||
{
|
||||
ConnectionAbortedWhenRequestHeadersNotReceivedInTime(server, ""),
|
||||
ConnectionAbortedWhenRequestHeadersNotReceivedInTime(server, "Content-Length: 1\r\n"),
|
||||
ConnectionAbortedWhenRequestHeadersNotReceivedInTime(server, "Content-Length: 1\r\n\r"),
|
||||
RequestHeadersTimeoutCanceledAfterHeadersReceived(server),
|
||||
ConnectionAbortedWhenRequestLineNotReceivedInTime(server, "P"),
|
||||
ConnectionAbortedWhenRequestLineNotReceivedInTime(server, "POST / HTTP/1.1\r"),
|
||||
TimeoutNotResetOnEachRequestLineCharacterReceived(server)
|
||||
};
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectionAbortedWhenRequestHeadersNotReceivedInTime(TestServer server, string headers)
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"GET / HTTP/1.1",
|
||||
headers);
|
||||
await ReceiveTimeoutResponse(connection, server.Context);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RequestHeadersTimeoutCanceledAfterHeadersReceived(TestServer server)
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Content-Length: 1",
|
||||
"",
|
||||
"");
|
||||
await Task.Delay(RequestHeadersTimeout);
|
||||
await connection.Send(
|
||||
"a");
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(TestServer server, string requestLine)
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(requestLine);
|
||||
await ReceiveTimeoutResponse(connection, server.Context);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TimeoutNotResetOnEachRequestLineCharacterReceived(TestServer server)
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await Assert.ThrowsAsync<IOException>(async () =>
|
||||
{
|
||||
foreach (var ch in "POST / HTTP/1.1\r\n\r\n")
|
||||
{
|
||||
await connection.Send(ch.ToString());
|
||||
await Task.Delay(ShortDelay);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private TestServer CreateServer()
|
||||
{
|
||||
return new TestServer(async httpContext =>
|
||||
{
|
||||
await httpContext.Request.Body.ReadAsync(new byte[1], 0, 1);
|
||||
await httpContext.Response.WriteAsync("hello, world");
|
||||
},
|
||||
new TestServiceContext
|
||||
{
|
||||
ServerOptions = new KestrelServerOptions
|
||||
{
|
||||
AddServerHeader = false,
|
||||
Limits =
|
||||
{
|
||||
RequestHeadersTimeout = RequestHeadersTimeout
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
|
||||
private async Task ReceiveTimeoutResponse(TestConnection connection, TestServiceContext testServiceContext)
|
||||
{
|
||||
await connection.ReceiveForcedEnd(
|
||||
"HTTP/1.1 408 Request Timeout",
|
||||
"Connection: close",
|
||||
$"Date: {testServiceContext.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
|
@ -178,6 +179,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
// https://github.com/aspnet/KestrelHttpServer/pull/1111/files#r80584475 explains the reason for this test.
|
||||
[Fact]
|
||||
public async Task SingleErrorResponseSentWhenAppSwallowsBadRequestException()
|
||||
{
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await httpContext.Request.Body.ReadAsync(new byte[1], 0, 1);
|
||||
}
|
||||
catch (BadHttpRequestException)
|
||||
{
|
||||
}
|
||||
}, new TestServiceContext()))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"POST / HTTP/1.1",
|
||||
"Transfer-Encoding: chunked",
|
||||
"",
|
||||
"g",
|
||||
"");
|
||||
await connection.ReceiveForcedEnd(
|
||||
"HTTP/1.1 400 Bad Request",
|
||||
"Connection: close",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<string, StringValues, string> NullHeaderData
|
||||
{
|
||||
get
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
};
|
||||
var connectionContext = new ConnectionContext(listenerContext);
|
||||
var connectionContext = new ConnectionContext(listenerContext)
|
||||
{
|
||||
ConnectionControl = Mock.Of<IConnectionControl>()
|
||||
};
|
||||
|
||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||
frame.Reset();
|
||||
|
|
@ -84,7 +87,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
};
|
||||
var connectionContext = new ConnectionContext(listenerContext);
|
||||
var connectionContext = new ConnectionContext(listenerContext)
|
||||
{
|
||||
ConnectionControl = Mock.Of<IConnectionControl>()
|
||||
};
|
||||
|
||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||
frame.Reset();
|
||||
|
|
@ -131,7 +137,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
};
|
||||
var connectionContext = new ConnectionContext(listenerContext);
|
||||
var connectionContext = new ConnectionContext(listenerContext)
|
||||
{
|
||||
ConnectionControl = Mock.Of<IConnectionControl>()
|
||||
};
|
||||
|
||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||
frame.Reset();
|
||||
|
|
@ -178,7 +187,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
};
|
||||
var connectionContext = new ConnectionContext(listenerContext);
|
||||
var connectionContext = new ConnectionContext(listenerContext)
|
||||
{
|
||||
ConnectionControl = Mock.Of<IConnectionControl>()
|
||||
};
|
||||
|
||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||
frame.Reset();
|
||||
|
|
@ -564,7 +576,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
};
|
||||
var connectionContext = new ConnectionContext(listenerContext);
|
||||
var connectionContext = new ConnectionContext(listenerContext)
|
||||
{
|
||||
ConnectionControl = Mock.Of<IConnectionControl>()
|
||||
};
|
||||
|
||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||
frame.Reset();
|
||||
|
|
@ -633,7 +648,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
};
|
||||
var connectionContext = new ConnectionContext(listenerContext);
|
||||
var connectionContext = new ConnectionContext(listenerContext)
|
||||
{
|
||||
ConnectionControl = Mock.Of<IConnectionControl>()
|
||||
};
|
||||
|
||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||
frame.Reset();
|
||||
|
|
@ -931,7 +949,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void TakeStartLineDisablesKeepAliveTimeoutOnFirstByteAvailable()
|
||||
public void TakeStartLineStartsRequestHeadersTimeoutOnFirstByteAvailable()
|
||||
{
|
||||
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||
var ltp = new LoggingThreadPool(trace);
|
||||
|
|
@ -960,12 +978,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
|
||||
|
||||
frame.TakeStartLine(socketInput);
|
||||
connectionControl.Verify(cc => cc.CancelTimeout());
|
||||
var expectedRequestHeadersTimeout = (long)serviceContext.ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds;
|
||||
connectionControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutAction.SendTimeoutResponse));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TakeStartLineDoesNotDisableKeepAliveTimeoutIfNoDataAvailable()
|
||||
public void TakeStartLineDoesNotStartRequestHeadersTimeoutIfNoDataAvailable()
|
||||
{
|
||||
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||
var ltp = new LoggingThreadPool(trace);
|
||||
|
|
@ -991,7 +1010,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
frame.Reset();
|
||||
|
||||
frame.TakeStartLine(socketInput);
|
||||
connectionControl.Verify(cc => cc.CancelTimeout(), Times.Never);
|
||||
connectionControl.Verify(cc => cc.ResetTimeout(It.IsAny<long>(), It.IsAny<TimeoutAction>()), Times.Never);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1132,7 +1151,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
};
|
||||
var connectionContext = new ConnectionContext(listenerContext);
|
||||
var connectionContext = new ConnectionContext(listenerContext)
|
||||
{
|
||||
ConnectionControl = Mock.Of<IConnectionControl>()
|
||||
};
|
||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||
frame.Reset();
|
||||
frame.InitializeHeaders();
|
||||
|
|
@ -1228,7 +1250,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
frame.Reset();
|
||||
|
||||
var requestProcessingTask = frame.RequestProcessingAsync();
|
||||
connectionControl.Verify(cc => cc.SetTimeout((long)serviceContext.ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds));
|
||||
|
||||
var expectedKeepAliveTimeout = (long)serviceContext.ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds;
|
||||
connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection));
|
||||
|
||||
frame.Stop();
|
||||
socketInput.IncomingFin();
|
||||
|
|
|
|||
|
|
@ -167,5 +167,25 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
o.KeepAliveTimeout = TimeSpan.FromSeconds(seconds);
|
||||
Assert.Equal(seconds, o.KeepAliveTimeout.TotalSeconds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequestHeadersTimeoutDefault()
|
||||
{
|
||||
Assert.Equal(TimeSpan.FromSeconds(30), new KestrelServerLimits().RequestHeadersTimeout);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(0.5)]
|
||||
[InlineData(1.0)]
|
||||
[InlineData(2.5)]
|
||||
[InlineData(10)]
|
||||
[InlineData(60)]
|
||||
public void RequestHeadersTimeoutValid(double seconds)
|
||||
{
|
||||
var o = new KestrelServerLimits();
|
||||
o.KeepAliveTimeout = TimeSpan.FromSeconds(seconds);
|
||||
Assert.Equal(seconds, o.KeepAliveTimeout.TotalSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
}
|
||||
|
||||
public void SetTimeout(long milliseconds)
|
||||
public void SetTimeout(long milliseconds, TimeoutAction timeoutAction)
|
||||
{
|
||||
}
|
||||
|
||||
public void ResetTimeout(long milliseconds, TimeoutAction timeoutAction)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,31 @@ namespace Microsoft.AspNetCore.Testing
|
|||
}
|
||||
}
|
||||
|
||||
public Task WaitForConnectionClose()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
var eventArgs = new SocketAsyncEventArgs();
|
||||
eventArgs.SetBuffer(new byte[1], 0, 1);
|
||||
eventArgs.Completed += ReceiveAsyncCompleted;
|
||||
eventArgs.UserToken = tcs;
|
||||
|
||||
if (!_socket.ReceiveAsync(eventArgs))
|
||||
{
|
||||
ReceiveAsyncCompleted(this, eventArgs);
|
||||
}
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private void ReceiveAsyncCompleted(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
if (e.BytesTransferred == 0)
|
||||
{
|
||||
var tcs = (TaskCompletionSource<object>)e.UserToken;
|
||||
tcs.SetResult(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static Socket CreateConnectedLoopbackSocket(int port)
|
||||
{
|
||||
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
|
|
|
|||
Loading…
Reference in New Issue