Merge branch 'release/2.2'
This commit is contained in:
commit
64769f2277
|
|
@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
ServiceContext = serviceContext,
|
ServiceContext = serviceContext,
|
||||||
ConnectionFeatures = new FeatureCollection(),
|
ConnectionFeatures = new FeatureCollection(),
|
||||||
MemoryPool = memoryPool,
|
MemoryPool = memoryPool,
|
||||||
TimeoutControl = new MockTimeoutControl(),
|
TimeoutControl = new TimeoutControl(timeoutHandler: null),
|
||||||
Transport = pair.Transport
|
Transport = pair.Transport
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
|
@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
ServiceContext = serviceContext,
|
ServiceContext = serviceContext,
|
||||||
ConnectionFeatures = new FeatureCollection(),
|
ConnectionFeatures = new FeatureCollection(),
|
||||||
MemoryPool = memoryPool,
|
MemoryPool = memoryPool,
|
||||||
TimeoutControl = new MockTimeoutControl(),
|
TimeoutControl = new TimeoutControl(timeoutHandler: null),
|
||||||
Transport = pair.Transport
|
Transport = pair.Transport
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks
|
|
||||||
{
|
|
||||||
public class MockTimeoutControl : ITimeoutControl
|
|
||||||
{
|
|
||||||
public void CancelTimeout()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResetTimeout(long ticks, TimeoutReason timeoutReason)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetTimeout(long ticks, TimeoutReason timeoutReason)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartTimingReads(MinDataRate minRate)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopTimingReads()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PauseTimingReads()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResumeTimingReads()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void BytesRead(long count)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartTimingWrite(MinDataRate rate, long size)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopTimingWrite()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
ConnectionFeatures = new FeatureCollection(),
|
ConnectionFeatures = new FeatureCollection(),
|
||||||
MemoryPool = _memoryPool,
|
MemoryPool = _memoryPool,
|
||||||
Transport = pair.Transport,
|
Transport = pair.Transport,
|
||||||
TimeoutControl = new MockTimeoutControl()
|
TimeoutControl = new TimeoutControl(timeoutHandler: null)
|
||||||
});
|
});
|
||||||
|
|
||||||
http1Connection.Reset();
|
http1Connection.Reset();
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
|
||||||
|
|
@ -132,7 +132,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
ServiceContext = serviceContext,
|
ServiceContext = serviceContext,
|
||||||
ConnectionFeatures = new FeatureCollection(),
|
ConnectionFeatures = new FeatureCollection(),
|
||||||
MemoryPool = _memoryPool,
|
MemoryPool = _memoryPool,
|
||||||
TimeoutControl = new MockTimeoutControl(),
|
TimeoutControl = new TimeoutControl(timeoutHandler: null),
|
||||||
Transport = _pair.Transport
|
Transport = _pair.Transport
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
Input.CancelPendingRead();
|
Input.CancelPendingRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void HandleRequestHeadersTimeout()
|
||||||
|
=> SendTimeoutResponse();
|
||||||
|
|
||||||
public void ParseRequest(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
|
public void ParseRequest(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
|
||||||
{
|
{
|
||||||
consumed = buffer.Start;
|
consumed = buffer.Start;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
|
|
@ -105,7 +106,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
public PipeReader Input => _context.Transport.Input;
|
public PipeReader Input => _context.Transport.Input;
|
||||||
public IKestrelTrace Log => _context.ServiceContext.Log;
|
public IKestrelTrace Log => _context.ServiceContext.Log;
|
||||||
public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures;
|
public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures;
|
||||||
public KestrelServerOptions ServerOptions => _context.ServiceContext.ServerOptions;
|
public ITimeoutControl TimeoutControl => _context.TimeoutControl;
|
||||||
|
public KestrelServerLimits Limits => _context.ServiceContext.ServerOptions.Limits;
|
||||||
|
|
||||||
internal Http2PeerSettings ServerSettings => _serverSettings;
|
internal Http2PeerSettings ServerSettings => _serverSettings;
|
||||||
|
|
||||||
|
|
@ -139,6 +141,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
public void StopProcessingNextRequest()
|
public void StopProcessingNextRequest()
|
||||||
=> StopProcessingNextRequest(true);
|
=> StopProcessingNextRequest(true);
|
||||||
|
|
||||||
|
public void HandleRequestHeadersTimeout()
|
||||||
|
{
|
||||||
|
Log.ConnectionBadRequest(ConnectionId, BadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout));
|
||||||
|
Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout));
|
||||||
|
}
|
||||||
|
|
||||||
public void StopProcessingNextRequest(bool sendGracefulGoAway = false)
|
public void StopProcessingNextRequest(bool sendGracefulGoAway = false)
|
||||||
{
|
{
|
||||||
lock (_stateLock)
|
lock (_stateLock)
|
||||||
|
|
@ -174,6 +182,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ValidateTlsRequirements();
|
ValidateTlsRequirements();
|
||||||
|
TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive);
|
||||||
|
|
||||||
if (!await TryReadPrefaceAsync())
|
if (!await TryReadPrefaceAsync())
|
||||||
{
|
{
|
||||||
|
|
@ -298,7 +307,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
await _streamsCompleted.Task;
|
await _streamsCompleted.Task;
|
||||||
|
|
||||||
_context.TimeoutControl.StartDrainTimeout(ServerOptions.Limits.MinResponseDataRate, ServerOptions.Limits.MaxResponseBufferSize);
|
TimeoutControl.StartDrainTimeout(Limits.MinResponseDataRate, Limits.MaxResponseBufferSize);
|
||||||
|
|
||||||
_frameWriter.Complete();
|
_frameWriter.Complete();
|
||||||
}
|
}
|
||||||
|
|
@ -551,30 +560,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Start a new stream
|
// Cancel keep-alive timeout and start header timeout if necessary. The keep-alive timeout can be
|
||||||
_currentHeadersStream = new Http2Stream(new Http2StreamContext
|
// started on another thread so the lock is necessary.
|
||||||
|
lock (_stateLock)
|
||||||
{
|
{
|
||||||
ConnectionId = ConnectionId,
|
if (TimeoutControl.TimerReason != TimeoutReason.None)
|
||||||
StreamId = _incomingFrame.StreamId,
|
{
|
||||||
ServiceContext = _context.ServiceContext,
|
Debug.Assert(TimeoutControl.TimerReason == TimeoutReason.KeepAlive, "Non keep-alive timeout set at start of stream.");
|
||||||
ConnectionFeatures = _context.ConnectionFeatures,
|
TimeoutControl.CancelTimeout();
|
||||||
MemoryPool = _context.MemoryPool,
|
}
|
||||||
LocalEndPoint = _context.LocalEndPoint,
|
|
||||||
RemoteEndPoint = _context.RemoteEndPoint,
|
|
||||||
StreamLifetimeHandler = this,
|
|
||||||
ClientPeerSettings = _clientSettings,
|
|
||||||
ServerPeerSettings = _serverSettings,
|
|
||||||
FrameWriter = _frameWriter,
|
|
||||||
ConnectionInputFlowControl = _inputFlowControl,
|
|
||||||
ConnectionOutputFlowControl = _outputFlowControl,
|
|
||||||
TimeoutControl = _context.TimeoutControl,
|
|
||||||
});
|
|
||||||
|
|
||||||
_currentHeadersStream.Reset();
|
if (!_incomingFrame.HeadersEndHeaders)
|
||||||
_headerFlags = _incomingFrame.HeadersFlags;
|
{
|
||||||
|
TimeoutControl.SetTimeout(Limits.RequestHeadersTimeout.Ticks, TimeoutReason.RequestHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding
|
// Start a new stream
|
||||||
return DecodeHeadersAsync(application, _incomingFrame.HeadersEndHeaders, headersPayload);
|
_currentHeadersStream = new Http2Stream(new Http2StreamContext
|
||||||
|
{
|
||||||
|
ConnectionId = ConnectionId,
|
||||||
|
StreamId = _incomingFrame.StreamId,
|
||||||
|
ServiceContext = _context.ServiceContext,
|
||||||
|
ConnectionFeatures = _context.ConnectionFeatures,
|
||||||
|
MemoryPool = _context.MemoryPool,
|
||||||
|
LocalEndPoint = _context.LocalEndPoint,
|
||||||
|
RemoteEndPoint = _context.RemoteEndPoint,
|
||||||
|
StreamLifetimeHandler = this,
|
||||||
|
ClientPeerSettings = _clientSettings,
|
||||||
|
ServerPeerSettings = _serverSettings,
|
||||||
|
FrameWriter = _frameWriter,
|
||||||
|
ConnectionInputFlowControl = _inputFlowControl,
|
||||||
|
ConnectionOutputFlowControl = _outputFlowControl,
|
||||||
|
TimeoutControl = TimeoutControl,
|
||||||
|
});
|
||||||
|
|
||||||
|
_currentHeadersStream.Reset();
|
||||||
|
_headerFlags = _incomingFrame.HeadersFlags;
|
||||||
|
|
||||||
|
var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding
|
||||||
|
return DecodeHeadersAsync(application, _incomingFrame.HeadersEndHeaders, headersPayload);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -835,7 +860,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return DecodeHeadersAsync(application, _incomingFrame.ContinuationEndHeaders, payload);
|
lock (_stateLock)
|
||||||
|
{
|
||||||
|
Debug.Assert(TimeoutControl.TimerReason == TimeoutReason.RequestHeaders, "Received continuation frame without request header timeout being set.");
|
||||||
|
|
||||||
|
if (_incomingFrame.HeadersEndHeaders)
|
||||||
|
{
|
||||||
|
TimeoutControl.CancelTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecodeHeadersAsync(application, _incomingFrame.ContinuationEndHeaders, payload);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -849,24 +884,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is always called with the _stateLock acquired.
|
||||||
private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application, bool endHeaders, ReadOnlySequence<byte> payload)
|
private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application, bool endHeaders, ReadOnlySequence<byte> payload)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
lock (_stateLock)
|
_highestOpenedStreamId = _currentHeadersStream.StreamId;
|
||||||
|
_hpackDecoder.Decode(payload, endHeaders, handler: this);
|
||||||
|
|
||||||
|
if (endHeaders)
|
||||||
{
|
{
|
||||||
_highestOpenedStreamId = _currentHeadersStream.StreamId;
|
if (_state != Http2ConnectionState.Closed)
|
||||||
_hpackDecoder.Decode(payload, endHeaders, handler: this);
|
|
||||||
|
|
||||||
if (endHeaders)
|
|
||||||
{
|
{
|
||||||
if (_state != Http2ConnectionState.Closed)
|
StartStream(application);
|
||||||
{
|
|
||||||
StartStream(application);
|
|
||||||
}
|
|
||||||
|
|
||||||
ResetRequestHeaderParsingState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResetRequestHeaderParsingState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Http2StreamErrorException)
|
catch (Http2StreamErrorException)
|
||||||
|
|
@ -979,7 +1012,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
Input.CancelPendingRead();
|
Input.CancelPendingRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_state != Http2ConnectionState.Open)
|
if (_state == Http2ConnectionState.Open)
|
||||||
|
{
|
||||||
|
// If we're awaiting headers, either a new stream will be started, or there will be a connection
|
||||||
|
// error possibly due to a request header timeout, so no need to start a keep-alive timeout.
|
||||||
|
if (TimeoutControl.TimerReason != TimeoutReason.RequestHeaders)
|
||||||
|
{
|
||||||
|
TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// Complete the task waiting on all streams to finish
|
// Complete the task waiting on all streams to finish
|
||||||
_streamsCompleted.TrySetResult(null);
|
_streamsCompleted.TrySetResult(null);
|
||||||
|
|
@ -1164,6 +1206,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
}
|
}
|
||||||
else if (state == Http2ConnectionState.Closed)
|
else if (state == Http2ConnectionState.Closed)
|
||||||
{
|
{
|
||||||
|
// This cancels keep-alive and request header timeouts, but not the response drain timeout.
|
||||||
|
TimeoutControl.CancelTimeout();
|
||||||
Log.Http2ConnectionClosed(_context.ConnectionId, _highestOpenedStreamId);
|
Log.Http2ConnectionClosed(_context.ConnectionId, _highestOpenedStreamId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
_timeoutControl = new TimeoutControl(this);
|
_timeoutControl = new TimeoutControl(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For testing
|
|
||||||
internal HttpProtocol Http1Connection => _http1Connection;
|
|
||||||
internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton;
|
|
||||||
|
|
||||||
public string ConnectionId => _context.ConnectionId;
|
public string ConnectionId => _context.ConnectionId;
|
||||||
public IPEndPoint LocalEndPoint => _context.LocalEndPoint;
|
public IPEndPoint LocalEndPoint => _context.LocalEndPoint;
|
||||||
public IPEndPoint RemoteEndPoint => _context.RemoteEndPoint;
|
public IPEndPoint RemoteEndPoint => _context.RemoteEndPoint;
|
||||||
|
|
@ -196,9 +192,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
// For testing only
|
// For testing only
|
||||||
internal void Initialize(IDuplexPipe transport)
|
internal void Initialize(IRequestProcessor requestProcessor)
|
||||||
{
|
{
|
||||||
_requestProcessor = _http1Connection = new Http1Connection(CreateDerivedContext(transport));
|
_requestProcessor = requestProcessor;
|
||||||
|
_http1Connection = requestProcessor as Http1Connection;
|
||||||
_protocolSelectionState = ProtocolSelectionState.Selected;
|
_protocolSelectionState = ProtocolSelectionState.Selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,17 +376,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
switch (reason)
|
switch (reason)
|
||||||
{
|
{
|
||||||
case TimeoutReason.KeepAlive:
|
case TimeoutReason.KeepAlive:
|
||||||
_http1Connection.StopProcessingNextRequest();
|
_requestProcessor.StopProcessingNextRequest();
|
||||||
break;
|
break;
|
||||||
case TimeoutReason.RequestHeaders:
|
case TimeoutReason.RequestHeaders:
|
||||||
_http1Connection.SendTimeoutResponse();
|
_requestProcessor.HandleRequestHeadersTimeout();
|
||||||
break;
|
break;
|
||||||
case TimeoutReason.ReadDataRate:
|
case TimeoutReason.ReadDataRate:
|
||||||
Log.RequestBodyMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection.TraceIdentifier, _http1Connection.MinRequestBodyDataRate.BytesPerSecond);
|
Log.RequestBodyMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection.TraceIdentifier, _http1Connection.MinRequestBodyDataRate.BytesPerSecond);
|
||||||
_http1Connection.SendTimeoutResponse();
|
_http1Connection.SendTimeoutResponse();
|
||||||
break;
|
break;
|
||||||
case TimeoutReason.WriteDataRate:
|
case TimeoutReason.WriteDataRate:
|
||||||
Log.ResponseMinimumDataRateNotSatisfied(_http1Connection.ConnectionIdFeature, _http1Connection.TraceIdentifier);
|
Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection?.TraceIdentifier);
|
||||||
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied));
|
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied));
|
||||||
break;
|
break;
|
||||||
case TimeoutReason.RequestBodyDrain:
|
case TimeoutReason.RequestBodyDrain:
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
{
|
{
|
||||||
Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application);
|
Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application);
|
||||||
void StopProcessingNextRequest();
|
void StopProcessingNextRequest();
|
||||||
|
void HandleRequestHeadersTimeout();
|
||||||
void OnInputOrOutputCompleted();
|
void OnInputOrOutputCompleted();
|
||||||
void Abort(ConnectionAbortedException ex);
|
void Abort(ConnectionAbortedException ex);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
public interface ITimeoutControl
|
public interface ITimeoutControl
|
||||||
{
|
{
|
||||||
|
TimeoutReason TimerReason { get; }
|
||||||
|
|
||||||
void SetTimeout(long ticks, TimeoutReason timeoutReason);
|
void SetTimeout(long ticks, TimeoutReason timeoutReason);
|
||||||
void ResetTimeout(long ticks, TimeoutReason timeoutReason);
|
void ResetTimeout(long ticks, TimeoutReason timeoutReason);
|
||||||
void CancelTimeout();
|
void CancelTimeout();
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
|
|
||||||
private long _lastTimestamp;
|
private long _lastTimestamp;
|
||||||
private long _timeoutTimestamp = long.MaxValue;
|
private long _timeoutTimestamp = long.MaxValue;
|
||||||
private TimeoutReason _timeoutReason;
|
|
||||||
|
|
||||||
private readonly object _readTimingLock = new object();
|
private readonly object _readTimingLock = new object();
|
||||||
private MinDataRate _minReadRate;
|
private MinDataRate _minReadRate;
|
||||||
|
|
@ -32,6 +31,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
_timeoutHandler = timeoutHandler;
|
_timeoutHandler = timeoutHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TimeoutReason TimerReason { get; private set; }
|
||||||
|
|
||||||
internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton;
|
internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton;
|
||||||
|
|
||||||
public void Initialize(DateTimeOffset now)
|
public void Initialize(DateTimeOffset now)
|
||||||
|
|
@ -56,9 +57,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
if (timestamp > Interlocked.Read(ref _timeoutTimestamp))
|
if (timestamp > Interlocked.Read(ref _timeoutTimestamp))
|
||||||
{
|
{
|
||||||
|
var timeoutReason = TimerReason;
|
||||||
|
|
||||||
CancelTimeout();
|
CancelTimeout();
|
||||||
|
|
||||||
_timeoutHandler.OnTimeout(_timeoutReason);
|
_timeoutHandler.OnTimeout(timeoutReason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +120,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
|
|
||||||
public void SetTimeout(long ticks, TimeoutReason timeoutReason)
|
public void SetTimeout(long ticks, TimeoutReason timeoutReason)
|
||||||
{
|
{
|
||||||
Debug.Assert(_timeoutTimestamp == long.MaxValue, "Concurrent timeouts are not supported");
|
Debug.Assert(_timeoutTimestamp == long.MaxValue, "Concurrent timeouts are not supported.");
|
||||||
|
|
||||||
AssignTimeout(ticks, timeoutReason);
|
AssignTimeout(ticks, timeoutReason);
|
||||||
}
|
}
|
||||||
|
|
@ -130,11 +133,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
public void CancelTimeout()
|
public void CancelTimeout()
|
||||||
{
|
{
|
||||||
Interlocked.Exchange(ref _timeoutTimestamp, long.MaxValue);
|
Interlocked.Exchange(ref _timeoutTimestamp, long.MaxValue);
|
||||||
|
|
||||||
|
TimerReason = TimeoutReason.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AssignTimeout(long ticks, TimeoutReason timeoutReason)
|
private void AssignTimeout(long ticks, TimeoutReason timeoutReason)
|
||||||
{
|
{
|
||||||
_timeoutReason = timeoutReason;
|
TimerReason = timeoutReason;
|
||||||
|
|
||||||
// Add Heartbeat.Interval since this can be called right before the next heartbeat.
|
// Add Heartbeat.Interval since this can be called right before the next heartbeat.
|
||||||
Interlocked.Exchange(ref _timeoutTimestamp, _lastTimestamp + ticks + Heartbeat.Interval.Ticks);
|
Interlocked.Exchange(ref _timeoutTimestamp, _lastTimestamp + ticks + Heartbeat.Interval.Ticks);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
public enum TimeoutReason
|
public enum TimeoutReason
|
||||||
{
|
{
|
||||||
|
None,
|
||||||
KeepAlive,
|
KeepAlive,
|
||||||
RequestHeaders,
|
RequestHeaders,
|
||||||
ReadDataRate,
|
ReadDataRate,
|
||||||
|
|
@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
|
|
||||||
void ConnectionReadFin(string connectionId);
|
void ConnectionReadFin(string connectionId);
|
||||||
|
|
||||||
void ConnectionWriteFin(string connectionId);
|
void ConnectionWriteFin(string connectionId, string reason);
|
||||||
|
|
||||||
void ConnectionWrite(string connectionId, int count);
|
void ConnectionWrite(string connectionId, int count);
|
||||||
|
|
||||||
|
|
@ -25,7 +25,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
void ConnectionPause(string connectionId);
|
void ConnectionPause(string connectionId);
|
||||||
|
|
||||||
void ConnectionResume(string connectionId);
|
void ConnectionResume(string connectionId);
|
||||||
|
|
||||||
void ConnectionAborted(string connectionId, string message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
inputError = inputError ?? _abortReason ?? new ConnectionAbortedException("The libuv transport's send loop completed gracefully.");
|
||||||
|
|
||||||
// Now, complete the input so that no more reads can happen
|
// Now, complete the input so that no more reads can happen
|
||||||
Input.Complete(inputError ?? _abortReason ?? new ConnectionAbortedException("The libuv transport's send loop completed gracefully."));
|
Input.Complete(inputError);
|
||||||
Output.Complete(outputError);
|
Output.Complete(outputError);
|
||||||
|
|
||||||
// Make sure it isn't possible for a paused read to resume reading after calling uv_close
|
// Make sure it isn't possible for a paused read to resume reading after calling uv_close
|
||||||
|
|
@ -104,7 +106,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
Input.CancelPendingFlush();
|
Input.CancelPendingFlush();
|
||||||
|
|
||||||
// Send a FIN
|
// Send a FIN
|
||||||
Log.ConnectionWriteFin(ConnectionId);
|
Log.ConnectionWriteFin(ConnectionId, inputError.Message);
|
||||||
|
|
||||||
// We're done with the socket now
|
// We're done with the socket now
|
||||||
_socket.Dispose();
|
_socket.Dispose();
|
||||||
|
|
@ -119,8 +121,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
|
|
||||||
public override void Abort(ConnectionAbortedException abortReason)
|
public override void Abort(ConnectionAbortedException abortReason)
|
||||||
{
|
{
|
||||||
Log.ConnectionAborted(ConnectionId, abortReason?.Message);
|
|
||||||
|
|
||||||
_abortReason = abortReason;
|
_abortReason = abortReason;
|
||||||
|
|
||||||
// Cancel WriteOutputAsync loop after setting _abortReason.
|
// Cancel WriteOutputAsync loop after setting _abortReason.
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
private static readonly Action<ILogger, string, Exception> _connectionReadFin =
|
private static readonly Action<ILogger, string, Exception> _connectionReadFin =
|
||||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, nameof(ConnectionReadFin)), @"Connection id ""{ConnectionId}"" received FIN.");
|
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, nameof(ConnectionReadFin)), @"Connection id ""{ConnectionId}"" received FIN.");
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, Exception> _connectionWriteFin =
|
private static readonly Action<ILogger, string, string, Exception> _connectionWriteFin =
|
||||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, nameof(ConnectionWriteFin)), @"Connection id ""{ConnectionId}"" sending FIN.");
|
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(7, nameof(ConnectionWriteFin)), @"Connection id ""{ConnectionId}"" sending FIN because: ""{Reason}""");
|
||||||
|
|
||||||
// ConnectionWrite: Reserved: 11
|
// ConnectionWrite: Reserved: 11
|
||||||
|
|
||||||
|
|
@ -32,9 +32,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
private static readonly Action<ILogger, string, Exception> _connectionReset =
|
private static readonly Action<ILogger, string, Exception> _connectionReset =
|
||||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(19, nameof(ConnectionReset)), @"Connection id ""{ConnectionId}"" reset.");
|
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(19, nameof(ConnectionReset)), @"Connection id ""{ConnectionId}"" reset.");
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, string, Exception> _connectionAborted =
|
|
||||||
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(20, nameof(ConnectionAborted)), @"Connection id ""{ConnectionId}"" closing because: ""{Message}""");
|
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public LibuvTrace(ILogger logger)
|
public LibuvTrace(ILogger logger)
|
||||||
|
|
@ -53,9 +50,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
_connectionReadFin(_logger, connectionId, null);
|
_connectionReadFin(_logger, connectionId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectionWriteFin(string connectionId)
|
public void ConnectionWriteFin(string connectionId, string reason)
|
||||||
{
|
{
|
||||||
_connectionWriteFin(_logger, connectionId, null);
|
_connectionWriteFin(_logger, connectionId, reason, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectionWrite(string connectionId, int count)
|
public void ConnectionWrite(string connectionId, int count)
|
||||||
|
|
@ -90,11 +87,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
_connectionResume(_logger, connectionId, null);
|
_connectionResume(_logger, connectionId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectionAborted(string connectionId, string message)
|
|
||||||
{
|
|
||||||
_connectionAborted(_logger, connectionId, message, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDisposable BeginScope<TState>(TState state) => _logger.BeginScope(state);
|
public IDisposable BeginScope<TState>(TState state) => _logger.BeginScope(state);
|
||||||
|
|
||||||
public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel);
|
public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
||||||
{
|
{
|
||||||
void ConnectionReadFin(string connectionId);
|
void ConnectionReadFin(string connectionId);
|
||||||
|
|
||||||
void ConnectionWriteFin(string connectionId);
|
void ConnectionWriteFin(string connectionId, string reason);
|
||||||
|
|
||||||
void ConnectionError(string connectionId, Exception ex);
|
void ConnectionError(string connectionId, Exception ex);
|
||||||
|
|
||||||
|
|
@ -19,7 +19,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
||||||
void ConnectionPause(string connectionId);
|
void ConnectionPause(string connectionId);
|
||||||
|
|
||||||
void ConnectionResume(string connectionId);
|
void ConnectionResume(string connectionId);
|
||||||
|
|
||||||
void ConnectionAborted(string connectionId, string message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
||||||
|
|
||||||
public override void Abort(ConnectionAbortedException abortReason)
|
public override void Abort(ConnectionAbortedException abortReason)
|
||||||
{
|
{
|
||||||
_trace.ConnectionAborted(ConnectionId, abortReason?.Message);
|
|
||||||
|
|
||||||
// Try to gracefully close the socket to match libuv behavior.
|
// Try to gracefully close the socket to match libuv behavior.
|
||||||
Shutdown(abortReason);
|
Shutdown(abortReason);
|
||||||
|
|
||||||
|
|
@ -290,7 +288,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
||||||
// to half close the connection which is currently unsupported.
|
// to half close the connection which is currently unsupported.
|
||||||
_shutdownReason = shutdownReason ?? new ConnectionAbortedException("The Socket transport's send loop completed gracefully.");
|
_shutdownReason = shutdownReason ?? new ConnectionAbortedException("The Socket transport's send loop completed gracefully.");
|
||||||
|
|
||||||
_trace.ConnectionWriteFin(ConnectionId);
|
_trace.ConnectionWriteFin(ConnectionId, _shutdownReason.Message);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
||||||
private static readonly Action<ILogger, string, Exception> _connectionReadFin =
|
private static readonly Action<ILogger, string, Exception> _connectionReadFin =
|
||||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, nameof(ConnectionReadFin)), @"Connection id ""{ConnectionId}"" received FIN.");
|
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, nameof(ConnectionReadFin)), @"Connection id ""{ConnectionId}"" received FIN.");
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, Exception> _connectionWriteFin =
|
private static readonly Action<ILogger, string, string, Exception> _connectionWriteFin =
|
||||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, nameof(ConnectionWriteFin)), @"Connection id ""{ConnectionId}"" sending FIN.");
|
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(7, nameof(ConnectionWriteFin)), @"Connection id ""{ConnectionId}"" sending FIN because: ""{Reason}""");
|
||||||
|
|
||||||
// ConnectionWrite: Reserved: 11
|
// ConnectionWrite: Reserved: 11
|
||||||
|
|
||||||
|
|
@ -32,9 +32,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
||||||
private static readonly Action<ILogger, string, Exception> _connectionReset =
|
private static readonly Action<ILogger, string, Exception> _connectionReset =
|
||||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(19, nameof(ConnectionReset)), @"Connection id ""{ConnectionId}"" reset.");
|
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(19, nameof(ConnectionReset)), @"Connection id ""{ConnectionId}"" reset.");
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, string, Exception> _connectionAborted =
|
|
||||||
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(20, nameof(ConnectionAborted)), @"Connection id ""{ConnectionId}"" closing because: ""{Message}""");
|
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public SocketsTrace(ILogger logger)
|
public SocketsTrace(ILogger logger)
|
||||||
|
|
@ -53,9 +50,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
||||||
_connectionReadFin(_logger, connectionId, null);
|
_connectionReadFin(_logger, connectionId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectionWriteFin(string connectionId)
|
public void ConnectionWriteFin(string connectionId, string reason)
|
||||||
{
|
{
|
||||||
_connectionWriteFin(_logger, connectionId, null);
|
_connectionWriteFin(_logger, connectionId, reason, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectionWrite(string connectionId, int count)
|
public void ConnectionWrite(string connectionId, int count)
|
||||||
|
|
@ -90,11 +87,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
||||||
_connectionResume(_logger, connectionId, null);
|
_connectionResume(_logger, connectionId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectionAborted(string connectionId, string message)
|
|
||||||
{
|
|
||||||
_connectionAborted(_logger, connectionId, message, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDisposable BeginScope<TState>(TState state) => _logger.BeginScope(state);
|
public IDisposable BeginScope<TState>(TState state) => _logger.BeginScope(state);
|
||||||
|
|
||||||
public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel);
|
public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.IO.Pipelines;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -29,10 +30,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
var httpConnection = new HttpConnection(httpConnectionContext);
|
var httpConnection = new HttpConnection(httpConnectionContext);
|
||||||
|
|
||||||
var aborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var aborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
var http1Connection = new Http1Connection(httpConnectionContext);
|
||||||
|
|
||||||
httpConnection.Initialize(httpConnectionContext.Transport);
|
httpConnection.Initialize(http1Connection);
|
||||||
httpConnection.Http1Connection.Reset();
|
http1Connection.Reset();
|
||||||
httpConnection.Http1Connection.RequestAborted.Register(() =>
|
http1Connection.RequestAborted.Register(() =>
|
||||||
{
|
{
|
||||||
aborted.SetResult(null);
|
aborted.SetResult(null);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,9 @@ using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
@ -2971,34 +2969,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1));
|
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GOAWAY_Received_ConnectionClosedWhenResponseNotDrainedAtMinimumDataRate()
|
|
||||||
{
|
|
||||||
var mockSystemClock = new MockSystemClock();
|
|
||||||
var limits = _connectionContext.ServiceContext.ServerOptions.Limits;
|
|
||||||
var timeoutControl = _connectionContext.TimeoutControl;
|
|
||||||
|
|
||||||
_timeoutControl.Initialize(mockSystemClock.UtcNow);
|
|
||||||
|
|
||||||
await InitializeConnectionAsync(_noopApplication);
|
|
||||||
|
|
||||||
await SendGoAwayAsync();
|
|
||||||
|
|
||||||
await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
|
|
||||||
|
|
||||||
mockSystemClock.UtcNow +=
|
|
||||||
Heartbeat.Interval +
|
|
||||||
TimeSpan.FromSeconds(limits.MaxResponseBufferSize.Value * 2 / limits.MinResponseDataRate.BytesPerSecond);
|
|
||||||
_timeoutControl.Tick(mockSystemClock.UtcNow);
|
|
||||||
|
|
||||||
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
|
|
||||||
|
|
||||||
mockSystemClock.UtcNow += TimeSpan.FromTicks(1);
|
|
||||||
_timeoutControl.Tick(mockSystemClock.UtcNow);
|
|
||||||
|
|
||||||
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task WINDOW_UPDATE_Received_StreamIdEven_ConnectionError()
|
public async Task WINDOW_UPDATE_Received_StreamIdEven_ConnectionError()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
protected readonly HPackDecoder _hpackDecoder;
|
protected readonly HPackDecoder _hpackDecoder;
|
||||||
private readonly byte[] _headerEncodingBuffer = new byte[Http2PeerSettings.MinAllowedMaxFrameSize];
|
private readonly byte[] _headerEncodingBuffer = new byte[Http2PeerSettings.MinAllowedMaxFrameSize];
|
||||||
|
|
||||||
protected readonly Mock<ITimeoutHandler> _mockTimeoutHandler = new Mock<ITimeoutHandler>();
|
|
||||||
protected readonly TimeoutControl _timeoutControl;
|
protected readonly TimeoutControl _timeoutControl;
|
||||||
|
protected readonly Mock<ConnectionContext> _mockConnectionContext = new Mock<ConnectionContext>();
|
||||||
|
protected readonly Mock<ITimeoutHandler> _mockTimeoutHandler = new Mock<ITimeoutHandler>();
|
||||||
|
protected readonly Mock<MockTimeoutControlBase> _mockTimeoutControl;
|
||||||
|
|
||||||
protected readonly ConcurrentDictionary<int, TaskCompletionSource<object>> _runningStreams = new ConcurrentDictionary<int, TaskCompletionSource<object>>();
|
protected readonly ConcurrentDictionary<int, TaskCompletionSource<object>> _runningStreams = new ConcurrentDictionary<int, TaskCompletionSource<object>>();
|
||||||
protected readonly Dictionary<string, string> _receivedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
protected readonly Dictionary<string, string> _receivedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
@ -105,7 +107,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
|
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
|
||||||
_hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize);
|
_hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize);
|
||||||
|
|
||||||
_timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object);
|
_timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object);
|
||||||
|
_mockTimeoutControl = new Mock<MockTimeoutControlBase>(_timeoutControl) { CallBase = true };
|
||||||
|
|
||||||
_noopApplication = context => Task.CompletedTask;
|
_noopApplication = context => Task.CompletedTask;
|
||||||
|
|
||||||
|
|
@ -289,15 +293,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
_connectionContext = new HttpConnectionContext
|
_connectionContext = new HttpConnectionContext
|
||||||
{
|
{
|
||||||
ConnectionContext = Mock.Of<ConnectionContext>(),
|
ConnectionContext = _mockConnectionContext.Object,
|
||||||
ConnectionFeatures = new FeatureCollection(),
|
ConnectionFeatures = new FeatureCollection(),
|
||||||
ServiceContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object),
|
ServiceContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object),
|
||||||
MemoryPool = _memoryPool,
|
MemoryPool = _memoryPool,
|
||||||
Transport = _pair.Transport,
|
Transport = _pair.Transport,
|
||||||
TimeoutControl = _timeoutControl
|
TimeoutControl = _mockTimeoutControl.Object
|
||||||
};
|
};
|
||||||
|
|
||||||
_connection = new Http2Connection(_connectionContext);
|
_connection = new Http2Connection(_connectionContext);
|
||||||
|
|
||||||
|
var httpConnection = new HttpConnection(_connectionContext);
|
||||||
|
httpConnection.Initialize(_connection);
|
||||||
|
_mockTimeoutHandler.Setup(h => h.OnTimeout(It.IsAny<TimeoutReason>()))
|
||||||
|
.Callback<TimeoutReason>(r => httpConnection.OnTimeout(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
|
|
@ -1065,5 +1074,69 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
public ReadOnlySequence<byte> PayloadSequence => new ReadOnlySequence<byte>(Payload);
|
public ReadOnlySequence<byte> PayloadSequence => new ReadOnlySequence<byte>(Payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class MockTimeoutControlBase : ITimeoutControl
|
||||||
|
{
|
||||||
|
private readonly ITimeoutControl _realTimeoutControl;
|
||||||
|
|
||||||
|
public MockTimeoutControlBase(ITimeoutControl realTimeoutControl)
|
||||||
|
{
|
||||||
|
_realTimeoutControl = realTimeoutControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual TimeoutReason TimerReason => _realTimeoutControl.TimerReason;
|
||||||
|
|
||||||
|
public virtual void SetTimeout(long ticks, TimeoutReason timeoutReason)
|
||||||
|
{
|
||||||
|
_realTimeoutControl.SetTimeout(ticks, timeoutReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void ResetTimeout(long ticks, TimeoutReason timeoutReason)
|
||||||
|
{
|
||||||
|
_realTimeoutControl.ResetTimeout(ticks, timeoutReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void CancelTimeout()
|
||||||
|
{
|
||||||
|
_realTimeoutControl.CancelTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public virtual void StartTimingReads(MinDataRate minRate)
|
||||||
|
{
|
||||||
|
_realTimeoutControl.StartTimingReads(minRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void PauseTimingReads()
|
||||||
|
{
|
||||||
|
_realTimeoutControl.PauseTimingReads();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void ResumeTimingReads()
|
||||||
|
{
|
||||||
|
_realTimeoutControl.ResumeTimingReads();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void StopTimingReads()
|
||||||
|
{
|
||||||
|
_realTimeoutControl.StopTimingReads();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void BytesRead(long count)
|
||||||
|
{
|
||||||
|
_realTimeoutControl.BytesRead(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public virtual void StartTimingWrite(MinDataRate minRate, long size)
|
||||||
|
{
|
||||||
|
_realTimeoutControl.StartTimingWrite(minRate, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void StopTimingWrite()
|
||||||
|
{
|
||||||
|
_realTimeoutControl.StopTimingWrite();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
// 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.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
{
|
||||||
|
public class Http2TimeoutTests : Http2TestBase
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task HEADERS_NotReceivedInitially_WithinKeepAliveTimeout_ClosesConnection()
|
||||||
|
{
|
||||||
|
var mockSystemClock = new MockSystemClock();
|
||||||
|
var limits = _connectionContext.ServiceContext.ServerOptions.Limits;
|
||||||
|
|
||||||
|
_timeoutControl.Initialize(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
await InitializeConnectionAsync(_noopApplication);
|
||||||
|
|
||||||
|
mockSystemClock.UtcNow += limits.KeepAliveTimeout + Heartbeat.Interval;
|
||||||
|
_timeoutControl.Tick(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
|
||||||
|
|
||||||
|
mockSystemClock.UtcNow += TimeSpan.FromTicks(1);
|
||||||
|
_timeoutControl.Tick(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once);
|
||||||
|
|
||||||
|
await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HEADERS_NotReceivedAfterFirstRequest_WithinKeepAliveTimeout_ClosesConnection()
|
||||||
|
{
|
||||||
|
var mockSystemClock = new MockSystemClock();
|
||||||
|
var limits = _connectionContext.ServiceContext.ServerOptions.Limits;
|
||||||
|
|
||||||
|
_timeoutControl.Initialize(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
await InitializeConnectionAsync(_noopApplication);
|
||||||
|
|
||||||
|
mockSystemClock.UtcNow += limits.KeepAliveTimeout + Heartbeat.Interval;
|
||||||
|
_timeoutControl.Tick(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
// keep-alive timeout set but not fired.
|
||||||
|
_mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny<long>(), TimeoutReason.KeepAlive), Times.Once);
|
||||||
|
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
|
||||||
|
|
||||||
|
// The KeepAlive timeout is set when the stream completes processing on a background thread, so we need to hook the
|
||||||
|
// keep-alive set afterwards to make a reliable test.
|
||||||
|
var setTimeoutTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
_mockTimeoutControl.Setup(c => c.SetTimeout(It.IsAny<long>(), TimeoutReason.KeepAlive)).Callback<long, TimeoutReason>((t, r) =>
|
||||||
|
{
|
||||||
|
_timeoutControl.SetTimeout(t, r);
|
||||||
|
setTimeoutTcs.SetResult(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send continuation frame to verify intermediate request header timeout doesn't interfere with keep-alive timeout.
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
|
||||||
|
await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS);
|
||||||
|
await SendDataAsync(1, new Memory<byte>(), endStream: true);
|
||||||
|
|
||||||
|
_mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny<long>(), TimeoutReason.RequestHeaders), Times.Once);
|
||||||
|
|
||||||
|
await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 55,
|
||||||
|
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||||
|
withStreamId: 1);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await setTimeoutTcs.Task.DefaultTimeout();
|
||||||
|
|
||||||
|
mockSystemClock.UtcNow += limits.KeepAliveTimeout + Heartbeat.Interval;
|
||||||
|
_timeoutControl.Tick(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
|
||||||
|
|
||||||
|
mockSystemClock.UtcNow += TimeSpan.FromTicks(1);
|
||||||
|
_timeoutControl.Tick(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once);
|
||||||
|
|
||||||
|
await WaitForConnectionStopAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HEADERS_ReceivedWithoutAllCONTINUATIONs_WithinRequestHeadersTimeout_AbortsConnection()
|
||||||
|
{
|
||||||
|
var mockSystemClock = new MockSystemClock();
|
||||||
|
var limits = _connectionContext.ServiceContext.ServerOptions.Limits;
|
||||||
|
|
||||||
|
_mockConnectionContext.Setup(c => c.Abort(It.IsAny<ConnectionAbortedException>())).Callback<ConnectionAbortedException>(ex =>
|
||||||
|
{
|
||||||
|
// Emulate transport abort so the _connectionTask completes.
|
||||||
|
_pair.Application.Output.Complete(ex);
|
||||||
|
});
|
||||||
|
|
||||||
|
_timeoutControl.Initialize(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
await InitializeConnectionAsync(_noopApplication);
|
||||||
|
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
|
||||||
|
|
||||||
|
await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.NONE);
|
||||||
|
|
||||||
|
mockSystemClock.UtcNow += limits.RequestHeadersTimeout + Heartbeat.Interval;
|
||||||
|
_timeoutControl.Tick(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
|
||||||
|
|
||||||
|
await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.NONE);
|
||||||
|
|
||||||
|
mockSystemClock.UtcNow += TimeSpan.FromTicks(1);
|
||||||
|
_timeoutControl.Tick(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.RequestHeaders), Times.Once);
|
||||||
|
|
||||||
|
await WaitForConnectionErrorAsync<BadHttpRequestException>(
|
||||||
|
ignoreNonGoAwayFrames: false,
|
||||||
|
expectedLastStreamId: 1,
|
||||||
|
Http2ErrorCode.INTERNAL_ERROR,
|
||||||
|
CoreStrings.BadRequest_RequestHeadersTimeout);
|
||||||
|
|
||||||
|
_mockConnectionContext.Verify(c =>c.Abort(It.Is<ConnectionAbortedException>(e =>
|
||||||
|
e.Message == CoreStrings.BadRequest_RequestHeadersTimeout)), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection()
|
||||||
|
{
|
||||||
|
var mockSystemClock = new MockSystemClock();
|
||||||
|
var limits = _connectionContext.ServiceContext.ServerOptions.Limits;
|
||||||
|
|
||||||
|
_timeoutControl.Initialize(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
await InitializeConnectionAsync(_noopApplication);
|
||||||
|
|
||||||
|
await SendGoAwayAsync();
|
||||||
|
|
||||||
|
await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
|
||||||
|
|
||||||
|
mockSystemClock.UtcNow +=
|
||||||
|
TimeSpan.FromSeconds(limits.MaxResponseBufferSize.Value * 2 / limits.MinResponseDataRate.BytesPerSecond) +
|
||||||
|
Heartbeat.Interval;
|
||||||
|
_timeoutControl.Tick(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
|
||||||
|
_mockConnectionContext.Verify(c => c.Abort(It.IsAny<ConnectionAbortedException>()), Times.Never);
|
||||||
|
|
||||||
|
mockSystemClock.UtcNow += TimeSpan.FromTicks(1);
|
||||||
|
_timeoutControl.Tick(mockSystemClock.UtcNow);
|
||||||
|
|
||||||
|
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once);
|
||||||
|
|
||||||
|
_mockConnectionContext.Verify(c =>c.Abort(It.Is<ConnectionAbortedException>(e =>
|
||||||
|
e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
await RegisterIPEndPoint_Success(endpoint, testUrl, 443);
|
await RegisterIPEndPoint_Success(endpoint, testUrl, 443);
|
||||||
}
|
}
|
||||||
|
|
||||||
[ConditionalTheory(Skip="https://github.com/aspnet/KestrelHttpServer/issues/2434")]
|
[ConditionalTheory]
|
||||||
[MemberData(nameof(AddressRegistrationDataIPv6))]
|
[MemberData(nameof(AddressRegistrationDataIPv6))]
|
||||||
[IPv6SupportedCondition]
|
[IPv6SupportedCondition]
|
||||||
public async Task RegisterAddresses_IPv6_Success(string addressInput, string[] testUrls)
|
public async Task RegisterAddresses_IPv6_Success(string addressInput, string[] testUrls)
|
||||||
|
|
|
||||||
|
|
@ -456,95 +456,98 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate()
|
public async Task ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate()
|
||||||
{
|
{
|
||||||
using (StartLog(out var loggerFactory, "ConnClosedWhenRespDoesNotSatisfyMin"))
|
var logger = LoggerFactory.CreateLogger($"{ typeof(ResponseTests).FullName}.{ nameof(ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate)}");
|
||||||
|
const int chunkSize = 1024;
|
||||||
|
const int chunks = 256 * 1024;
|
||||||
|
var responseSize = chunks * chunkSize;
|
||||||
|
var chunkData = new byte[chunkSize];
|
||||||
|
|
||||||
|
var responseRateTimeoutMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
var connectionStopMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
var requestAborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
var appFuncCompleted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
var mockKestrelTrace = new Mock<IKestrelTrace>();
|
||||||
|
mockKestrelTrace
|
||||||
|
.Setup(trace => trace.ResponseMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
|
.Callback(() => responseRateTimeoutMessageLogged.SetResult(null));
|
||||||
|
mockKestrelTrace
|
||||||
|
.Setup(trace => trace.ConnectionStop(It.IsAny<string>()))
|
||||||
|
.Callback(() => connectionStopMessageLogged.SetResult(null));
|
||||||
|
|
||||||
|
var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object)
|
||||||
{
|
{
|
||||||
var logger = loggerFactory.CreateLogger($"{ typeof(ResponseTests).FullName}.{ nameof(ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate)}");
|
ServerOptions =
|
||||||
const int chunkSize = 1024;
|
|
||||||
const int chunks = 256 * 1024;
|
|
||||||
var responseSize = chunks * chunkSize;
|
|
||||||
var chunkData = new byte[chunkSize];
|
|
||||||
|
|
||||||
var responseRateTimeoutMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
var connectionStopMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
var requestAborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
var appFuncCompleted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
|
|
||||||
var mockKestrelTrace = new Mock<IKestrelTrace>();
|
|
||||||
mockKestrelTrace
|
|
||||||
.Setup(trace => trace.ResponseMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>()))
|
|
||||||
.Callback(() => responseRateTimeoutMessageLogged.SetResult(null));
|
|
||||||
mockKestrelTrace
|
|
||||||
.Setup(trace => trace.ConnectionStop(It.IsAny<string>()))
|
|
||||||
.Callback(() => connectionStopMessageLogged.SetResult(null));
|
|
||||||
|
|
||||||
var testContext = new TestServiceContext(loggerFactory, mockKestrelTrace.Object)
|
|
||||||
{
|
{
|
||||||
ServerOptions =
|
Limits =
|
||||||
{
|
{
|
||||||
Limits =
|
MinResponseDataRate = new MinDataRate(bytesPerSecond: 1024 * 1024, gracePeriod: TimeSpan.FromSeconds(2))
|
||||||
{
|
|
||||||
MinResponseDataRate = new MinDataRate(bytesPerSecond: 1024 * 1024, gracePeriod: TimeSpan.FromSeconds(2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
testContext.InitializeHeartbeat();
|
|
||||||
|
|
||||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
|
|
||||||
listenOptions.ConnectionAdapters.Add(new LoggingConnectionAdapter(loggerFactory.CreateLogger<LoggingConnectionAdapter>()));
|
|
||||||
|
|
||||||
var appLogger = loggerFactory.CreateLogger("App");
|
|
||||||
async Task App(HttpContext context)
|
|
||||||
{
|
|
||||||
appLogger.LogInformation("Request received");
|
|
||||||
context.RequestAborted.Register(() => requestAborted.SetResult(null));
|
|
||||||
|
|
||||||
context.Response.ContentLength = responseSize;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
for (var i = 0; i < chunks; i++)
|
|
||||||
{
|
|
||||||
await context.Response.Body.WriteAsync(chunkData, 0, chunkData.Length, context.RequestAborted);
|
|
||||||
appLogger.LogInformation("Wrote chunk of {chunkSize} bytes", chunkSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
appFuncCompleted.SetResult(null);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await requestAborted.Task.DefaultTimeout();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
using (var server = new TestServer(App, testContext, listenOptions))
|
testContext.InitializeHeartbeat();
|
||||||
|
|
||||||
|
var appLogger = LoggerFactory.CreateLogger("App");
|
||||||
|
async Task App(HttpContext context)
|
||||||
|
{
|
||||||
|
appLogger.LogInformation("Request received");
|
||||||
|
context.RequestAborted.Register(() => requestAborted.SetResult(null));
|
||||||
|
|
||||||
|
context.Response.ContentLength = responseSize;
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
for (; i < chunks; i++)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Sending request");
|
await context.Response.Body.WriteAsync(chunkData, 0, chunkData.Length, context.RequestAborted);
|
||||||
await connection.Send(
|
await Task.Yield();
|
||||||
"GET / HTTP/1.1",
|
|
||||||
"Host:",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
|
|
||||||
logger.LogInformation("Sent request");
|
|
||||||
|
|
||||||
var sw = Stopwatch.StartNew();
|
|
||||||
logger.LogInformation("Waiting for connection to abort.");
|
|
||||||
|
|
||||||
await requestAborted.Task.DefaultTimeout();
|
|
||||||
await responseRateTimeoutMessageLogged.Task.DefaultTimeout();
|
|
||||||
await connectionStopMessageLogged.Task.DefaultTimeout();
|
|
||||||
await appFuncCompleted.Task.DefaultTimeout();
|
|
||||||
await AssertStreamAborted(connection.Stream, chunkSize * chunks);
|
|
||||||
|
|
||||||
sw.Stop();
|
|
||||||
logger.LogInformation("Connection was aborted after {totalMilliseconds}ms.", sw.ElapsedMilliseconds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appFuncCompleted.SetException(new Exception("This shouldn't be reached."));
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
appFuncCompleted.SetResult(null);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
appFuncCompleted.SetException(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
appLogger.LogInformation("Wrote {total} bytes", chunkSize * i);
|
||||||
|
await requestAborted.Task.DefaultTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var server = new TestServer(App, testContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
logger.LogInformation("Sending request");
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
logger.LogInformation("Sent request");
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
logger.LogInformation("Waiting for connection to abort.");
|
||||||
|
|
||||||
|
await requestAborted.Task.DefaultTimeout();
|
||||||
|
await responseRateTimeoutMessageLogged.Task.DefaultTimeout();
|
||||||
|
await connectionStopMessageLogged.Task.DefaultTimeout();
|
||||||
|
await appFuncCompleted.Task.DefaultTimeout();
|
||||||
|
await AssertStreamAborted(connection.Stream, chunkSize * chunks);
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
logger.LogInformation("Connection was aborted after {totalMilliseconds}ms.", sw.ElapsedMilliseconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue