Improve timeout logic
This commit is contained in:
parent
66d8e8198d
commit
1e465e9643
|
|
@ -7,8 +7,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks
|
|||
{
|
||||
public class MockTimeoutControl : ITimeoutControl
|
||||
{
|
||||
public bool TimedOut { get; }
|
||||
|
||||
public void CancelTimeout()
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -345,4 +345,10 @@
|
|||
<data name="PositiveNumberOrNullMinDataRateRequired" xml:space="preserve">
|
||||
<value>Value must be a positive number. To disable a minimum data rate, use null where a MinDataRate instance is expected.</value>
|
||||
</data>
|
||||
<data name="ConcurrentTimeoutsNotSupported" xml:space="preserve">
|
||||
<value>Concurrent timeouts are not supported.</value>
|
||||
</data>
|
||||
<data name="PositiveFiniteTimeSpanRequired" xml:space="preserve">
|
||||
<value>Timespan must be positive and finite.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// Feature for efficiently handling connection timeouts.
|
||||
/// </summary>
|
||||
public interface IConnectionTimeoutFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// Close the connection after the specified positive finite <see cref="TimeSpan"/>
|
||||
/// unless the timeout is canceled or reset. This will fail if there is an ongoing timeout.
|
||||
/// </summary>
|
||||
void SetTimeout(TimeSpan timeSpan);
|
||||
|
||||
/// <summary>
|
||||
/// Close the connection after the specified positive finite <see cref="TimeSpan"/>
|
||||
/// unless the timeout is canceled or reset. This will cancel any ongoing timeouts.
|
||||
/// </summary>
|
||||
void ResetTimeout(TimeSpan timeSpan);
|
||||
|
||||
/// <summary>
|
||||
/// Prevent the connection from closing after a timeout specified by <see cref="SetTimeout(TimeSpan)"/>
|
||||
/// or <see cref="ResetTimeout(TimeSpan)"/>.
|
||||
/// </summary>
|
||||
void CancelTimeout();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ using System.Net;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
|
|
@ -20,7 +19,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||
{
|
||||
public class FrameConnection : ITimeoutControl
|
||||
public class FrameConnection : ITimeoutControl, IConnectionTimeoutFeature
|
||||
{
|
||||
private const int Http2ConnectionNotStarted = 0;
|
||||
private const int Http2ConnectionStarted = 1;
|
||||
|
|
@ -58,7 +57,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
internal Frame Frame => _frame;
|
||||
internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton;
|
||||
|
||||
public bool TimedOut { get; private set; }
|
||||
// For testing
|
||||
internal bool RequestTimedOut { get; private set; }
|
||||
|
||||
public string ConnectionId => _context.ConnectionId;
|
||||
public IPEndPoint LocalEndPoint => _context.LocalEndPoint;
|
||||
|
|
@ -106,8 +106,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
{
|
||||
adaptedPipeline = new AdaptedPipeline(transport,
|
||||
application,
|
||||
PipeFactory.Create(AdaptedInputPipeOptions),
|
||||
PipeFactory.Create(AdaptedOutputPipeOptions));
|
||||
PipeFactory.Create(AdaptedInputPipeOptions),
|
||||
PipeFactory.Create(AdaptedOutputPipeOptions));
|
||||
|
||||
transport = adaptedPipeline;
|
||||
}
|
||||
|
|
@ -134,6 +134,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
_context.ServiceContext.ConnectionManager.AddConnection(_context.FrameConnectionId, this);
|
||||
_lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks;
|
||||
|
||||
_frame.ConnectionFeatures.Set<IConnectionTimeoutFeature>(this);
|
||||
|
||||
if (adaptedPipeline != null)
|
||||
{
|
||||
// Stream can be null here and run async will close the connection in that case
|
||||
|
|
@ -195,7 +197,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
_socketClosedTcs.TrySetResult(null);
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
public Task StopProcessingNextRequestAsync()
|
||||
{
|
||||
Debug.Assert(_frame != null, $"{nameof(_frame)} is null");
|
||||
|
||||
|
|
@ -205,7 +207,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
}
|
||||
else
|
||||
{
|
||||
_frame.Stop();
|
||||
_frame.StopProcessingNextRequest();
|
||||
}
|
||||
|
||||
return _lifetimeTask;
|
||||
|
|
@ -233,19 +235,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
return _lifetimeTask;
|
||||
}
|
||||
|
||||
public void SetTimeoutResponse()
|
||||
public void SendTimeoutResponse()
|
||||
{
|
||||
Debug.Assert(_frame != null, $"{nameof(_frame)} is null");
|
||||
|
||||
_frame.SetBadRequestState(RequestRejectionReason.RequestTimeout);
|
||||
RequestTimedOut = true;
|
||||
_frame.SendTimeoutResponse();
|
||||
}
|
||||
|
||||
public void Timeout()
|
||||
public void StopProcessingNextRequest()
|
||||
{
|
||||
Debug.Assert(_frame != null, $"{nameof(_frame)} is null");
|
||||
|
||||
TimedOut = true;
|
||||
_frame.Stop();
|
||||
_frame.StopProcessingNextRequest();
|
||||
}
|
||||
|
||||
private async Task<Stream> ApplyConnectionAdaptersAsync()
|
||||
|
|
@ -303,11 +305,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
|
||||
private void CheckForTimeout(long timestamp)
|
||||
{
|
||||
if (TimedOut)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Use PlatformApis.VolatileRead equivalent again
|
||||
if (timestamp > Interlocked.Read(ref _timeoutTimestamp))
|
||||
{
|
||||
|
|
@ -315,12 +312,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
{
|
||||
CancelTimeout();
|
||||
|
||||
if (_timeoutAction == TimeoutAction.SendTimeoutResponse)
|
||||
switch (_timeoutAction)
|
||||
{
|
||||
SetTimeoutResponse();
|
||||
case TimeoutAction.StopProcessingNextRequest:
|
||||
StopProcessingNextRequest();
|
||||
break;
|
||||
case TimeoutAction.SendTimeoutResponse:
|
||||
SendTimeoutResponse();
|
||||
break;
|
||||
case TimeoutAction.AbortConnection:
|
||||
Abort(new TimeoutException());
|
||||
break;
|
||||
}
|
||||
|
||||
Timeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -330,7 +333,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
// The only time when both a timeout is set and the read data rate could be enforced is
|
||||
// when draining the request body. Since there's already a (short) timeout set for draining,
|
||||
// it's safe to not check the data rate at this point.
|
||||
if (TimedOut || Interlocked.Read(ref _timeoutTimestamp) != long.MaxValue)
|
||||
if (Interlocked.Read(ref _timeoutTimestamp) != long.MaxValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -352,7 +355,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
if (rate < minRequestBodyDataRate.BytesPerSecond && !Debugger.IsAttached)
|
||||
{
|
||||
Log.RequestBodyMininumDataRateNotSatisfied(_context.ConnectionId, _frame.TraceIdentifier, minRequestBodyDataRate.BytesPerSecond);
|
||||
Timeout();
|
||||
SendTimeoutResponse();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -370,16 +373,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
|
||||
private void CheckForWriteDataRateTimeout(long timestamp)
|
||||
{
|
||||
if (TimedOut)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_writeTimingLock)
|
||||
{
|
||||
if (_writeTimingWrites > 0 && timestamp > _writeTimingTimeoutTimestamp && !Debugger.IsAttached)
|
||||
{
|
||||
TimedOut = true;
|
||||
RequestTimedOut = true;
|
||||
Log.ResponseMininumDataRateNotSatisfied(_frame.ConnectionIdFeature, _frame.TraceIdentifier);
|
||||
Abort(new TimeoutException());
|
||||
}
|
||||
|
|
@ -484,5 +482,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
_writeTimingWrites--;
|
||||
}
|
||||
}
|
||||
|
||||
void IConnectionTimeoutFeature.SetTimeout(TimeSpan timeSpan)
|
||||
{
|
||||
if (timeSpan < TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentException(CoreStrings.PositiveFiniteTimeSpanRequired, nameof(timeSpan));
|
||||
}
|
||||
if (_timeoutTimestamp != long.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException(CoreStrings.ConcurrentTimeoutsNotSupported);
|
||||
}
|
||||
|
||||
SetTimeout(timeSpan.Ticks, TimeoutAction.AbortConnection);
|
||||
}
|
||||
|
||||
void IConnectionTimeoutFeature.ResetTimeout(TimeSpan timeSpan)
|
||||
{
|
||||
if (timeSpan < TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentException(CoreStrings.PositiveFiniteTimeSpanRequired, nameof(timeSpan));
|
||||
}
|
||||
|
||||
ResetTimeout(timeSpan.Ticks, TimeoutAction.AbortConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Protocols;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
|
@ -52,13 +51,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
protected Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
|
||||
protected Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted;
|
||||
|
||||
protected volatile bool _requestProcessingStopping; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
|
||||
protected int _requestAborted;
|
||||
protected volatile int _requestAborted;
|
||||
private volatile bool _requestTimedOut;
|
||||
private CancellationTokenSource _abortedCts;
|
||||
private CancellationToken? _manuallySetRequestAbortToken;
|
||||
|
||||
protected RequestProcessingStatus _requestProcessingStatus;
|
||||
protected bool _keepAlive;
|
||||
protected volatile bool _keepAlive = true; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
|
||||
protected bool _upgradeAvailable;
|
||||
private volatile bool _wasUpgraded;
|
||||
private bool _canHaveBody;
|
||||
|
|
@ -111,6 +110,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
public IPipeReader Input => _frameContext.Transport.Input;
|
||||
public OutputProducer Output { get; }
|
||||
public ITimeoutControl TimeoutControl => _frameContext.TimeoutControl;
|
||||
public bool RequestTimedOut => _requestTimedOut;
|
||||
|
||||
protected IKestrelTrace Log => ServiceContext.Log;
|
||||
private DateHeaderValueManager DateHeaderValueManager => ServiceContext.DateHeaderValueManager;
|
||||
|
|
@ -260,7 +260,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
var cts = _abortedCts;
|
||||
return
|
||||
cts != null ? cts.Token :
|
||||
(Volatile.Read(ref _requestAborted) == 1) ? new CancellationToken(true) :
|
||||
(_requestAborted == 1) ? new CancellationToken(true) :
|
||||
RequestAbortedSource.Token;
|
||||
}
|
||||
set
|
||||
|
|
@ -285,7 +285,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
var cts = LazyInitializer.EnsureInitialized(ref _abortedCts, () => new CancellationTokenSource())
|
||||
?? new CancellationTokenSource();
|
||||
|
||||
if (Volatile.Read(ref _requestAborted) == 1)
|
||||
if (_requestAborted == 1)
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
|
|
@ -329,9 +329,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_onCompleted = null;
|
||||
|
||||
_requestProcessingStatus = RequestProcessingStatus.RequestPending;
|
||||
_keepAlive = false;
|
||||
_autoChunk = false;
|
||||
_applicationException = null;
|
||||
_requestTimedOut = false;
|
||||
_requestRejectedException = null;
|
||||
|
||||
ResetFeatureCollection();
|
||||
|
||||
|
|
@ -389,9 +390,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
/// Called on all active connections when the server wants to initiate a shutdown
|
||||
/// and after a keep-alive timeout.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
public void StopProcessingNextRequest()
|
||||
{
|
||||
_requestProcessingStopping = true;
|
||||
_keepAlive = false;
|
||||
Input.CancelPendingRead();
|
||||
}
|
||||
|
||||
public void SendTimeoutResponse()
|
||||
{
|
||||
_requestTimedOut = true;
|
||||
Input.CancelPendingRead();
|
||||
}
|
||||
|
||||
|
|
@ -415,7 +422,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
if (Interlocked.Exchange(ref _requestAborted, 1) == 0)
|
||||
{
|
||||
_requestProcessingStopping = true;
|
||||
_keepAlive = false;
|
||||
|
||||
_frameStreams?.Abort(error);
|
||||
|
||||
|
|
@ -789,7 +796,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
protected Task TryProduceInvalidRequestResponse()
|
||||
{
|
||||
if (_requestRejectedException != null)
|
||||
// If _requestAborted is set, the connection has already been closed.
|
||||
if (_requestRejectedException != null && _requestAborted == 0)
|
||||
{
|
||||
return ProduceEnd();
|
||||
}
|
||||
|
|
@ -804,7 +812,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
if (HasResponseStarted)
|
||||
{
|
||||
// We can no longer change the response, so we simply close the connection.
|
||||
_requestProcessingStopping = true;
|
||||
_keepAlive = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
@ -883,9 +891,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
var hasTransferEncoding = responseHeaders.HasTransferEncoding;
|
||||
var transferCoding = FrameHeaders.GetFinalTransferCoding(responseHeaders.HeaderTransferEncoding);
|
||||
|
||||
if (_keepAlive && hasConnection)
|
||||
if (_keepAlive && hasConnection && (connectionOptions & ConnectionOptions.KeepAlive) != ConnectionOptions.KeepAlive)
|
||||
{
|
||||
_keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive;
|
||||
_keepAlive = false;
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7230#section-3.3.1
|
||||
|
|
@ -1171,7 +1179,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
|
||||
_keepAlive = false;
|
||||
_requestProcessingStopping = true;
|
||||
_requestRejectedException = ex;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Protocols;
|
||||
|
|
@ -32,15 +31,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
try
|
||||
{
|
||||
while (!_requestProcessingStopping)
|
||||
while (_keepAlive)
|
||||
{
|
||||
TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutAction.CloseConnection);
|
||||
|
||||
Reset();
|
||||
TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutAction.StopProcessingNextRequest);
|
||||
|
||||
while (!_requestProcessingStopping)
|
||||
while (_requestProcessingStatus != RequestProcessingStatus.AppStarted)
|
||||
{
|
||||
var result = await Input.ReadAsync();
|
||||
|
||||
var examined = result.Buffer.End;
|
||||
var consumed = result.Buffer.End;
|
||||
|
||||
|
|
@ -62,11 +61,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
Input.Advance(consumed, examined);
|
||||
}
|
||||
|
||||
if (_requestProcessingStatus == RequestProcessingStatus.AppStarted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
switch (_requestProcessingStatus)
|
||||
|
|
@ -81,132 +75,140 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
RequestRejectionReason.MalformedRequestInvalidHeaders);
|
||||
}
|
||||
}
|
||||
else if (!_keepAlive && _requestProcessingStatus == RequestProcessingStatus.RequestPending)
|
||||
{
|
||||
// Stop the request processing loop if the server is shutting down or there was a keep-alive timeout
|
||||
// and there is no ongoing request.
|
||||
return;
|
||||
}
|
||||
else if (RequestTimedOut)
|
||||
{
|
||||
// In this case, there is an ongoing request but the start line/header parsing has timed out, so send
|
||||
// a 408 response.
|
||||
throw BadHttpRequestException.GetException(RequestRejectionReason.RequestTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_requestProcessingStopping)
|
||||
EnsureHostHeaderExists();
|
||||
|
||||
var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this);
|
||||
|
||||
if (!messageBody.RequestKeepAlive)
|
||||
{
|
||||
EnsureHostHeaderExists();
|
||||
_keepAlive = false;
|
||||
}
|
||||
|
||||
var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this);
|
||||
_keepAlive = messageBody.RequestKeepAlive;
|
||||
_upgradeAvailable = messageBody.RequestUpgrade;
|
||||
_upgradeAvailable = messageBody.RequestUpgrade;
|
||||
|
||||
InitializeStreams(messageBody);
|
||||
InitializeStreams(messageBody);
|
||||
|
||||
var context = _application.CreateContext(this);
|
||||
var context = _application.CreateContext(this);
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
KestrelEventSource.Log.RequestStart(this);
|
||||
|
||||
await _application.ProcessRequestAsync(context);
|
||||
|
||||
if (_requestAborted == 0)
|
||||
{
|
||||
KestrelEventSource.Log.RequestStart(this);
|
||||
|
||||
await _application.ProcessRequestAsync(context);
|
||||
|
||||
if (Volatile.Read(ref _requestAborted) == 0)
|
||||
{
|
||||
VerifyResponseContentLength();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ReportApplicationError(ex);
|
||||
|
||||
if (ex is BadHttpRequestException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
KestrelEventSource.Log.RequestStop(this);
|
||||
|
||||
// Trigger OnStarting if it hasn't been called yet and the app hasn't
|
||||
// already failed. If an OnStarting callback throws we can go through
|
||||
// our normal error handling in ProduceEnd.
|
||||
// https://github.com/aspnet/KestrelHttpServer/issues/43
|
||||
if (!HasResponseStarted && _applicationException == null && _onStarting != null)
|
||||
{
|
||||
await FireOnStarting();
|
||||
}
|
||||
|
||||
PauseStreams();
|
||||
|
||||
if (_onCompleted != null)
|
||||
{
|
||||
await FireOnCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
// If _requestAbort is set, the connection has already been closed.
|
||||
if (Volatile.Read(ref _requestAborted) == 0)
|
||||
{
|
||||
if (HasResponseStarted)
|
||||
{
|
||||
// If the response has already started, call ProduceEnd() before
|
||||
// consuming the rest of the request body to prevent
|
||||
// delaying clients waiting for the chunk terminator:
|
||||
//
|
||||
// https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663
|
||||
//
|
||||
// ProduceEnd() must be called before _application.DisposeContext(), to ensure
|
||||
// HttpContext.Response.StatusCode is correctly set when
|
||||
// IHttpContextFactory.Dispose(HttpContext) is called.
|
||||
await ProduceEnd();
|
||||
}
|
||||
|
||||
// ForZeroContentLength does not complete the reader nor the writer
|
||||
if (!messageBody.IsEmpty && _keepAlive)
|
||||
{
|
||||
// Finish reading the request body in case the app did not.
|
||||
TimeoutControl.SetTimeout(Constants.RequestBodyDrainTimeout.Ticks, TimeoutAction.SendTimeoutResponse);
|
||||
await messageBody.ConsumeAsync();
|
||||
TimeoutControl.CancelTimeout();
|
||||
}
|
||||
|
||||
if (!HasResponseStarted)
|
||||
{
|
||||
await ProduceEnd();
|
||||
}
|
||||
}
|
||||
else if (!HasResponseStarted)
|
||||
{
|
||||
// If the request was aborted and no response was sent, there's no
|
||||
// meaningful status code to log.
|
||||
StatusCode = 0;
|
||||
VerifyResponseContentLength();
|
||||
}
|
||||
}
|
||||
catch (BadHttpRequestException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle BadHttpRequestException thrown during app execution or remaining message body consumption.
|
||||
// This has to be caught here so StatusCode is set properly before disposing the HttpContext
|
||||
// (DisposeContext logs StatusCode).
|
||||
SetBadRequestState(ex);
|
||||
ReportApplicationError(ex);
|
||||
|
||||
if (ex is BadHttpRequestException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_application.DisposeContext(context, _applicationException);
|
||||
KestrelEventSource.Log.RequestStop(this);
|
||||
|
||||
// StopStreams should be called before the end of the "if (!_requestProcessingStopping)" block
|
||||
// to ensure InitializeStreams has been called.
|
||||
StopStreams();
|
||||
|
||||
if (HasStartedConsumingRequestBody)
|
||||
// Trigger OnStarting if it hasn't been called yet and the app hasn't
|
||||
// already failed. If an OnStarting callback throws we can go through
|
||||
// our normal error handling in ProduceEnd.
|
||||
// https://github.com/aspnet/KestrelHttpServer/issues/43
|
||||
if (!HasResponseStarted && _applicationException == null && _onStarting != null)
|
||||
{
|
||||
RequestBodyPipe.Reader.Complete();
|
||||
await FireOnStarting();
|
||||
}
|
||||
|
||||
// Wait for MessageBody.PumpAsync() to call RequestBodyPipe.Writer.Complete().
|
||||
await messageBody.StopAsync();
|
||||
PauseStreams();
|
||||
|
||||
// At this point both the request body pipe reader and writer should be completed.
|
||||
RequestBodyPipe.Reset();
|
||||
if (_onCompleted != null)
|
||||
{
|
||||
await FireOnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_keepAlive)
|
||||
// If _requestAbort is set, the connection has already been closed.
|
||||
if (_requestAborted == 0)
|
||||
{
|
||||
if (HasResponseStarted)
|
||||
{
|
||||
// If the response has already started, call ProduceEnd() before
|
||||
// consuming the rest of the request body to prevent
|
||||
// delaying clients waiting for the chunk terminator:
|
||||
//
|
||||
// https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663
|
||||
//
|
||||
// ProduceEnd() must be called before _application.DisposeContext(), to ensure
|
||||
// HttpContext.Response.StatusCode is correctly set when
|
||||
// IHttpContextFactory.Dispose(HttpContext) is called.
|
||||
await ProduceEnd();
|
||||
}
|
||||
|
||||
// ForZeroContentLength does not complete the reader nor the writer
|
||||
if (!messageBody.IsEmpty && _keepAlive)
|
||||
{
|
||||
// Finish reading the request body in case the app did not.
|
||||
TimeoutControl.SetTimeout(Constants.RequestBodyDrainTimeout.Ticks, TimeoutAction.SendTimeoutResponse);
|
||||
await messageBody.ConsumeAsync();
|
||||
TimeoutControl.CancelTimeout();
|
||||
}
|
||||
|
||||
if (!HasResponseStarted)
|
||||
{
|
||||
await ProduceEnd();
|
||||
}
|
||||
}
|
||||
else if (!HasResponseStarted)
|
||||
{
|
||||
// If the request was aborted and no response was sent, there's no
|
||||
// meaningful status code to log.
|
||||
StatusCode = 0;
|
||||
}
|
||||
}
|
||||
catch (BadHttpRequestException ex)
|
||||
{
|
||||
// End the connection for non keep alive as data incoming may have been thrown off
|
||||
return;
|
||||
// Handle BadHttpRequestException thrown during app execution or remaining message body consumption.
|
||||
// This has to be caught here so StatusCode is set properly before disposing the HttpContext
|
||||
// (DisposeContext logs StatusCode).
|
||||
SetBadRequestState(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_application.DisposeContext(context, _applicationException);
|
||||
|
||||
// StopStreams should be called before the end of the "if (!_requestProcessingStopping)" block
|
||||
// to ensure InitializeStreams has been called.
|
||||
StopStreams();
|
||||
|
||||
if (HasStartedConsumingRequestBody)
|
||||
{
|
||||
RequestBodyPipe.Reader.Complete();
|
||||
|
||||
// Wait for MessageBody.PumpAsync() to call RequestBodyPipe.Writer.Complete().
|
||||
await messageBody.StopAsync();
|
||||
|
||||
// At this point both the request body pipe reader and writer should be completed.
|
||||
RequestBodyPipe.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -237,13 +239,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
try
|
||||
{
|
||||
Input.Complete();
|
||||
|
||||
// If _requestAborted is set, the connection has already been closed.
|
||||
if (Volatile.Read(ref _requestAborted) == 0)
|
||||
{
|
||||
await TryProduceInvalidRequestResponse();
|
||||
Output.Dispose();
|
||||
}
|
||||
await TryProduceInvalidRequestResponse();
|
||||
Output.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
var result = await awaitable;
|
||||
|
||||
if (_context.TimeoutControl.TimedOut)
|
||||
if (_context.RequestTimedOut)
|
||||
{
|
||||
_context.ThrowRequestRejected(RequestRejectionReason.RequestTimeout);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,8 +50,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
public IKestrelTrace Log => _context.ServiceContext.Log;
|
||||
|
||||
bool ITimeoutControl.TimedOut => throw new NotImplementedException();
|
||||
|
||||
public void Abort(Exception ex)
|
||||
{
|
||||
_stopping = true;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
|
||||
connectionManager.Walk(connection =>
|
||||
{
|
||||
closeTasks.Add(connection.StopAsync());
|
||||
closeTasks.Add(connection.StopProcessingNextRequestAsync());
|
||||
});
|
||||
|
||||
var allClosedTask = Task.WhenAll(closeTasks.ToArray());
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
{
|
||||
public interface ITimeoutControl
|
||||
{
|
||||
bool TimedOut { get; }
|
||||
|
||||
void SetTimeout(long ticks, TimeoutAction timeoutAction);
|
||||
void ResetTimeout(long ticks, TimeoutAction timeoutAction);
|
||||
void CancelTimeout();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
{
|
||||
public enum TimeoutAction
|
||||
{
|
||||
CloseConnection,
|
||||
SendTimeoutResponse
|
||||
StopProcessingNextRequest,
|
||||
SendTimeoutResponse,
|
||||
AbortConnection,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1074,6 +1074,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
internal static string FormatPositiveNumberOrNullMinDataRateRequired()
|
||||
=> GetString("PositiveNumberOrNullMinDataRateRequired");
|
||||
|
||||
/// <summary>
|
||||
/// Concurrent timeouts are not supported.
|
||||
/// </summary>
|
||||
internal static string ConcurrentTimeoutsNotSupported
|
||||
{
|
||||
get => GetString("ConcurrentTimeoutsNotSupported");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Concurrent timeouts are not supported.
|
||||
/// </summary>
|
||||
internal static string FormatConcurrentTimeoutsNotSupported()
|
||||
=> GetString("ConcurrentTimeoutsNotSupported");
|
||||
|
||||
/// <summary>
|
||||
/// Timespan must be positive and finite.
|
||||
/// </summary>
|
||||
internal static string PositiveFiniteTimeSpanRequired
|
||||
{
|
||||
get => GetString("PositiveFiniteTimeSpanRequired");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timespan must be positive and finite.
|
||||
/// </summary>
|
||||
internal static string FormatPositiveFiniteTimeSpanRequired()
|
||||
=> GetString("PositiveFiniteTimeSpanRequired");
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
||||
{
|
||||
|
|
@ -13,6 +14,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
|||
/// </summary>
|
||||
public class HttpsConnectionAdapterOptions
|
||||
{
|
||||
private TimeSpan _handshakeTimeout;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="HttpsConnectionAdapterOptions"/>.
|
||||
/// </summary>
|
||||
|
|
@ -20,6 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
|||
{
|
||||
ClientCertificateMode = ClientCertificateMode.NoCertificate;
|
||||
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11;
|
||||
HandshakeTimeout = TimeSpan.FromSeconds(10);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -51,5 +55,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
|||
/// Specifies whether the certificate revocation list is checked during authentication.
|
||||
/// </summary>
|
||||
public bool CheckCertificateRevocation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the maximum amount of time allowed for the TLS/SSL handshake. This must be positive and finite.
|
||||
/// </summary>
|
||||
public TimeSpan HandshakeTimeout
|
||||
{
|
||||
get => _handshakeTimeout;
|
||||
set
|
||||
{
|
||||
if (value <= TimeSpan.Zero && value != Timeout.InfiniteTimeSpan)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), HttpsStrings.PositiveTimeSpanRequired);
|
||||
}
|
||||
_handshakeTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,9 +120,15 @@
|
|||
<data name="AuthenticationFailed" xml:space="preserve">
|
||||
<value>Failed to authenticate HTTPS connection.</value>
|
||||
</data>
|
||||
<data name="AuthenticationTimedOut" xml:space="preserve">
|
||||
<value>Authentication of the HTTPS connection timed out.</value>
|
||||
</data>
|
||||
<data name="InvalidServerCertificateEku" xml:space="preserve">
|
||||
<value>Certificate {thumbprint} cannot be used as an SSL server certificate. It has an Extended Key Usage extension but the usages do not include Server Authentication (OID 1.3.6.1.5.5.7.3.1).</value>
|
||||
</data>
|
||||
<data name="PositiveTimeSpanRequired" xml:space="preserve">
|
||||
<value>Value must be a positive TimeSpan.</value>
|
||||
</data>
|
||||
<data name="ServiceCertificateRequired" xml:space="preserve">
|
||||
<value>The server certificate parameter is required.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using System.Security.Cryptography.X509Certificates;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||
|
|
@ -108,17 +109,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
|||
certificateRequired = true;
|
||||
}
|
||||
|
||||
var timeoutFeature = context.Features.Get<IConnectionTimeoutFeature>();
|
||||
timeoutFeature.SetTimeout(_options.HandshakeTimeout);
|
||||
|
||||
try
|
||||
{
|
||||
await sslStream.AuthenticateAsServerAsync(_serverCertificate, certificateRequired,
|
||||
_options.SslProtocols, _options.CheckCertificateRevocation);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger?.LogInformation(2, HttpsStrings.AuthenticationTimedOut);
|
||||
sslStream.Dispose();
|
||||
return _closedAdaptedConnection;
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_logger?.LogInformation(1, ex, HttpsStrings.AuthenticationFailed);
|
||||
sslStream.Dispose();
|
||||
return _closedAdaptedConnection;
|
||||
}
|
||||
finally
|
||||
{
|
||||
timeoutFeature.CancelTimeout();
|
||||
}
|
||||
|
||||
// Always set the feature even though the cert might be null
|
||||
context.Features.Set<ITlsConnectionFeature>(new TlsConnectionFeature
|
||||
|
|
|
|||
|
|
@ -24,6 +24,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
|||
internal static string FormatAuthenticationFailed()
|
||||
=> GetString("AuthenticationFailed");
|
||||
|
||||
/// <summary>
|
||||
/// Authentication of the HTTPS connection timed out.
|
||||
/// </summary>
|
||||
internal static string AuthenticationTimedOut
|
||||
{
|
||||
get => GetString("AuthenticationTimedOut");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authentication of the HTTPS connection timed out.
|
||||
/// </summary>
|
||||
internal static string FormatAuthenticationTimedOut()
|
||||
=> GetString("AuthenticationTimedOut");
|
||||
|
||||
/// <summary>
|
||||
/// Certificate {thumbprint} cannot be used as an SSL server certificate. It has an Extended Key Usage extension but the usages do not include Server Authentication (OID 1.3.6.1.5.5.7.3.1).
|
||||
/// </summary>
|
||||
|
|
@ -38,6 +52,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
|||
internal static string FormatInvalidServerCertificateEku(object thumbprint)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidServerCertificateEku", "thumbprint"), thumbprint);
|
||||
|
||||
/// <summary>
|
||||
/// Value must be a positive TimeSpan.
|
||||
/// </summary>
|
||||
internal static string PositiveTimeSpanRequired
|
||||
{
|
||||
get => GetString("PositiveTimeSpanRequired");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value must be a positive TimeSpan.
|
||||
/// </summary>
|
||||
internal static string FormatPositiveTimeSpanRequired()
|
||||
=> GetString("PositiveTimeSpanRequired");
|
||||
|
||||
/// <summary>
|
||||
/// The server certificate parameter is required.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.SetTimeout(1, TimeoutAction.SendTimeoutResponse);
|
||||
_frameConnection.Tick(now.AddTicks(2).Add(Heartbeat.Interval));
|
||||
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
TickBodyWithMinimumDataRate(mockLogger.Object, bytesPerSecond);
|
||||
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
TickBodyWithMinimumDataRate(mockLogger.Object, bytesPerSecond);
|
||||
|
||||
// Timed out
|
||||
Assert.True(_frameConnection.TimedOut);
|
||||
Assert.True(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(logger =>
|
||||
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Once);
|
||||
}
|
||||
|
|
@ -144,7 +144,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(now);
|
||||
|
||||
// Not timed out
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(logger =>
|
||||
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Never);
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(now);
|
||||
|
||||
// Timed out
|
||||
Assert.True(_frameConnection.TimedOut);
|
||||
Assert.True(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(logger =>
|
||||
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Once);
|
||||
}
|
||||
|
|
@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(now);
|
||||
|
||||
// Not timed out
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(logger =>
|
||||
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Never);
|
||||
|
||||
|
|
@ -201,7 +201,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(now);
|
||||
|
||||
// Not timed out
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(logger =>
|
||||
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Never);
|
||||
|
||||
|
|
@ -211,7 +211,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(now);
|
||||
|
||||
// Not timed out
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(logger =>
|
||||
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Never);
|
||||
|
||||
|
|
@ -221,7 +221,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(now);
|
||||
|
||||
// Not timed out
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(logger =>
|
||||
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Never);
|
||||
|
||||
|
|
@ -231,7 +231,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(now);
|
||||
|
||||
// Timed out
|
||||
Assert.True(_frameConnection.TimedOut);
|
||||
Assert.True(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(logger =>
|
||||
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), bytesPerSecond), Times.Once);
|
||||
}
|
||||
|
|
@ -274,7 +274,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
// Not timed out
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(
|
||||
logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
|
||||
Times.Never);
|
||||
|
|
@ -288,7 +288,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
// Not timed out
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(
|
||||
logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
|
||||
Times.Never);
|
||||
|
|
@ -298,7 +298,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
// Timed out
|
||||
Assert.True(_frameConnection.TimedOut);
|
||||
Assert.True(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(
|
||||
logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
|
||||
Times.Once);
|
||||
|
|
@ -330,7 +330,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
// Not timed out
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(
|
||||
logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
|
||||
Times.Never);
|
||||
|
|
@ -349,7 +349,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
// Not timed out
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(
|
||||
logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
|
||||
Times.Never);
|
||||
|
|
@ -359,7 +359,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
// Timed out
|
||||
Assert.True(_frameConnection.TimedOut);
|
||||
Assert.True(_frameConnection.RequestTimedOut);
|
||||
mockLogger.Verify(
|
||||
logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
|
||||
Times.Once);
|
||||
|
|
@ -388,7 +388,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
_frameConnection.StartTimingReads();
|
||||
|
||||
_frameConnection.SetTimeout(timeout.Ticks, TimeoutAction.CloseConnection);
|
||||
_frameConnection.SetTimeout(timeout.Ticks, TimeoutAction.StopProcessingNextRequest);
|
||||
|
||||
// Tick beyond grace period with low data rate
|
||||
systemClock.UtcNow += TimeSpan.FromSeconds(3);
|
||||
|
|
@ -396,14 +396,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
// Not timed out
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
|
||||
// Tick just past timeout period, adjusted by Heartbeat.Interval
|
||||
systemClock.UtcNow = startTime + timeout + Heartbeat.Interval + TimeSpan.FromTicks(1);
|
||||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
// Timed out
|
||||
Assert.True(_frameConnection.TimedOut);
|
||||
Assert.True(_frameConnection.RequestTimedOut);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -436,7 +436,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
systemClock.UtcNow += TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromTicks(1);
|
||||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
Assert.True(_frameConnection.TimedOut);
|
||||
Assert.True(_frameConnection.RequestTimedOut);
|
||||
Assert.True(aborted.Wait(TimeSpan.FromSeconds(10)));
|
||||
}
|
||||
|
||||
|
|
@ -472,13 +472,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
// Still within grace period, not timed out
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
|
||||
// Tick just past grace period (adjusted by Heartbeat.Interval)
|
||||
systemClock.UtcNow = startTime + minResponseDataRate.GracePeriod + Heartbeat.Interval + TimeSpan.FromTicks(1);
|
||||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
Assert.True(_frameConnection.TimedOut);
|
||||
Assert.True(_frameConnection.RequestTimedOut);
|
||||
Assert.True(aborted.Wait(TimeSpan.FromSeconds(10)));
|
||||
}
|
||||
|
||||
|
|
@ -516,7 +516,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
// Not timed out because the timeout was pushed by the second write
|
||||
Assert.False(_frameConnection.TimedOut);
|
||||
Assert.False(_frameConnection.RequestTimedOut);
|
||||
|
||||
// Complete the first write, this should have no effect on the timeout
|
||||
_frameConnection.StopTimingWrite();
|
||||
|
|
@ -525,7 +525,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
systemClock.UtcNow += TimeSpan.FromSeconds(3) + TimeSpan.FromTicks(1);
|
||||
_frameConnection.Tick(systemClock.UtcNow);
|
||||
|
||||
Assert.True(_frameConnection.TimedOut);
|
||||
Assert.True(_frameConnection.RequestTimedOut);
|
||||
Assert.True(aborted.Wait(TimeSpan.FromSeconds(10)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -511,9 +511,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var requestProcessingTask = _frame.ProcessRequestsAsync();
|
||||
|
||||
var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout.Ticks;
|
||||
_timeoutControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection));
|
||||
_timeoutControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.StopProcessingNextRequest));
|
||||
|
||||
_frame.Stop();
|
||||
_frame.StopProcessingNextRequest();
|
||||
_application.Output.Complete();
|
||||
|
||||
requestProcessingTask.Wait();
|
||||
|
|
@ -598,7 +598,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n");
|
||||
await _application.Output.WriteAsync(data);
|
||||
|
||||
_frame.Stop();
|
||||
_frame.StopProcessingNextRequest();
|
||||
Assert.IsNotType<Task<Task>>(requestProcessingTask);
|
||||
|
||||
await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10));
|
||||
|
|
|
|||
|
|
@ -314,11 +314,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
TimeSpan.MaxValue,
|
||||
};
|
||||
|
||||
public static TheoryData<TimeSpan> TimeoutInfiniteData => new TheoryData<TimeSpan>
|
||||
{
|
||||
Timeout.InfiniteTimeSpan,
|
||||
};
|
||||
|
||||
public static TheoryData<TimeSpan> TimeoutInvalidData => new TheoryData<TimeSpan>
|
||||
{
|
||||
TimeSpan.MinValue,
|
||||
|
|
|
|||
|
|
@ -566,11 +566,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
Assert.Equal(1, await body.ReadAsync(new ArraySegment<byte>(new byte[1])));
|
||||
|
||||
// Time out on the next read
|
||||
mockTimeoutControl
|
||||
.Setup(timeoutControl => timeoutControl.TimedOut)
|
||||
.Returns(true);
|
||||
|
||||
input.Cancel();
|
||||
input.Frame.SendTimeoutResponse();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadHttpRequestException>(() => body.ReadAsync(new ArraySegment<byte>(new byte[1])));
|
||||
Assert.Equal(StatusCodes.Status408RequestTimeout, exception.StatusCode);
|
||||
|
|
@ -595,11 +591,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
Assert.Equal(1, await body.ReadAsync(new ArraySegment<byte>(new byte[1])));
|
||||
|
||||
// Time out on the next read
|
||||
mockTimeoutControl
|
||||
.Setup(timeoutControl => timeoutControl.TimedOut)
|
||||
.Returns(true);
|
||||
|
||||
input.Cancel();
|
||||
input.Frame.SendTimeoutResponse();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadHttpRequestException>(() => body.ConsumeAsync());
|
||||
Assert.Equal(StatusCodes.Status408RequestTimeout, exception.StatusCode);
|
||||
|
|
@ -624,11 +616,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
Assert.Equal(1, await body.ReadAsync(new ArraySegment<byte>(new byte[1])));
|
||||
|
||||
// Time out on the next read
|
||||
mockTimeoutControl
|
||||
.Setup(timeoutControl => timeoutControl.TimedOut)
|
||||
.Returns(true);
|
||||
|
||||
input.Cancel();
|
||||
input.Frame.SendTimeoutResponse();
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
// 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.Threading;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||
{
|
||||
public class HttpsConnectionAdapterOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void HandshakeTimeoutDefault()
|
||||
{
|
||||
Assert.Equal(TimeSpan.FromSeconds(10), new HttpsConnectionAdapterOptions().HandshakeTimeout);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TimeoutValidData))]
|
||||
public void HandshakeTimeoutValid(TimeSpan value)
|
||||
{
|
||||
Assert.Equal(value, new HttpsConnectionAdapterOptions { HandshakeTimeout = value }.HandshakeTimeout);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandshakeTimeoutCanBeSetToInfinite()
|
||||
{
|
||||
Assert.Equal(TimeSpan.MaxValue, new HttpsConnectionAdapterOptions { HandshakeTimeout = Timeout.InfiniteTimeSpan }.HandshakeTimeout);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TimeoutInvalidData))]
|
||||
public void HandshakeTimeoutInvalid(TimeSpan value)
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => new HttpsConnectionAdapterOptions { HandshakeTimeout = value });
|
||||
|
||||
Assert.Equal("value", exception.ParamName);
|
||||
Assert.StartsWith(HttpsStrings.PositiveTimeSpanRequired, exception.Message);
|
||||
}
|
||||
|
||||
public static TheoryData<TimeSpan> TimeoutValidData => new TheoryData<TimeSpan>
|
||||
{
|
||||
TimeSpan.FromTicks(1),
|
||||
TimeSpan.MaxValue,
|
||||
};
|
||||
|
||||
public static TheoryData<TimeSpan> TimeoutInvalidData => new TheoryData<TimeSpan>
|
||||
{
|
||||
TimeSpan.MinValue,
|
||||
TimeSpan.FromTicks(-1),
|
||||
TimeSpan.Zero
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -3,15 +3,18 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -258,6 +261,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandshakeTimesOutAndIsLoggedAsInformation()
|
||||
{
|
||||
var loggerProvider = new HandshakeErrorLoggerProvider();
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps(new HttpsConnectionAdapterOptions
|
||||
{
|
||||
ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"),
|
||||
HandshakeTimeout = TimeSpan.FromSeconds(1)
|
||||
});
|
||||
});
|
||||
})
|
||||
.ConfigureLogging(builder => builder.AddProvider(loggerProvider))
|
||||
.Configure(app => app.Run(httpContext => Task.CompletedTask));
|
||||
|
||||
using (var host = hostBuilder.Build())
|
||||
{
|
||||
host.Start();
|
||||
|
||||
using (var socket = await HttpClientSlim.GetSocket(new Uri($"https://127.0.0.1:{host.GetPort()}/")))
|
||||
using (var stream = new NetworkStream(socket, ownsSocket: false))
|
||||
{
|
||||
// No data should be sent and the connection should be closed in well under 30 seconds.
|
||||
Assert.Equal(0, await stream.ReadAsync(new byte[1], 0, 1).TimeoutAfter(TimeSpan.FromSeconds(30)));
|
||||
}
|
||||
}
|
||||
|
||||
await loggerProvider.FilterLogger.LogTcs.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
|
||||
Assert.Equal(2, loggerProvider.FilterLogger.LastEventId);
|
||||
Assert.Equal(LogLevel.Information, loggerProvider.FilterLogger.LastLogLevel);
|
||||
}
|
||||
|
||||
private class HandshakeErrorLoggerProvider : ILoggerProvider
|
||||
{
|
||||
public HttpsConnectionFilterLogger FilterLogger { get; } = new HttpsConnectionFilterLogger();
|
||||
|
|
|
|||
Loading…
Reference in New Issue