Add request body minimum data rate feature (#1874).
This commit is contained in:
parent
f96c48c08d
commit
fcc04f8c3d
|
|
@ -25,7 +25,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{0EF2AC
|
||||||
test\shared\KestrelTestLoggerProvider.cs = test\shared\KestrelTestLoggerProvider.cs
|
test\shared\KestrelTestLoggerProvider.cs = test\shared\KestrelTestLoggerProvider.cs
|
||||||
test\shared\LifetimeNotImplemented.cs = test\shared\LifetimeNotImplemented.cs
|
test\shared\LifetimeNotImplemented.cs = test\shared\LifetimeNotImplemented.cs
|
||||||
test\shared\MockConnectionInformation.cs = test\shared\MockConnectionInformation.cs
|
test\shared\MockConnectionInformation.cs = test\shared\MockConnectionInformation.cs
|
||||||
test\shared\MockFrameControl.cs = test\shared\MockFrameControl.cs
|
|
||||||
test\shared\MockLogger.cs = test\shared\MockLogger.cs
|
test\shared\MockLogger.cs = test\shared\MockLogger.cs
|
||||||
test\shared\MockSystemClock.cs = test\shared\MockSystemClock.cs
|
test\shared\MockSystemClock.cs = test\shared\MockSystemClock.cs
|
||||||
test\shared\StringExtensions.cs = test\shared\StringExtensions.cs
|
test\shared\StringExtensions.cs = test\shared\StringExtensions.cs
|
||||||
|
|
|
||||||
|
|
@ -327,4 +327,10 @@
|
||||||
<data name="MaxRequestBodySizeCannotBeModifiedForUpgradedRequests" xml:space="preserve">
|
<data name="MaxRequestBodySizeCannotBeModifiedForUpgradedRequests" xml:space="preserve">
|
||||||
<value>The maximum request body size cannot be modified after the request has been upgraded.</value>
|
<value>The maximum request body size cannot be modified after the request has been upgraded.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PositiveTimeSpanRequired" xml:space="preserve">
|
||||||
|
<value>Value must be a positive TimeSpan.</value>
|
||||||
|
</data>
|
||||||
|
<data name="NonNegativeTimeSpanRequired" xml:space="preserve">
|
||||||
|
<value>Value must be a non-negative TimeSpan.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a minimum data rate for the request body of an HTTP request.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHttpRequestBodyMinimumDataRateFeature
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum data rate in bytes/second at which the request body should be received.
|
||||||
|
/// Setting this property to null indicates no minimum data rate should be enforced.
|
||||||
|
/// This limit has no effect on upgraded connections which are always unlimited.
|
||||||
|
/// </summary>
|
||||||
|
MinimumDataRate MinimumDataRate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
public class MinimumDataRate
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="MinimumDataRate"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rate">The minimum rate in bytes/second at which data should be processed.</param>
|
||||||
|
/// <param name="gracePeriod">The amount of time to delay enforcement of <paramref name="rate"/>.</param>
|
||||||
|
public MinimumDataRate(double rate, TimeSpan gracePeriod)
|
||||||
|
{
|
||||||
|
if (rate <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(rate), CoreStrings.PositiveNumberRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gracePeriod < TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(gracePeriod), CoreStrings.NonNegativeTimeSpanRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rate = rate;
|
||||||
|
GracePeriod = gracePeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum rate in bytes/second at which data should be processed.
|
||||||
|
/// </summary>
|
||||||
|
public double Rate { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of time to delay enforcement of <see cref="MinimumDataRate" />.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan GracePeriod { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
private long _timeoutTimestamp = long.MaxValue;
|
private long _timeoutTimestamp = long.MaxValue;
|
||||||
private TimeoutAction _timeoutAction;
|
private TimeoutAction _timeoutAction;
|
||||||
|
|
||||||
|
private object _readTimingLock = new object();
|
||||||
|
private bool _readTimingEnabled;
|
||||||
|
private bool _readTimingPauseRequested;
|
||||||
|
private long _readTimingElapsedTicks;
|
||||||
|
private long _readTimingBytesRead;
|
||||||
|
|
||||||
private Task _lifetimeTask;
|
private Task _lifetimeTask;
|
||||||
|
|
||||||
public FrameConnection(FrameConnectionContext context)
|
public FrameConnection(FrameConnectionContext context)
|
||||||
|
|
@ -36,6 +42,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For testing
|
||||||
|
internal Frame Frame => _frame;
|
||||||
|
|
||||||
|
public bool TimedOut { get; private set; }
|
||||||
|
|
||||||
public string ConnectionId => _context.ConnectionId;
|
public string ConnectionId => _context.ConnectionId;
|
||||||
public IPipeWriter Input => _context.Input.Writer;
|
public IPipeWriter Input => _context.Input.Writer;
|
||||||
public IPipeReader Output => _context.Output.Reader;
|
public IPipeReader Output => _context.Output.Reader;
|
||||||
|
|
@ -91,15 +102,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
// _frame must be initialized before adding the connection to the connection manager
|
// _frame must be initialized before adding the connection to the connection manager
|
||||||
_frame = new Frame<TContext>(application, new FrameContext
|
CreateFrame(application, input, output);
|
||||||
{
|
|
||||||
ConnectionId = _context.ConnectionId,
|
|
||||||
ConnectionInformation = _context.ConnectionInformation,
|
|
||||||
ServiceContext = _context.ServiceContext,
|
|
||||||
TimeoutControl = this,
|
|
||||||
Input = input,
|
|
||||||
Output = output
|
|
||||||
});
|
|
||||||
|
|
||||||
// Do this before the first await so we don't yield control to the transport until we've
|
// Do this before the first await so we don't yield control to the transport until we've
|
||||||
// added the connection to the connection manager
|
// added the connection to the connection manager
|
||||||
|
|
@ -140,9 +143,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void CreateFrame<TContext>(IHttpApplication<TContext> application, IPipeReader input, IPipe output)
|
||||||
|
{
|
||||||
|
_frame = new Frame<TContext>(application, new FrameContext
|
||||||
|
{
|
||||||
|
ConnectionId = _context.ConnectionId,
|
||||||
|
ConnectionInformation = _context.ConnectionInformation,
|
||||||
|
ServiceContext = _context.ServiceContext,
|
||||||
|
TimeoutControl = this,
|
||||||
|
Input = input,
|
||||||
|
Output = output
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void OnConnectionClosed(Exception ex)
|
public void OnConnectionClosed(Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Assert(_frame != null, $"nameof({_frame}) is null");
|
Debug.Assert(_frame != null, $"{nameof(_frame)} is null");
|
||||||
|
|
||||||
// Abort the connection (if not already aborted)
|
// Abort the connection (if not already aborted)
|
||||||
_frame.Abort(ex);
|
_frame.Abort(ex);
|
||||||
|
|
@ -152,7 +168,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
||||||
public Task StopAsync()
|
public Task StopAsync()
|
||||||
{
|
{
|
||||||
Debug.Assert(_frame != null, $"nameof({_frame}) is null");
|
Debug.Assert(_frame != null, $"{nameof(_frame)} is null");
|
||||||
|
|
||||||
_frame.Stop();
|
_frame.Stop();
|
||||||
|
|
||||||
|
|
@ -161,7 +177,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
||||||
public void Abort(Exception ex)
|
public void Abort(Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Assert(_frame != null, $"nameof({_frame}) is null");
|
Debug.Assert(_frame != null, $"{nameof(_frame)} is null");
|
||||||
|
|
||||||
// Abort the connection (if not already aborted)
|
// Abort the connection (if not already aborted)
|
||||||
_frame.Abort(ex);
|
_frame.Abort(ex);
|
||||||
|
|
@ -169,7 +185,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
||||||
public Task AbortAsync(Exception ex)
|
public Task AbortAsync(Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Assert(_frame != null, $"nameof({_frame}) is null");
|
Debug.Assert(_frame != null, $"{nameof(_frame)} is null");
|
||||||
|
|
||||||
// Abort the connection (if not already aborted)
|
// Abort the connection (if not already aborted)
|
||||||
_frame.Abort(ex);
|
_frame.Abort(ex);
|
||||||
|
|
@ -177,16 +193,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
return _lifetimeTask;
|
return _lifetimeTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Timeout()
|
public void SetTimeoutResponse()
|
||||||
{
|
{
|
||||||
Debug.Assert(_frame != null, $"nameof({_frame}) is null");
|
Debug.Assert(_frame != null, $"{nameof(_frame)} is null");
|
||||||
|
|
||||||
_frame.SetBadRequestState(RequestRejectionReason.RequestTimeout);
|
_frame.SetBadRequestState(RequestRejectionReason.RequestTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Timeout()
|
||||||
|
{
|
||||||
|
Debug.Assert(_frame != null, $"{nameof(_frame)} is null");
|
||||||
|
|
||||||
|
TimedOut = true;
|
||||||
|
_readTimingEnabled = false;
|
||||||
|
_frame.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<Stream> ApplyConnectionAdaptersAsync()
|
private async Task<Stream> ApplyConnectionAdaptersAsync()
|
||||||
{
|
{
|
||||||
Debug.Assert(_frame != null, $"nameof({_frame}) is null");
|
Debug.Assert(_frame != null, $"{nameof(_frame)} is null");
|
||||||
|
|
||||||
var features = new FeatureCollection();
|
var features = new FeatureCollection();
|
||||||
var connectionAdapters = _context.ConnectionAdapters;
|
var connectionAdapters = _context.ConnectionAdapters;
|
||||||
|
|
@ -231,7 +256,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
||||||
public void Tick(DateTimeOffset now)
|
public void Tick(DateTimeOffset now)
|
||||||
{
|
{
|
||||||
Debug.Assert(_frame != null, $"nameof({_frame}) is null");
|
Debug.Assert(_frame != null, $"{nameof(_frame)} is null");
|
||||||
|
|
||||||
var timestamp = now.Ticks;
|
var timestamp = now.Ticks;
|
||||||
|
|
||||||
|
|
@ -242,10 +267,41 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
||||||
if (_timeoutAction == TimeoutAction.SendTimeoutResponse)
|
if (_timeoutAction == TimeoutAction.SendTimeoutResponse)
|
||||||
{
|
{
|
||||||
Timeout();
|
SetTimeoutResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
_frame.Stop();
|
Timeout();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lock (_readTimingLock)
|
||||||
|
{
|
||||||
|
if (_readTimingEnabled)
|
||||||
|
{
|
||||||
|
_readTimingElapsedTicks += timestamp - _lastTimestamp;
|
||||||
|
|
||||||
|
if (_frame.RequestBodyMinimumDataRate?.Rate > 0 && _readTimingElapsedTicks > _frame.RequestBodyMinimumDataRate.GracePeriod.Ticks)
|
||||||
|
{
|
||||||
|
var elapsedSeconds = (double)_readTimingElapsedTicks / TimeSpan.TicksPerSecond;
|
||||||
|
var rate = Interlocked.Read(ref _readTimingBytesRead) / elapsedSeconds;
|
||||||
|
|
||||||
|
if (rate < _frame.RequestBodyMinimumDataRate.Rate)
|
||||||
|
{
|
||||||
|
Log.RequestBodyMininumDataRateNotSatisfied(_context.ConnectionId, _frame.TraceIdentifier, _frame.RequestBodyMinimumDataRate.Rate);
|
||||||
|
Timeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PauseTimingReads() cannot just set _timingReads to false. It needs to go through at least one tick
|
||||||
|
// before pausing, otherwise _readTimingElapsed might never be updated if PauseTimingReads() is always
|
||||||
|
// called before the next tick.
|
||||||
|
if (_readTimingPauseRequested)
|
||||||
|
{
|
||||||
|
_readTimingEnabled = false;
|
||||||
|
_readTimingPauseRequested = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Interlocked.Exchange(ref _lastTimestamp, timestamp);
|
Interlocked.Exchange(ref _lastTimestamp, timestamp);
|
||||||
|
|
@ -275,5 +331,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void StartTimingReads()
|
||||||
|
{
|
||||||
|
lock (_readTimingLock)
|
||||||
|
{
|
||||||
|
_readTimingElapsedTicks = 0;
|
||||||
|
_readTimingBytesRead = 0;
|
||||||
|
_readTimingEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopTimingReads()
|
||||||
|
{
|
||||||
|
lock (_readTimingLock)
|
||||||
|
{
|
||||||
|
_readTimingEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PauseTimingReads()
|
||||||
|
{
|
||||||
|
lock (_readTimingLock)
|
||||||
|
{
|
||||||
|
_readTimingPauseRequested = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResumeTimingReads()
|
||||||
|
{
|
||||||
|
lock (_readTimingLock)
|
||||||
|
{
|
||||||
|
_readTimingEnabled = true;
|
||||||
|
|
||||||
|
// In case pause and resume were both called between ticks
|
||||||
|
_readTimingPauseRequested = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BytesRead(int count)
|
||||||
|
{
|
||||||
|
Interlocked.Add(ref _readTimingBytesRead, count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
@ -21,7 +22,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
IHttpConnectionFeature,
|
IHttpConnectionFeature,
|
||||||
IHttpRequestLifetimeFeature,
|
IHttpRequestLifetimeFeature,
|
||||||
IHttpRequestIdentifierFeature,
|
IHttpRequestIdentifierFeature,
|
||||||
IHttpMaxRequestBodySizeFeature
|
IHttpMaxRequestBodySizeFeature,
|
||||||
|
IHttpRequestBodyMinimumDataRateFeature
|
||||||
{
|
{
|
||||||
// NOTE: When feature interfaces are added to or removed from this Frame class implementation,
|
// NOTE: When feature interfaces are added to or removed from this Frame class implementation,
|
||||||
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
|
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
|
||||||
|
|
@ -227,6 +229,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MinimumDataRate IHttpRequestBodyMinimumDataRateFeature.MinimumDataRate
|
||||||
|
{
|
||||||
|
get => RequestBodyMinimumDataRate;
|
||||||
|
set => RequestBodyMinimumDataRate = value;
|
||||||
|
}
|
||||||
|
|
||||||
object IFeatureCollection.this[Type key]
|
object IFeatureCollection.this[Type key]
|
||||||
{
|
{
|
||||||
get => FastFeatureGet(key);
|
get => FastFeatureGet(key);
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private static readonly Type IHttpWebSocketFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature);
|
private static readonly Type IHttpWebSocketFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature);
|
||||||
private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
|
private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
|
||||||
private static readonly Type IHttpMaxRequestBodySizeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature);
|
private static readonly Type IHttpMaxRequestBodySizeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature);
|
||||||
|
private static readonly Type IHttpRequestBodyMinimumDataRateFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpRequestBodyMinimumDataRateFeature);
|
||||||
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
||||||
|
|
||||||
private object _currentIHttpRequestFeature;
|
private object _currentIHttpRequestFeature;
|
||||||
|
|
@ -42,6 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private object _currentIHttpWebSocketFeature;
|
private object _currentIHttpWebSocketFeature;
|
||||||
private object _currentISessionFeature;
|
private object _currentISessionFeature;
|
||||||
private object _currentIHttpMaxRequestBodySizeFeature;
|
private object _currentIHttpMaxRequestBodySizeFeature;
|
||||||
|
private object _currentIHttpRequestBodyMinimumDataRateFeature;
|
||||||
private object _currentIHttpSendFileFeature;
|
private object _currentIHttpSendFileFeature;
|
||||||
|
|
||||||
private void FastReset()
|
private void FastReset()
|
||||||
|
|
@ -53,6 +55,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
_currentIHttpRequestLifetimeFeature = this;
|
_currentIHttpRequestLifetimeFeature = this;
|
||||||
_currentIHttpConnectionFeature = this;
|
_currentIHttpConnectionFeature = this;
|
||||||
_currentIHttpMaxRequestBodySizeFeature = this;
|
_currentIHttpMaxRequestBodySizeFeature = this;
|
||||||
|
_currentIHttpRequestBodyMinimumDataRateFeature = this;
|
||||||
|
|
||||||
_currentIServiceProvidersFeature = null;
|
_currentIServiceProvidersFeature = null;
|
||||||
_currentIHttpAuthenticationFeature = null;
|
_currentIHttpAuthenticationFeature = null;
|
||||||
|
|
@ -132,6 +135,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
return _currentIHttpMaxRequestBodySizeFeature;
|
return _currentIHttpMaxRequestBodySizeFeature;
|
||||||
}
|
}
|
||||||
|
if (key == IHttpRequestBodyMinimumDataRateFeatureType)
|
||||||
|
{
|
||||||
|
return _currentIHttpRequestBodyMinimumDataRateFeature;
|
||||||
|
}
|
||||||
if (key == IHttpSendFileFeatureType)
|
if (key == IHttpSendFileFeatureType)
|
||||||
{
|
{
|
||||||
return _currentIHttpSendFileFeature;
|
return _currentIHttpSendFileFeature;
|
||||||
|
|
@ -223,6 +230,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
_currentIHttpMaxRequestBodySizeFeature = feature;
|
_currentIHttpMaxRequestBodySizeFeature = feature;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (key == IHttpRequestBodyMinimumDataRateFeatureType)
|
||||||
|
{
|
||||||
|
_currentIHttpRequestBodyMinimumDataRateFeature = feature;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (key == IHttpSendFileFeatureType)
|
if (key == IHttpSendFileFeatureType)
|
||||||
{
|
{
|
||||||
_currentIHttpSendFileFeature = feature;
|
_currentIHttpSendFileFeature = feature;
|
||||||
|
|
@ -297,6 +309,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
yield return new KeyValuePair<Type, object>(IHttpMaxRequestBodySizeFeatureType, _currentIHttpMaxRequestBodySizeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature);
|
yield return new KeyValuePair<Type, object>(IHttpMaxRequestBodySizeFeatureType, _currentIHttpMaxRequestBodySizeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature);
|
||||||
}
|
}
|
||||||
|
if (_currentIHttpRequestBodyMinimumDataRateFeature != null)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<Type, object>(IHttpRequestBodyMinimumDataRateFeatureType, _currentIHttpRequestBodyMinimumDataRateFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpRequestBodyMinimumDataRateFeature);
|
||||||
|
}
|
||||||
if (_currentIHttpSendFileFeature != null)
|
if (_currentIHttpSendFileFeature != null)
|
||||||
{
|
{
|
||||||
yield return new KeyValuePair<Type, object>(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
yield return new KeyValuePair<Type, object>(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ using Microsoft.Extensions.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||||
|
|
||||||
// ReSharper disable AccessToModifiedClosure
|
// ReSharper disable AccessToModifiedClosure
|
||||||
|
|
||||||
|
|
@ -300,6 +301,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
protected FrameResponseHeaders FrameResponseHeaders { get; } = new FrameResponseHeaders();
|
protected FrameResponseHeaders FrameResponseHeaders { get; } = new FrameResponseHeaders();
|
||||||
|
|
||||||
|
public MinimumDataRate RequestBodyMinimumDataRate { get; set; }
|
||||||
|
|
||||||
public void InitializeStreams(MessageBody messageBody)
|
public void InitializeStreams(MessageBody messageBody)
|
||||||
{
|
{
|
||||||
if (_frameStreams == null)
|
if (_frameStreams == null)
|
||||||
|
|
@ -376,6 +379,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
_responseBytesWritten = 0;
|
_responseBytesWritten = 0;
|
||||||
_requestCount++;
|
_requestCount++;
|
||||||
|
|
||||||
|
RequestBodyMinimumDataRate = ServerOptions.Limits.RequestBodyMinimumDataRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
if (_keepAlive)
|
if (_keepAlive)
|
||||||
{
|
{
|
||||||
// Finish reading the request body in case the app did not.
|
// Finish reading the request body in case the app did not.
|
||||||
|
TimeoutControl.SetTimeout(Constants.RequestBodyDrainTimeout.Ticks, TimeoutAction.SendTimeoutResponse);
|
||||||
await messageBody.ConsumeAsync();
|
await messageBody.ConsumeAsync();
|
||||||
|
TimeoutControl.CancelTimeout();
|
||||||
|
|
||||||
// At this point both the request body pipe reader and writer should be completed.
|
// At this point both the request body pipe reader and writer should be completed.
|
||||||
RequestBodyPipe.Reset();
|
RequestBodyPipe.Reset();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
@ -16,6 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private static readonly MessageBody _zeroContentLengthKeepAlive = new ForZeroContentLength(keepAlive: true);
|
private static readonly MessageBody _zeroContentLengthKeepAlive = new ForZeroContentLength(keepAlive: true);
|
||||||
|
|
||||||
private readonly Frame _context;
|
private readonly Frame _context;
|
||||||
|
|
||||||
private bool _send100Continue = true;
|
private bool _send100Continue = true;
|
||||||
private volatile bool _canceled;
|
private volatile bool _canceled;
|
||||||
|
|
||||||
|
|
@ -32,22 +34,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
public virtual bool IsEmpty => false;
|
public virtual bool IsEmpty => false;
|
||||||
|
|
||||||
|
private IKestrelTrace Log => _context.ServiceContext.Log;
|
||||||
|
|
||||||
private async Task PumpAsync()
|
private async Task PumpAsync()
|
||||||
{
|
{
|
||||||
Exception error = null;
|
Exception error = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var awaitable = _context.Input.ReadAsync();
|
||||||
|
|
||||||
|
if (!awaitable.IsCompleted)
|
||||||
|
{
|
||||||
|
TryProduceContinue();
|
||||||
|
}
|
||||||
|
|
||||||
|
TryStartTimingReads();
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var awaitable = _context.Input.ReadAsync();
|
var result = await awaitable;
|
||||||
|
|
||||||
if (!awaitable.IsCompleted)
|
if (_context.TimeoutControl.TimedOut)
|
||||||
{
|
{
|
||||||
TryProduceContinue();
|
_context.ThrowRequestRejected(RequestRejectionReason.RequestTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await awaitable;
|
|
||||||
var readableBuffer = result.Buffer;
|
var readableBuffer = result.Buffer;
|
||||||
var consumed = readableBuffer.Start;
|
var consumed = readableBuffer.Start;
|
||||||
var examined = readableBuffer.End;
|
var examined = readableBuffer.End;
|
||||||
|
|
@ -73,7 +85,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
writableBuffer.Commit();
|
writableBuffer.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
await writableBuffer.FlushAsync();
|
var writeAwaitable = writableBuffer.FlushAsync();
|
||||||
|
var backpressure = false;
|
||||||
|
|
||||||
|
if (!writeAwaitable.IsCompleted)
|
||||||
|
{
|
||||||
|
// Backpressure, stop controlling incoming data rate until data is read.
|
||||||
|
backpressure = true;
|
||||||
|
_context.TimeoutControl.PauseTimingReads();
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeAwaitable;
|
||||||
|
|
||||||
|
if (backpressure)
|
||||||
|
{
|
||||||
|
_context.TimeoutControl.ResumeTimingReads();
|
||||||
|
}
|
||||||
|
|
||||||
if (done)
|
if (done)
|
||||||
{
|
{
|
||||||
|
|
@ -84,6 +111,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
_context.ThrowRequestRejected(RequestRejectionReason.UnexpectedEndOfRequestContent);
|
_context.ThrowRequestRejected(RequestRejectionReason.UnexpectedEndOfRequestContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
awaitable = _context.Input.ReadAsync();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -98,6 +127,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_context.RequestBodyPipe.Writer.Complete(error);
|
_context.RequestBodyPipe.Writer.Complete(error);
|
||||||
|
TryStopTimingReads();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,6 +221,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
protected void Copy(ReadableBuffer readableBuffer, WritableBuffer writableBuffer)
|
protected void Copy(ReadableBuffer readableBuffer, WritableBuffer writableBuffer)
|
||||||
{
|
{
|
||||||
|
_context.TimeoutControl.BytesRead(readableBuffer.Length);
|
||||||
|
|
||||||
if (readableBuffer.IsSingleSpan)
|
if (readableBuffer.IsSingleSpan)
|
||||||
{
|
{
|
||||||
writableBuffer.Write(readableBuffer.First.Span);
|
writableBuffer.Write(readableBuffer.First.Span);
|
||||||
|
|
@ -232,6 +264,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TryStartTimingReads()
|
||||||
|
{
|
||||||
|
if (!RequestUpgrade)
|
||||||
|
{
|
||||||
|
Log.RequestBodyStart(_context.ConnectionIdFeature, _context.TraceIdentifier);
|
||||||
|
_context.TimeoutControl.StartTimingReads();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryStopTimingReads()
|
||||||
|
{
|
||||||
|
if (!RequestUpgrade)
|
||||||
|
{
|
||||||
|
Log.RequestBodyDone(_context.ConnectionIdFeature, _context.TraceIdentifier);
|
||||||
|
_context.TimeoutControl.StopTimingReads();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static MessageBody For(
|
public static MessageBody For(
|
||||||
HttpVersion httpVersion,
|
HttpVersion httpVersion,
|
||||||
FrameRequestHeaders headers,
|
FrameRequestHeaders headers,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.Internal.Infrastructure
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
internal static class Constants
|
internal static class Constants
|
||||||
|
|
@ -28,5 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
public const string SocketDescriptorPrefix = "sockfd:";
|
public const string SocketDescriptorPrefix = "sockfd:";
|
||||||
|
|
||||||
public const string ServerName = "Kestrel";
|
public const string ServerName = "Kestrel";
|
||||||
|
|
||||||
|
public static readonly TimeSpan RequestBodyDrainTimeout = TimeSpan.FromSeconds(5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,5 +37,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
void HeartbeatSlow(TimeSpan interval, DateTimeOffset now);
|
void HeartbeatSlow(TimeSpan interval, DateTimeOffset now);
|
||||||
|
|
||||||
void ApplicationNeverCompleted(string connectionId);
|
void ApplicationNeverCompleted(string connectionId);
|
||||||
|
|
||||||
|
void RequestBodyStart(string connectionId, string traceIdentifier);
|
||||||
|
|
||||||
|
void RequestBodyDone(string connectionId, string traceIdentifier);
|
||||||
|
|
||||||
|
void RequestBodyMininumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,8 +5,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
public interface ITimeoutControl
|
public interface ITimeoutControl
|
||||||
{
|
{
|
||||||
|
bool TimedOut { get; }
|
||||||
|
|
||||||
void SetTimeout(long ticks, TimeoutAction timeoutAction);
|
void SetTimeout(long ticks, TimeoutAction timeoutAction);
|
||||||
void ResetTimeout(long ticks, TimeoutAction timeoutAction);
|
void ResetTimeout(long ticks, TimeoutAction timeoutAction);
|
||||||
void CancelTimeout();
|
void CancelTimeout();
|
||||||
|
|
||||||
|
void StartTimingReads();
|
||||||
|
void PauseTimingReads();
|
||||||
|
void ResumeTimingReads();
|
||||||
|
void StopTimingReads();
|
||||||
|
void BytesRead(int count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,6 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Summary description for KestrelTrace
|
|
||||||
/// </summary>
|
|
||||||
public class KestrelTrace : IKestrelTrace
|
public class KestrelTrace : IKestrelTrace
|
||||||
{
|
{
|
||||||
private static readonly Action<ILogger, string, Exception> _connectionStart =
|
private static readonly Action<ILogger, string, Exception> _connectionStart =
|
||||||
|
|
@ -57,6 +54,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
private static readonly Action<ILogger, string, Exception> _connectionRejected =
|
private static readonly Action<ILogger, string, Exception> _connectionRejected =
|
||||||
LoggerMessage.Define<string>(LogLevel.Warning, 24, @"Connection id ""{ConnectionId}"" rejected because the maximum number of concurrent connections has been reached.");
|
LoggerMessage.Define<string>(LogLevel.Warning, 24, @"Connection id ""{ConnectionId}"" rejected because the maximum number of concurrent connections has been reached.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, Exception> _requestBodyStart =
|
||||||
|
LoggerMessage.Define<string, string>(LogLevel.Debug, 25, @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": started reading request body.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, Exception> _requestBodyDone =
|
||||||
|
LoggerMessage.Define<string, string>(LogLevel.Debug, 26, @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": done reading request body.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, double, Exception> _requestBodyMinimumDataRateNotSatisfied =
|
||||||
|
LoggerMessage.Define<string, string, double>(LogLevel.Information, 27, @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": request body incoming data rate dropped below {Rate} bytes/second.");
|
||||||
|
|
||||||
protected readonly ILogger _logger;
|
protected readonly ILogger _logger;
|
||||||
|
|
||||||
public KestrelTrace(ILogger logger)
|
public KestrelTrace(ILogger logger)
|
||||||
|
|
@ -139,6 +145,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
_applicationNeverCompleted(_logger, connectionId, null);
|
_applicationNeverCompleted(_logger, connectionId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual void RequestBodyStart(string connectionId, string traceIdentifier)
|
||||||
|
{
|
||||||
|
_requestBodyStart(_logger, connectionId, traceIdentifier, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void RequestBodyDone(string connectionId, string traceIdentifier)
|
||||||
|
{
|
||||||
|
_requestBodyDone(_logger, connectionId, traceIdentifier, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestBodyMininumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate)
|
||||||
|
{
|
||||||
|
_requestBodyMinimumDataRateNotSatisfied(_logger, connectionId, traceIdentifier, rate, null);
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
public virtual void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||||
=> _logger.Log(logLevel, eventId, state, exception, formatter);
|
=> _logger.Log(logLevel, eventId, state, exception, formatter);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
{
|
{
|
||||||
|
|
@ -11,8 +13,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
// Matches the non-configurable default response buffer size for Kestrel in 1.0.0
|
// Matches the non-configurable default response buffer size for Kestrel in 1.0.0
|
||||||
private long? _maxResponseBufferSize = 64 * 1024;
|
private long? _maxResponseBufferSize = 64 * 1024;
|
||||||
|
|
||||||
// Matches the default client_max_body_size in nginx. Also large enough that most requests
|
// Matches the default client_max_body_size in nginx.
|
||||||
// should be under the limit.
|
// Also large enough that most requests should be under the limit.
|
||||||
private long? _maxRequestBufferSize = 1024 * 1024;
|
private long? _maxRequestBufferSize = 1024 * 1024;
|
||||||
|
|
||||||
// Matches the default large_client_header_buffers in nginx.
|
// Matches the default large_client_header_buffers in nginx.
|
||||||
|
|
@ -33,7 +35,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
|
|
||||||
private TimeSpan _requestHeadersTimeout = TimeSpan.FromSeconds(30);
|
private TimeSpan _requestHeadersTimeout = TimeSpan.FromSeconds(30);
|
||||||
|
|
||||||
// default to unlimited
|
// Unlimited connections are allowed by default.
|
||||||
private long? _maxConcurrentConnections = null;
|
private long? _maxConcurrentConnections = null;
|
||||||
private long? _maxConcurrentUpgradedConnections = null;
|
private long? _maxConcurrentUpgradedConnections = null;
|
||||||
|
|
||||||
|
|
@ -169,7 +171,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
public TimeSpan KeepAliveTimeout
|
public TimeSpan KeepAliveTimeout
|
||||||
{
|
{
|
||||||
get => _keepAliveTimeout;
|
get => _keepAliveTimeout;
|
||||||
set => _keepAliveTimeout = value;
|
set
|
||||||
|
{
|
||||||
|
if (value <= TimeSpan.Zero && value != Timeout.InfiniteTimeSpan)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.PositiveTimeSpanRequired);
|
||||||
|
}
|
||||||
|
_keepAliveTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -181,7 +190,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
public TimeSpan RequestHeadersTimeout
|
public TimeSpan RequestHeadersTimeout
|
||||||
{
|
{
|
||||||
get => _requestHeadersTimeout;
|
get => _requestHeadersTimeout;
|
||||||
set => _requestHeadersTimeout = value;
|
set
|
||||||
|
{
|
||||||
|
if (value <= TimeSpan.Zero && value != Timeout.InfiniteTimeSpan)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.PositiveTimeSpanRequired);
|
||||||
|
}
|
||||||
|
_requestHeadersTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -234,5 +250,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
_maxConcurrentUpgradedConnections = value;
|
_maxConcurrentUpgradedConnections = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the request body minimum data rate in bytes/second.
|
||||||
|
/// Setting this property to null indicates no minimum data rate should be enforced.
|
||||||
|
/// This limit has no effect on upgraded connections which are always unlimited.
|
||||||
|
/// This can be overridden per-request via <see cref="IHttpRequestBodyMinimumDataRateFeature"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Defaults to null.
|
||||||
|
/// </remarks>
|
||||||
|
public MinimumDataRate RequestBodyMinimumDataRate { get; set; } = new MinimumDataRate(rate: 1, gracePeriod: TimeSpan.FromSeconds(5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -977,7 +977,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
=> GetString("MaxRequestBodySizeCannotBeModifiedAfterRead");
|
=> GetString("MaxRequestBodySizeCannotBeModifiedAfterRead");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum request body size cannot be modified after the request has be upgraded.
|
/// The maximum request body size cannot be modified after the request has been upgraded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string MaxRequestBodySizeCannotBeModifiedForUpgradedRequests
|
internal static string MaxRequestBodySizeCannotBeModifiedForUpgradedRequests
|
||||||
{
|
{
|
||||||
|
|
@ -985,11 +985,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum request body size cannot be modified after the request has be upgraded.
|
/// The maximum request body size cannot be modified after the request has been upgraded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatMaxRequestBodySizeCannotBeModifiedForUpgradedRequests()
|
internal static string FormatMaxRequestBodySizeCannotBeModifiedForUpgradedRequests()
|
||||||
=> GetString("MaxRequestBodySizeCannotBeModifiedForUpgradedRequests");
|
=> GetString("MaxRequestBodySizeCannotBeModifiedForUpgradedRequests");
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// Value must be a non-negative TimeSpan.
|
||||||
|
/// </summary>
|
||||||
|
internal static string NonNegativeTimeSpanRequired
|
||||||
|
{
|
||||||
|
get => GetString("NonNegativeTimeSpanRequired");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Value must be a non-negative TimeSpan.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatNonNegativeTimeSpanRequired()
|
||||||
|
=> GetString("NonNegativeTimeSpanRequired");
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,324 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
{
|
||||||
|
public class FrameConnectionTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly PipeFactory _pipeFactory;
|
||||||
|
private readonly FrameConnectionContext _frameConnectionContext;
|
||||||
|
private readonly FrameConnection _frameConnection;
|
||||||
|
|
||||||
|
public FrameConnectionTests()
|
||||||
|
{
|
||||||
|
_pipeFactory = new PipeFactory();
|
||||||
|
|
||||||
|
_frameConnectionContext = new FrameConnectionContext
|
||||||
|
{
|
||||||
|
ConnectionId = "0123456789",
|
||||||
|
ConnectionAdapters = new List<IConnectionAdapter>(),
|
||||||
|
ConnectionInformation = new MockConnectionInformation
|
||||||
|
{
|
||||||
|
PipeFactory = _pipeFactory
|
||||||
|
},
|
||||||
|
FrameConnectionId = long.MinValue,
|
||||||
|
Input = _pipeFactory.Create(),
|
||||||
|
Output = _pipeFactory.Create(),
|
||||||
|
ServiceContext = new TestServiceContext
|
||||||
|
{
|
||||||
|
SystemClock = new SystemClock()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_frameConnection = new FrameConnection(_frameConnectionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_pipeFactory.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TimesOutWhenRequestBodyDoesNotSatisfyMinimumDataRate()
|
||||||
|
{
|
||||||
|
var requestBodyMinimumDataRate = 100;
|
||||||
|
var requestBodyGracePeriod = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
_frameConnectionContext.ServiceContext.ServerOptions.Limits.RequestBodyMinimumDataRate =
|
||||||
|
new MinimumDataRate(rate: requestBodyMinimumDataRate, gracePeriod: requestBodyGracePeriod);
|
||||||
|
|
||||||
|
var mockLogger = new Mock<IKestrelTrace>();
|
||||||
|
_frameConnectionContext.ServiceContext.Log = mockLogger.Object;
|
||||||
|
|
||||||
|
_frameConnection.CreateFrame(new DummyApplication(context => Task.CompletedTask), _frameConnectionContext.Input.Reader, _frameConnectionContext.Output);
|
||||||
|
_frameConnection.Frame.Reset();
|
||||||
|
|
||||||
|
// Initialize timestamp
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
_frameConnection.Tick(now);
|
||||||
|
|
||||||
|
_frameConnection.StartTimingReads();
|
||||||
|
|
||||||
|
// Tick after grace period w/ low data rate
|
||||||
|
now += requestBodyGracePeriod + TimeSpan.FromSeconds(1);
|
||||||
|
_frameConnection.BytesRead(1);
|
||||||
|
_frameConnection.Tick(now);
|
||||||
|
|
||||||
|
// Timed out
|
||||||
|
Assert.True(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(logger =>
|
||||||
|
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), requestBodyMinimumDataRate), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MinimumDataRateNotEnforcedDuringGracePeriod()
|
||||||
|
{
|
||||||
|
var requestBodyMinimumDataRate = 100;
|
||||||
|
var requestBodyGracePeriod = TimeSpan.FromSeconds(2);
|
||||||
|
|
||||||
|
_frameConnectionContext.ServiceContext.ServerOptions.Limits.RequestBodyMinimumDataRate =
|
||||||
|
new MinimumDataRate(rate: requestBodyMinimumDataRate, gracePeriod: requestBodyGracePeriod);
|
||||||
|
|
||||||
|
var mockLogger = new Mock<IKestrelTrace>();
|
||||||
|
_frameConnectionContext.ServiceContext.Log = mockLogger.Object;
|
||||||
|
|
||||||
|
_frameConnection.CreateFrame(new DummyApplication(context => Task.CompletedTask), _frameConnectionContext.Input.Reader, _frameConnectionContext.Output);
|
||||||
|
_frameConnection.Frame.Reset();
|
||||||
|
|
||||||
|
// Initialize timestamp
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
_frameConnection.Tick(now);
|
||||||
|
|
||||||
|
_frameConnection.StartTimingReads();
|
||||||
|
|
||||||
|
// Tick during grace period w/ low data rate
|
||||||
|
now += TimeSpan.FromSeconds(1);
|
||||||
|
_frameConnection.BytesRead(10);
|
||||||
|
_frameConnection.Tick(now);
|
||||||
|
|
||||||
|
// Not timed out
|
||||||
|
Assert.False(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(logger =>
|
||||||
|
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), requestBodyMinimumDataRate), Times.Never);
|
||||||
|
|
||||||
|
// Tick after grace period w/ low data rate
|
||||||
|
now += TimeSpan.FromSeconds(2);
|
||||||
|
_frameConnection.BytesRead(10);
|
||||||
|
_frameConnection.Tick(now);
|
||||||
|
|
||||||
|
// Timed out
|
||||||
|
Assert.True(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(logger =>
|
||||||
|
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), requestBodyMinimumDataRate), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataRateIsAveragedOverTimeSpentReadingRequestBody()
|
||||||
|
{
|
||||||
|
var requestBodyMinimumDataRate = 100;
|
||||||
|
var requestBodyGracePeriod = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
_frameConnectionContext.ServiceContext.ServerOptions.Limits.RequestBodyMinimumDataRate =
|
||||||
|
new MinimumDataRate(rate: requestBodyMinimumDataRate, gracePeriod: requestBodyGracePeriod);
|
||||||
|
|
||||||
|
var mockLogger = new Mock<IKestrelTrace>();
|
||||||
|
_frameConnectionContext.ServiceContext.Log = mockLogger.Object;
|
||||||
|
|
||||||
|
_frameConnection.CreateFrame(new DummyApplication(context => Task.CompletedTask), _frameConnectionContext.Input.Reader, _frameConnectionContext.Output);
|
||||||
|
_frameConnection.Frame.Reset();
|
||||||
|
|
||||||
|
// Initialize timestamp
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
_frameConnection.Tick(now);
|
||||||
|
|
||||||
|
_frameConnection.StartTimingReads();
|
||||||
|
|
||||||
|
// Tick after grace period to start enforcing minimum data rate
|
||||||
|
now += requestBodyGracePeriod;
|
||||||
|
_frameConnection.BytesRead(100);
|
||||||
|
_frameConnection.Tick(now);
|
||||||
|
|
||||||
|
// Data rate: 200 bytes/second
|
||||||
|
now += TimeSpan.FromSeconds(1);
|
||||||
|
_frameConnection.BytesRead(300);
|
||||||
|
_frameConnection.Tick(now);
|
||||||
|
|
||||||
|
// Not timed out
|
||||||
|
Assert.False(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(logger =>
|
||||||
|
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), requestBodyMinimumDataRate), Times.Never);
|
||||||
|
|
||||||
|
// Data rate: 150 bytes/second
|
||||||
|
now += TimeSpan.FromSeconds(1);
|
||||||
|
_frameConnection.BytesRead(50);
|
||||||
|
_frameConnection.Tick(now);
|
||||||
|
|
||||||
|
// Not timed out
|
||||||
|
Assert.False(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(logger =>
|
||||||
|
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), requestBodyMinimumDataRate), Times.Never);
|
||||||
|
|
||||||
|
// Data rate: 115 bytes/second
|
||||||
|
now += TimeSpan.FromSeconds(1);
|
||||||
|
_frameConnection.BytesRead(10);
|
||||||
|
_frameConnection.Tick(now);
|
||||||
|
|
||||||
|
// Not timed out
|
||||||
|
Assert.False(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(logger =>
|
||||||
|
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), requestBodyMinimumDataRate), Times.Never);
|
||||||
|
|
||||||
|
// Data rate: 50 bytes/second
|
||||||
|
now += TimeSpan.FromSeconds(6);
|
||||||
|
_frameConnection.BytesRead(40);
|
||||||
|
_frameConnection.Tick(now);
|
||||||
|
|
||||||
|
// Timed out
|
||||||
|
Assert.True(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(logger =>
|
||||||
|
logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), requestBodyMinimumDataRate), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PausedTimeDoesNotCountAgainstRequestBodyTimeout()
|
||||||
|
{
|
||||||
|
var requestBodyTimeout = TimeSpan.FromSeconds(5);
|
||||||
|
var systemClock = new MockSystemClock();
|
||||||
|
|
||||||
|
_frameConnectionContext.ServiceContext.ServerOptions.Limits.RequestBodyMinimumDataRate =
|
||||||
|
new MinimumDataRate(rate: 100, gracePeriod: TimeSpan.Zero);
|
||||||
|
_frameConnectionContext.ServiceContext.SystemClock = systemClock;
|
||||||
|
|
||||||
|
var mockLogger = new Mock<IKestrelTrace>();
|
||||||
|
_frameConnectionContext.ServiceContext.Log = mockLogger.Object;
|
||||||
|
|
||||||
|
_frameConnection.CreateFrame(new DummyApplication(context => Task.CompletedTask), _frameConnectionContext.Input.Reader, _frameConnectionContext.Output);
|
||||||
|
_frameConnection.Frame.Reset();
|
||||||
|
|
||||||
|
// Initialize timestamp
|
||||||
|
_frameConnection.Tick(systemClock.UtcNow);
|
||||||
|
|
||||||
|
_frameConnection.StartTimingReads();
|
||||||
|
|
||||||
|
// Tick at 1s, expected counted time is 1s, expected data rate is 400 bytes/second
|
||||||
|
systemClock.UtcNow += TimeSpan.FromSeconds(1);
|
||||||
|
_frameConnection.BytesRead(400);
|
||||||
|
_frameConnection.Tick(systemClock.UtcNow);
|
||||||
|
|
||||||
|
// Pause at 1.5s
|
||||||
|
systemClock.UtcNow += TimeSpan.FromSeconds(0.5);
|
||||||
|
_frameConnection.PauseTimingReads();
|
||||||
|
|
||||||
|
// Tick at 2s, expected counted time is 2s, expected data rate is 400 bytes/second
|
||||||
|
systemClock.UtcNow += TimeSpan.FromSeconds(0.5);
|
||||||
|
_frameConnection.Tick(systemClock.UtcNow);
|
||||||
|
|
||||||
|
// Tick at 6s, expected counted time is 2s, expected data rate is 400 bytes/second
|
||||||
|
systemClock.UtcNow += TimeSpan.FromSeconds(4);
|
||||||
|
_frameConnection.Tick(systemClock.UtcNow);
|
||||||
|
|
||||||
|
// Not timed out
|
||||||
|
Assert.False(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(
|
||||||
|
logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
|
||||||
|
Times.Never);
|
||||||
|
|
||||||
|
// Resume at 6.5s
|
||||||
|
systemClock.UtcNow += TimeSpan.FromSeconds(0.5);
|
||||||
|
_frameConnection.ResumeTimingReads();
|
||||||
|
|
||||||
|
// Tick at 8s, expected counted time is 4s, expected data rate is 100 bytes/second
|
||||||
|
systemClock.UtcNow += TimeSpan.FromSeconds(1.5);
|
||||||
|
_frameConnection.Tick(systemClock.UtcNow);
|
||||||
|
|
||||||
|
// Not timed out
|
||||||
|
Assert.False(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(
|
||||||
|
logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
|
||||||
|
Times.Never);
|
||||||
|
|
||||||
|
// Tick at 9s, expected counted time is 9s, expected data rate drops below 100 bytes/second
|
||||||
|
systemClock.UtcNow += TimeSpan.FromSeconds(1);
|
||||||
|
_frameConnection.Tick(systemClock.UtcNow);
|
||||||
|
|
||||||
|
// Timed out
|
||||||
|
Assert.True(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(
|
||||||
|
logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
|
||||||
|
Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NotPausedWhenResumeCalledBeforeNextTick()
|
||||||
|
{
|
||||||
|
var systemClock = new MockSystemClock();
|
||||||
|
|
||||||
|
_frameConnectionContext.ServiceContext.ServerOptions.Limits.RequestBodyMinimumDataRate =
|
||||||
|
new MinimumDataRate(rate: 100, gracePeriod: TimeSpan.Zero);
|
||||||
|
_frameConnectionContext.ServiceContext.SystemClock = systemClock;
|
||||||
|
|
||||||
|
var mockLogger = new Mock<IKestrelTrace>();
|
||||||
|
_frameConnectionContext.ServiceContext.Log = mockLogger.Object;
|
||||||
|
|
||||||
|
_frameConnection.CreateFrame(new DummyApplication(context => Task.CompletedTask), _frameConnectionContext.Input.Reader, _frameConnectionContext.Output);
|
||||||
|
_frameConnection.Frame.Reset();
|
||||||
|
|
||||||
|
// Initialize timestamp
|
||||||
|
_frameConnection.Tick(systemClock.UtcNow);
|
||||||
|
|
||||||
|
_frameConnection.StartTimingReads();
|
||||||
|
|
||||||
|
// Tick at 1s, expected counted time is 1s, expected data rate is 100 bytes/second
|
||||||
|
systemClock.UtcNow += TimeSpan.FromSeconds(1);
|
||||||
|
_frameConnection.BytesRead(100);
|
||||||
|
_frameConnection.Tick(systemClock.UtcNow);
|
||||||
|
|
||||||
|
// Not timed out
|
||||||
|
Assert.False(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(
|
||||||
|
logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
|
||||||
|
Times.Never);
|
||||||
|
|
||||||
|
// Pause at 1.25s
|
||||||
|
systemClock.UtcNow += TimeSpan.FromSeconds(0.25);
|
||||||
|
_frameConnection.PauseTimingReads();
|
||||||
|
|
||||||
|
// Resume at 1.5s
|
||||||
|
systemClock.UtcNow += TimeSpan.FromSeconds(0.25);
|
||||||
|
_frameConnection.ResumeTimingReads();
|
||||||
|
|
||||||
|
// Tick at 2s, expected counted time is 2s, expected data rate is 100 bytes/second
|
||||||
|
systemClock.UtcNow += TimeSpan.FromSeconds(0.5);
|
||||||
|
_frameConnection.BytesRead(100);
|
||||||
|
_frameConnection.Tick(systemClock.UtcNow);
|
||||||
|
|
||||||
|
// Not timed out
|
||||||
|
Assert.False(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(
|
||||||
|
logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
|
||||||
|
Times.Never);
|
||||||
|
|
||||||
|
// Tick at 3s, expected counted time is 3s, expected data rate drops below 100 bytes/second
|
||||||
|
systemClock.UtcNow += TimeSpan.FromSeconds(1);
|
||||||
|
_frameConnection.Tick(systemClock.UtcNow);
|
||||||
|
|
||||||
|
// Timed out
|
||||||
|
Assert.True(_frameConnection.TimedOut);
|
||||||
|
mockLogger.Verify(
|
||||||
|
logger => logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<double>()),
|
||||||
|
Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||||
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.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
|
@ -140,6 +141,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
Assert.NotEqual(nextId, secondId);
|
Assert.NotEqual(nextId, secondId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResetResetsRequestBodyMinimumDataRate()
|
||||||
|
{
|
||||||
|
_frame.RequestBodyMinimumDataRate = new MinimumDataRate(rate: 1, gracePeriod: TimeSpan.Zero);
|
||||||
|
|
||||||
|
_frame.Reset();
|
||||||
|
|
||||||
|
Assert.Equal(_serviceContext.ServerOptions.Limits.RequestBodyMinimumDataRate, _frame.RequestBodyMinimumDataRate);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TraceIdentifierCountsRequestsPerFrame()
|
public void TraceIdentifierCountsRequestsPerFrame()
|
||||||
{
|
{
|
||||||
|
|
@ -243,6 +254,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
Assert.Throws<InvalidOperationException>(() => ((IHttpResponseFeature)_frame).OnStarting(_ => TaskCache.CompletedTask, null));
|
Assert.Throws<InvalidOperationException>(() => ((IHttpResponseFeature)_frame).OnStarting(_ => TaskCache.CompletedTask, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(RequestBodyMinimumDataRateData))]
|
||||||
|
public void ConfiguringRequestBodyMinimumDataRateFeatureSetsRequestBodyMinimumDateRate(MinimumDataRate minimumDataRate)
|
||||||
|
{
|
||||||
|
((IFeatureCollection)_frame).Get<IHttpRequestBodyMinimumDataRateFeature>().MinimumDataRate = minimumDataRate;
|
||||||
|
|
||||||
|
Assert.Same(minimumDataRate, _frame.RequestBodyMinimumDataRate);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ResetResetsRequestHeaders()
|
public void ResetResetsRequestHeaders()
|
||||||
{
|
{
|
||||||
|
|
@ -844,6 +864,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TheoryData<TimeSpan> RequestBodyTimeoutDataValid => new TheoryData<TimeSpan>
|
||||||
|
{
|
||||||
|
TimeSpan.FromTicks(1),
|
||||||
|
TimeSpan.MaxValue,
|
||||||
|
Timeout.InfiniteTimeSpan,
|
||||||
|
TimeSpan.FromMilliseconds(-1) // Same as Timeout.InfiniteTimeSpan
|
||||||
|
};
|
||||||
|
|
||||||
|
public static TheoryData<TimeSpan> RequestBodyTimeoutDataInvalid => new TheoryData<TimeSpan>
|
||||||
|
{
|
||||||
|
TimeSpan.MinValue,
|
||||||
|
TimeSpan.FromTicks(-1),
|
||||||
|
TimeSpan.Zero
|
||||||
|
};
|
||||||
|
|
||||||
|
public static TheoryData<MinimumDataRate> RequestBodyMinimumDataRateData => new TheoryData<MinimumDataRate>
|
||||||
|
{
|
||||||
|
null,
|
||||||
|
new MinimumDataRate(rate: 1, gracePeriod: TimeSpan.Zero)
|
||||||
|
};
|
||||||
|
|
||||||
private class RequestHeadersWrapper : IHeaderDictionary
|
private class RequestHeadersWrapper : IHeaderDictionary
|
||||||
{
|
{
|
||||||
IHeaderDictionary _innerHeaders;
|
IHeaderDictionary _innerHeaders;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using System.Threading;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
@ -154,16 +154,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(0)]
|
[MemberData(nameof(TimeoutValidData))]
|
||||||
[InlineData(0.5)]
|
public void KeepAliveTimeoutValid(TimeSpan value)
|
||||||
[InlineData(2.1)]
|
|
||||||
[InlineData(2.5)]
|
|
||||||
[InlineData(2.9)]
|
|
||||||
public void KeepAliveTimeoutValid(double seconds)
|
|
||||||
{
|
{
|
||||||
var o = new KestrelServerLimits();
|
Assert.Equal(value, new KestrelServerLimits { KeepAliveTimeout = value }.KeepAliveTimeout);
|
||||||
o.KeepAliveTimeout = TimeSpan.FromSeconds(seconds);
|
}
|
||||||
Assert.Equal(seconds, o.KeepAliveTimeout.TotalSeconds);
|
|
||||||
|
[Fact]
|
||||||
|
public void KeepAliveTimeoutCanBeSetToInfinite()
|
||||||
|
{
|
||||||
|
Assert.Equal(TimeSpan.MaxValue, new KestrelServerLimits { KeepAliveTimeout = Timeout.InfiniteTimeSpan }.KeepAliveTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TimeoutInvalidData))]
|
||||||
|
public void KeepAliveTimeoutInvalid(TimeSpan value)
|
||||||
|
{
|
||||||
|
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => new KestrelServerLimits { KeepAliveTimeout = value });
|
||||||
|
|
||||||
|
Assert.Equal("value", exception.ParamName);
|
||||||
|
Assert.StartsWith(CoreStrings.PositiveTimeSpanRequired, exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -173,17 +183,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(0)]
|
[MemberData(nameof(TimeoutValidData))]
|
||||||
[InlineData(0.5)]
|
public void RequestHeadersTimeoutValid(TimeSpan value)
|
||||||
[InlineData(1.0)]
|
|
||||||
[InlineData(2.5)]
|
|
||||||
[InlineData(10)]
|
|
||||||
[InlineData(60)]
|
|
||||||
public void RequestHeadersTimeoutValid(double seconds)
|
|
||||||
{
|
{
|
||||||
var o = new KestrelServerLimits();
|
Assert.Equal(value, new KestrelServerLimits { RequestHeadersTimeout = value }.RequestHeadersTimeout);
|
||||||
o.RequestHeadersTimeout = TimeSpan.FromSeconds(seconds);
|
}
|
||||||
Assert.Equal(seconds, o.RequestHeadersTimeout.TotalSeconds);
|
|
||||||
|
[Fact]
|
||||||
|
public void RequestHeadersTimeoutCanBeSetToInfinite()
|
||||||
|
{
|
||||||
|
Assert.Equal(TimeSpan.MaxValue, new KestrelServerLimits { RequestHeadersTimeout = Timeout.InfiniteTimeSpan }.RequestHeadersTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TimeoutInvalidData))]
|
||||||
|
public void RequestHeadersTimeoutInvalid(TimeSpan value)
|
||||||
|
{
|
||||||
|
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => new KestrelServerLimits { RequestHeadersTimeout = value });
|
||||||
|
|
||||||
|
Assert.Equal("value", exception.ParamName);
|
||||||
|
Assert.StartsWith(CoreStrings.PositiveTimeSpanRequired, exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -272,5 +291,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new KestrelServerLimits().MaxRequestBodySize = value);
|
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new KestrelServerLimits().MaxRequestBodySize = value);
|
||||||
Assert.StartsWith(CoreStrings.NonNegativeNumberOrNullRequired, ex.Message);
|
Assert.StartsWith(CoreStrings.NonNegativeNumberOrNullRequired, ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RequestBodyMinimumDataRateDefault()
|
||||||
|
{
|
||||||
|
Assert.NotNull(new KestrelServerLimits().RequestBodyMinimumDataRate);
|
||||||
|
Assert.Equal(1, new KestrelServerLimits().RequestBodyMinimumDataRate.Rate);
|
||||||
|
Assert.Equal(TimeSpan.FromSeconds(5), new KestrelServerLimits().RequestBodyMinimumDataRate.GracePeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData<TimeSpan> TimeoutValidData => new TheoryData<TimeSpan>
|
||||||
|
{
|
||||||
|
TimeSpan.FromTicks(1),
|
||||||
|
TimeSpan.MaxValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static TheoryData<TimeSpan> TimeoutInfiniteData => new TheoryData<TimeSpan>
|
||||||
|
{
|
||||||
|
Timeout.InfiniteTimeSpan,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static TheoryData<TimeSpan> TimeoutInvalidData => new TheoryData<TimeSpan>
|
||||||
|
{
|
||||||
|
TimeSpan.MinValue,
|
||||||
|
TimeSpan.FromTicks(-1),
|
||||||
|
TimeSpan.Zero
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Threading;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
@ -100,9 +101,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
public void StartWithMaxRequestBufferSizeLessThanMaxRequestLineSizeThrows(long maxRequestBufferSize, int maxRequestLineSize)
|
public void StartWithMaxRequestBufferSizeLessThanMaxRequestLineSizeThrows(long maxRequestBufferSize, int maxRequestLineSize)
|
||||||
{
|
{
|
||||||
var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false };
|
var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false };
|
||||||
var options = new KestrelServerOptions();
|
var options = new KestrelServerOptions
|
||||||
options.Limits.MaxRequestBufferSize = maxRequestBufferSize;
|
{
|
||||||
options.Limits.MaxRequestLineSize = maxRequestLineSize;
|
Limits =
|
||||||
|
{
|
||||||
|
MaxRequestBufferSize = maxRequestBufferSize,
|
||||||
|
MaxRequestLineSize = maxRequestLineSize
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
using (var server = CreateServer(options, testLogger))
|
using (var server = CreateServer(options, testLogger))
|
||||||
{
|
{
|
||||||
|
|
@ -121,10 +127,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
public void StartWithMaxRequestBufferSizeLessThanMaxRequestHeadersTotalSizeThrows(long maxRequestBufferSize, int maxRequestHeadersTotalSize)
|
public void StartWithMaxRequestBufferSizeLessThanMaxRequestHeadersTotalSizeThrows(long maxRequestBufferSize, int maxRequestHeadersTotalSize)
|
||||||
{
|
{
|
||||||
var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false };
|
var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false };
|
||||||
var options = new KestrelServerOptions();
|
var options = new KestrelServerOptions
|
||||||
options.Limits.MaxRequestBufferSize = maxRequestBufferSize;
|
{
|
||||||
options.Limits.MaxRequestLineSize = (int)maxRequestBufferSize;
|
Limits =
|
||||||
options.Limits.MaxRequestHeadersTotalSize = maxRequestHeadersTotalSize;
|
{
|
||||||
|
MaxRequestBufferSize = maxRequestBufferSize,
|
||||||
|
MaxRequestLineSize = (int)maxRequestBufferSize,
|
||||||
|
MaxRequestHeadersTotalSize = maxRequestHeadersTotalSize
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
using (var server = CreateServer(options, testLogger))
|
using (var server = CreateServer(options, testLogger))
|
||||||
{
|
{
|
||||||
|
|
@ -146,7 +157,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StartWithNoTrasnportFactoryThrows()
|
public void StartWithNoTransportFactoryThrows()
|
||||||
{
|
{
|
||||||
var exception = Assert.Throws<ArgumentNullException>(() =>
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
||||||
new KestrelServer(Options.Create<KestrelServerOptions>(null), null, Mock.Of<ILoggerFactory>()));
|
new KestrelServer(Options.Create<KestrelServerOptions>(null), null, Mock.Of<ILoggerFactory>()));
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,12 @@ using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
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.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
|
||||||
using Xunit.Sdk;
|
using Xunit.Sdk;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
@ -30,7 +28,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.FrameContext);
|
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -54,7 +52,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.FrameContext);
|
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -76,7 +74,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.FrameContext);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -100,7 +98,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.FrameContext);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -124,7 +122,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.FrameContext);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -147,7 +145,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.FrameContext);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -166,7 +164,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.FrameContext);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -187,7 +185,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.FrameContext);
|
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -210,7 +208,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.FrameContext);
|
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -233,7 +231,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.FrameContext);
|
var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -251,7 +249,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.FrameContext);
|
var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -267,7 +265,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "8197" }, input.FrameContext);
|
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "8197" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -294,7 +292,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var ex = Assert.Throws<BadHttpRequestException>(() =>
|
var ex = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked, not-chunked" }, input.FrameContext));
|
MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked, not-chunked" }, input.Frame));
|
||||||
|
|
||||||
Assert.Equal(StatusCodes.Status400BadRequest, ex.StatusCode);
|
Assert.Equal(StatusCodes.Status400BadRequest, ex.StatusCode);
|
||||||
Assert.Equal(CoreStrings.FormatBadRequest_FinalTransferCodingNotChunked("chunked, not-chunked"), ex.Message);
|
Assert.Equal(CoreStrings.FormatBadRequest_FinalTransferCodingNotChunked("chunked, not-chunked"), ex.Message);
|
||||||
|
|
@ -308,9 +306,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
input.FrameContext.Method = method;
|
input.Frame.Method = method;
|
||||||
var ex = Assert.Throws<BadHttpRequestException>(() =>
|
var ex = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders(), input.FrameContext));
|
MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders(), input.Frame));
|
||||||
|
|
||||||
Assert.Equal(StatusCodes.Status411LengthRequired, ex.StatusCode);
|
Assert.Equal(StatusCodes.Status411LengthRequired, ex.StatusCode);
|
||||||
Assert.Equal(CoreStrings.FormatBadRequest_LengthRequired(method), ex.Message);
|
Assert.Equal(CoreStrings.FormatBadRequest_LengthRequired(method), ex.Message);
|
||||||
|
|
@ -324,9 +322,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
input.FrameContext.Method = method;
|
input.Frame.Method = method;
|
||||||
var ex = Assert.Throws<BadHttpRequestException>(() =>
|
var ex = Assert.Throws<BadHttpRequestException>(() =>
|
||||||
MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders(), input.FrameContext));
|
MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders(), input.Frame));
|
||||||
|
|
||||||
Assert.Equal(StatusCodes.Status400BadRequest, ex.StatusCode);
|
Assert.Equal(StatusCodes.Status400BadRequest, ex.StatusCode);
|
||||||
Assert.Equal(CoreStrings.FormatBadRequest_LengthRequiredHttp10(method), ex.Message);
|
Assert.Equal(CoreStrings.FormatBadRequest_LengthRequiredHttp10(method), ex.Message);
|
||||||
|
|
@ -338,7 +336,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "5" }, input.FrameContext);
|
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
|
||||||
|
|
||||||
input.Add("Hello");
|
input.Add("Hello");
|
||||||
|
|
||||||
using (var ms = new MemoryStream())
|
using (var ms = new MemoryStream())
|
||||||
|
|
@ -355,7 +354,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "5" }, input.FrameContext);
|
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
|
||||||
|
|
||||||
input.Add("Hello");
|
input.Add("Hello");
|
||||||
|
|
||||||
await body.ConsumeAsync();
|
await body.ConsumeAsync();
|
||||||
|
|
@ -402,7 +402,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, headers, input.FrameContext);
|
var body = MessageBody.For(HttpVersion.Http11, headers, input.Frame);
|
||||||
|
|
||||||
var copyToAsyncTask = body.CopyToAsync(mockDestination.Object);
|
var copyToAsyncTask = body.CopyToAsync(mockDestination.Object);
|
||||||
|
|
||||||
// The block returned by IncomingStart always has at least 2048 available bytes,
|
// The block returned by IncomingStart always has at least 2048 available bytes,
|
||||||
|
|
@ -448,7 +449,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderConnection = headerConnection }, input.FrameContext);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderConnection = headerConnection }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
|
@ -463,15 +464,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task StartAsyncDoesNotReturnAfterCancelingInput()
|
public async Task PumpAsyncDoesNotReturnAfterCancelingInput()
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.FrameContext);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
// Add some input and consume it to ensure StartAsync is in the loop
|
// Add some input and consume it to ensure PumpAsync is running
|
||||||
input.Add("a");
|
input.Add("a");
|
||||||
Assert.Equal(1, await stream.ReadAsync(new byte[1], 0, 1));
|
Assert.Equal(1, await stream.ReadAsync(new byte[1], 0, 1));
|
||||||
|
|
||||||
|
|
@ -485,15 +486,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task StartAsyncReturnsAfterCanceling()
|
public async Task PumpAsyncReturnsAfterCanceling()
|
||||||
{
|
{
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.FrameContext);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream();
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
// Add some input and consume it to ensure StartAsync is in the loop
|
// Add some input and consume it to ensure PumpAsync is running
|
||||||
input.Add("a");
|
input.Add("a");
|
||||||
Assert.Equal(1, await stream.ReadAsync(new byte[1], 0, 1));
|
Assert.Equal(1, await stream.ReadAsync(new byte[1], 0, 1));
|
||||||
|
|
||||||
|
|
@ -511,6 +512,198 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadAsyncThrowsOnTimeout()
|
||||||
|
{
|
||||||
|
using (var input = new TestInput())
|
||||||
|
{
|
||||||
|
var mockTimeoutControl = new Mock<ITimeoutControl>();
|
||||||
|
|
||||||
|
input.FrameContext.TimeoutControl = mockTimeoutControl.Object;
|
||||||
|
|
||||||
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
|
||||||
|
|
||||||
|
// Add some input and read it to start PumpAsync
|
||||||
|
input.Add("a");
|
||||||
|
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();
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadHttpRequestException>(() => body.ReadAsync(new ArraySegment<byte>(new byte[1])));
|
||||||
|
Assert.Equal(StatusCodes.Status408RequestTimeout, exception.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ConsumeAsyncThrowsOnTimeout()
|
||||||
|
{
|
||||||
|
using (var input = new TestInput())
|
||||||
|
{
|
||||||
|
var mockTimeoutControl = new Mock<ITimeoutControl>();
|
||||||
|
|
||||||
|
input.FrameContext.TimeoutControl = mockTimeoutControl.Object;
|
||||||
|
|
||||||
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
|
||||||
|
|
||||||
|
// Add some input and read it to start PumpAsync
|
||||||
|
input.Add("a");
|
||||||
|
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();
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadHttpRequestException>(() => body.ConsumeAsync());
|
||||||
|
Assert.Equal(StatusCodes.Status408RequestTimeout, exception.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CopyToAsyncThrowsOnTimeout()
|
||||||
|
{
|
||||||
|
using (var input = new TestInput())
|
||||||
|
{
|
||||||
|
var mockTimeoutControl = new Mock<ITimeoutControl>();
|
||||||
|
|
||||||
|
input.FrameContext.TimeoutControl = mockTimeoutControl.Object;
|
||||||
|
|
||||||
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
|
||||||
|
|
||||||
|
// Add some input and read it to start PumpAsync
|
||||||
|
input.Add("a");
|
||||||
|
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();
|
||||||
|
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
var exception = await Assert.ThrowsAsync<BadHttpRequestException>(() => body.CopyToAsync(ms));
|
||||||
|
Assert.Equal(StatusCodes.Status408RequestTimeout, exception.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LogsWhenStartsReadingRequestBody()
|
||||||
|
{
|
||||||
|
using (var input = new TestInput())
|
||||||
|
{
|
||||||
|
var mockLogger = new Mock<IKestrelTrace>();
|
||||||
|
input.Frame.ServiceContext.Log = mockLogger.Object;
|
||||||
|
input.Frame.ConnectionIdFeature = "ConnectionId";
|
||||||
|
input.Frame.TraceIdentifier = "RequestId";
|
||||||
|
|
||||||
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
|
||||||
|
var stream = new FrameRequestStream();
|
||||||
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
// Add some input and consume it to ensure PumpAsync is running
|
||||||
|
input.Add("a");
|
||||||
|
Assert.Equal(1, await stream.ReadAsync(new byte[1], 0, 1));
|
||||||
|
|
||||||
|
mockLogger.Verify(logger => logger.RequestBodyStart("ConnectionId", "RequestId"));
|
||||||
|
|
||||||
|
input.Fin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LogsWhenStopsReadingRequestBody()
|
||||||
|
{
|
||||||
|
using (var input = new TestInput())
|
||||||
|
{
|
||||||
|
var logEvent = new ManualResetEventSlim();
|
||||||
|
var mockLogger = new Mock<IKestrelTrace>();
|
||||||
|
mockLogger
|
||||||
|
.Setup(logger => logger.RequestBodyDone("ConnectionId", "RequestId"))
|
||||||
|
.Callback(() => logEvent.Set());
|
||||||
|
input.Frame.ServiceContext.Log = mockLogger.Object;
|
||||||
|
input.Frame.ConnectionIdFeature = "ConnectionId";
|
||||||
|
input.Frame.TraceIdentifier = "RequestId";
|
||||||
|
|
||||||
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
|
||||||
|
var stream = new FrameRequestStream();
|
||||||
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
|
// Add some input and consume it to ensure PumpAsync is running
|
||||||
|
input.Add("a");
|
||||||
|
Assert.Equal(1, await stream.ReadAsync(new byte[1], 0, 1));
|
||||||
|
|
||||||
|
input.Fin();
|
||||||
|
|
||||||
|
Assert.True(logEvent.Wait(TimeSpan.FromSeconds(10)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task OnlyEnforcesRequestBodyTimeoutAfterSending100Continue()
|
||||||
|
{
|
||||||
|
using (var input = new TestInput())
|
||||||
|
{
|
||||||
|
var produceContinueCalled = false;
|
||||||
|
var startTimingReadsCalledAfterProduceContinue = false;
|
||||||
|
|
||||||
|
var mockFrameControl = new Mock<IFrameControl>();
|
||||||
|
mockFrameControl
|
||||||
|
.Setup(frameControl => frameControl.ProduceContinue())
|
||||||
|
.Callback(() => produceContinueCalled = true);
|
||||||
|
input.Frame.FrameControl = mockFrameControl.Object;
|
||||||
|
|
||||||
|
var mockTimeoutControl = new Mock<ITimeoutControl>();
|
||||||
|
mockTimeoutControl
|
||||||
|
.Setup(timeoutControl => timeoutControl.StartTimingReads())
|
||||||
|
.Callback(() => startTimingReadsCalledAfterProduceContinue = produceContinueCalled);
|
||||||
|
|
||||||
|
input.FrameContext.TimeoutControl = mockTimeoutControl.Object;
|
||||||
|
|
||||||
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
|
||||||
|
|
||||||
|
// Add some input and read it to start PumpAsync
|
||||||
|
var readTask = body.ReadAsync(new ArraySegment<byte>(new byte[1]));
|
||||||
|
|
||||||
|
Assert.True(startTimingReadsCalledAfterProduceContinue);
|
||||||
|
|
||||||
|
input.Add("a");
|
||||||
|
await readTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DoesNotEnforceRequestBodyTimeoutOnUpgradeRequests()
|
||||||
|
{
|
||||||
|
using (var input = new TestInput())
|
||||||
|
{
|
||||||
|
var mockTimeoutControl = new Mock<ITimeoutControl>();
|
||||||
|
input.FrameContext.TimeoutControl = mockTimeoutControl.Object;
|
||||||
|
|
||||||
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame);
|
||||||
|
|
||||||
|
// Add some input and read it to start PumpAsync
|
||||||
|
input.Add("a");
|
||||||
|
Assert.Equal(1, await body.ReadAsync(new ArraySegment<byte>(new byte[1])));
|
||||||
|
|
||||||
|
input.Fin();
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<BadHttpRequestException>(async () => await body.ReadAsync(new ArraySegment<byte>(new byte[1])));
|
||||||
|
|
||||||
|
mockTimeoutControl.Verify(timeoutControl => timeoutControl.StartTimingReads(), Times.Never);
|
||||||
|
mockTimeoutControl.Verify(timeoutControl => timeoutControl.StopTimingReads(), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void AssertASCII(string expected, ArraySegment<byte> actual)
|
private void AssertASCII(string expected, ArraySegment<byte> actual)
|
||||||
{
|
{
|
||||||
var encoding = Encoding.ASCII;
|
var encoding = Encoding.ASCII;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
{
|
||||||
|
public class MinimumDataRateTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(double.Epsilon)]
|
||||||
|
[InlineData(double.MaxValue)]
|
||||||
|
public void RateValid(double value)
|
||||||
|
{
|
||||||
|
Assert.Equal(value, new MinimumDataRate(rate: value, gracePeriod: TimeSpan.Zero).Rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(double.MinValue)]
|
||||||
|
[InlineData(0)]
|
||||||
|
public void RateInvalid(double value)
|
||||||
|
{
|
||||||
|
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => new MinimumDataRate(rate: value, gracePeriod: TimeSpan.Zero));
|
||||||
|
|
||||||
|
Assert.Equal("rate", exception.ParamName);
|
||||||
|
Assert.StartsWith(CoreStrings.PositiveNumberRequired, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(GracePeriodValidData))]
|
||||||
|
public void GracePeriodValid(TimeSpan value)
|
||||||
|
{
|
||||||
|
Assert.Equal(value, new MinimumDataRate(rate: 1, gracePeriod: value).GracePeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(GracePeriodInvalidData))]
|
||||||
|
public void GracePeriodInvalid(TimeSpan value)
|
||||||
|
{
|
||||||
|
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => new MinimumDataRate(rate: 1, gracePeriod: value));
|
||||||
|
|
||||||
|
Assert.Equal("gracePeriod", exception.ParamName);
|
||||||
|
Assert.StartsWith(CoreStrings.NonNegativeTimeSpanRequired, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData<TimeSpan> GracePeriodValidData => new TheoryData<TimeSpan>
|
||||||
|
{
|
||||||
|
TimeSpan.Zero,
|
||||||
|
TimeSpan.FromTicks(1),
|
||||||
|
TimeSpan.MaxValue
|
||||||
|
};
|
||||||
|
|
||||||
|
public static TheoryData<TimeSpan> GracePeriodInvalidData => new TheoryData<TimeSpan>
|
||||||
|
{
|
||||||
|
TimeSpan.MinValue,
|
||||||
|
TimeSpan.FromTicks(-1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,21 +2,16 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
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.Server.Kestrel.Internal.System.IO.Pipelines;
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
|
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Internal;
|
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
class TestInput : IFrameControl, IDisposable
|
class TestInput : IDisposable
|
||||||
{
|
{
|
||||||
private MemoryPool _memoryPool;
|
private MemoryPool _memoryPool;
|
||||||
private PipeFactory _pipelineFactory;
|
private PipeFactory _pipelineFactory;
|
||||||
|
|
@ -27,23 +22,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
_pipelineFactory = new PipeFactory();
|
_pipelineFactory = new PipeFactory();
|
||||||
Pipe = _pipelineFactory.Create();
|
Pipe = _pipelineFactory.Create();
|
||||||
|
|
||||||
FrameContext = new Frame<object>(null, new FrameContext
|
FrameContext = new FrameContext
|
||||||
{
|
{
|
||||||
ServiceContext = new TestServiceContext(),
|
ServiceContext = new TestServiceContext(),
|
||||||
Input = Pipe.Reader,
|
Input = Pipe.Reader,
|
||||||
ConnectionInformation = new MockConnectionInformation
|
ConnectionInformation = new MockConnectionInformation
|
||||||
{
|
{
|
||||||
PipeFactory = _pipelineFactory
|
PipeFactory = _pipelineFactory
|
||||||
}
|
},
|
||||||
});
|
TimeoutControl = Mock.Of<ITimeoutControl>()
|
||||||
FrameContext.FrameControl = this;
|
};
|
||||||
|
|
||||||
|
Frame = new Frame<object>(null, FrameContext);
|
||||||
|
Frame.FrameControl = Mock.Of<IFrameControl>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPipe Pipe { get; }
|
public IPipe Pipe { get; }
|
||||||
|
|
||||||
public PipeFactory PipeFactory => _pipelineFactory;
|
public PipeFactory PipeFactory => _pipelineFactory;
|
||||||
|
|
||||||
public Frame FrameContext { get; set; }
|
public FrameContext FrameContext { get; }
|
||||||
|
|
||||||
|
public Frame Frame { get; set; }
|
||||||
|
|
||||||
public void Add(string text)
|
public void Add(string text)
|
||||||
{
|
{
|
||||||
|
|
@ -56,38 +56,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
Pipe.Writer.Complete();
|
Pipe.Writer.Complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ProduceContinue()
|
public void Cancel()
|
||||||
{
|
{
|
||||||
}
|
Pipe.Reader.CancelPendingRead();
|
||||||
|
|
||||||
public void Pause()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Resume()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void End(ProduceEndType endType)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Abort()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write(ArraySegment<byte> data, Action<Exception, object> callback, object state)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Task IFrameControl.WriteAsync(ArraySegment<byte> data, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return TaskCache.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task IFrameControl.FlushAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return TaskCache.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -96,7 +97,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
cts.CancelAfter(LongDelay);
|
cts.CancelAfter(LongDelay);
|
||||||
|
|
||||||
await connection.Send(
|
await connection.Send(
|
||||||
"POST / HTTP/1.1",
|
"POST /consume HTTP/1.1",
|
||||||
"Host:",
|
"Host:",
|
||||||
"Transfer-Encoding: chunked",
|
"Transfer-Encoding: chunked",
|
||||||
"",
|
"",
|
||||||
|
|
@ -193,7 +194,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
ServerOptions =
|
ServerOptions =
|
||||||
{
|
{
|
||||||
AddServerHeader = false,
|
AddServerHeader = false,
|
||||||
Limits = { KeepAliveTimeout = KeepAliveTimeout }
|
Limits =
|
||||||
|
{
|
||||||
|
KeepAliveTimeout = KeepAliveTimeout,
|
||||||
|
RequestBodyMinimumDataRate = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -201,6 +206,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
private async Task App(HttpContext httpContext, CancellationToken longRunningCt, CancellationToken upgradeCt)
|
private async Task App(HttpContext httpContext, CancellationToken longRunningCt, CancellationToken upgradeCt)
|
||||||
{
|
{
|
||||||
var ct = httpContext.RequestAborted;
|
var ct = httpContext.RequestAborted;
|
||||||
|
var responseStream = httpContext.Response.Body;
|
||||||
|
var responseBytes = Encoding.ASCII.GetBytes("hello, world");
|
||||||
|
|
||||||
if (httpContext.Request.Path == "/longrunning")
|
if (httpContext.Request.Path == "/longrunning")
|
||||||
{
|
{
|
||||||
|
|
@ -208,8 +215,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
{
|
{
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
await httpContext.Response.WriteAsync("hello, world");
|
|
||||||
}
|
}
|
||||||
else if (httpContext.Request.Path == "/upgrade")
|
else if (httpContext.Request.Path == "/upgrade")
|
||||||
{
|
{
|
||||||
|
|
@ -220,14 +225,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
await Task.Delay(LongDelay);
|
await Task.Delay(LongDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseBytes = Encoding.ASCII.GetBytes("hello, world");
|
responseStream = stream;
|
||||||
await stream.WriteAsync(responseBytes, 0, responseBytes.Length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (httpContext.Request.Path == "/consume")
|
||||||
{
|
{
|
||||||
await httpContext.Response.WriteAsync("hello, world");
|
var buffer = new byte[1024];
|
||||||
|
while (await httpContext.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await responseStream.WriteAsync(responseBytes, 0, responseBytes.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ReceiveResponse(TestConnection connection)
|
private async Task ReceiveResponse(TestConnection connection)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
|
@ -274,6 +273,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
{
|
{
|
||||||
options.Limits.MaxRequestHeadersTotalSize = (int)maxRequestBufferSize;
|
options.Limits.MaxRequestHeadersTotalSize = (int)maxRequestBufferSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.Limits.RequestBodyMinimumDataRate = null;
|
||||||
})
|
})
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.Configure(app => app.Run(async context =>
|
.Configure(app => app.Run(async context =>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
// 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 System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
|
{
|
||||||
|
public class RequestBodyTimeoutTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task RequestTimesOutWhenRequestBodyNotReceivedAtDesiredMinimumRate()
|
||||||
|
{
|
||||||
|
var minimumDataRateGracePeriod = TimeSpan.FromSeconds(5);
|
||||||
|
var systemClock = new MockSystemClock();
|
||||||
|
var serviceContext = new TestServiceContext
|
||||||
|
{
|
||||||
|
SystemClock = systemClock,
|
||||||
|
DateHeaderValueManager = new DateHeaderValueManager(systemClock)
|
||||||
|
};
|
||||||
|
|
||||||
|
var appRunningEvent = new ManualResetEventSlim();
|
||||||
|
|
||||||
|
using (var server = new TestServer(context =>
|
||||||
|
{
|
||||||
|
context.Features.Get<IHttpRequestBodyMinimumDataRateFeature>().MinimumDataRate =
|
||||||
|
new MinimumDataRate(rate: 1, gracePeriod: minimumDataRateGracePeriod);
|
||||||
|
|
||||||
|
appRunningEvent.Set();
|
||||||
|
return context.Request.Body.ReadAsync(new byte[1], 0, 1);
|
||||||
|
}, serviceContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"POST / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"Content-Length: 1",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
Assert.True(appRunningEvent.Wait(TimeSpan.FromSeconds(10)));
|
||||||
|
systemClock.UtcNow += minimumDataRateGracePeriod + TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 408 Request Timeout",
|
||||||
|
"");
|
||||||
|
await connection.ReceiveForcedEnd(
|
||||||
|
"Connection: close",
|
||||||
|
$"Date: {serviceContext.DateHeaderValue}",
|
||||||
|
"Content-Length: 0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RequestTimesWhenNotDrainedWithinDrainTimeoutPeriod()
|
||||||
|
{
|
||||||
|
// This test requires a real clock since we can't control when the drain timeout is set
|
||||||
|
var systemClock = new SystemClock();
|
||||||
|
var serviceContext = new TestServiceContext
|
||||||
|
{
|
||||||
|
SystemClock = systemClock,
|
||||||
|
DateHeaderValueManager = new DateHeaderValueManager(systemClock)
|
||||||
|
};
|
||||||
|
|
||||||
|
var appRunningEvent = new ManualResetEventSlim();
|
||||||
|
|
||||||
|
using (var server = new TestServer(context =>
|
||||||
|
{
|
||||||
|
context.Features.Get<IHttpRequestBodyMinimumDataRateFeature>().MinimumDataRate = null;
|
||||||
|
|
||||||
|
appRunningEvent.Set();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}, serviceContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"POST / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"Content-Length: 1",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
Assert.True(appRunningEvent.Wait(TimeSpan.FromSeconds(10)));
|
||||||
|
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 408 Request Timeout",
|
||||||
|
"Connection: close",
|
||||||
|
"");
|
||||||
|
await connection.ReceiveStartsWith(
|
||||||
|
"Date: ");
|
||||||
|
await connection.ReceiveForcedEnd(
|
||||||
|
"Content-Length: 0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ConnectionClosedEvenIfAppSwallowsException()
|
||||||
|
{
|
||||||
|
var minimumDataRateGracePeriod = TimeSpan.FromSeconds(5);
|
||||||
|
var systemClock = new MockSystemClock();
|
||||||
|
var serviceContext = new TestServiceContext
|
||||||
|
{
|
||||||
|
SystemClock = systemClock,
|
||||||
|
DateHeaderValueManager = new DateHeaderValueManager(systemClock)
|
||||||
|
};
|
||||||
|
|
||||||
|
var appRunningEvent = new ManualResetEventSlim();
|
||||||
|
var exceptionSwallowedEvent = new ManualResetEventSlim();
|
||||||
|
|
||||||
|
using (var server = new TestServer(async context =>
|
||||||
|
{
|
||||||
|
context.Features.Get<IHttpRequestBodyMinimumDataRateFeature>().MinimumDataRate =
|
||||||
|
new MinimumDataRate(rate: 1, gracePeriod: minimumDataRateGracePeriod);
|
||||||
|
|
||||||
|
appRunningEvent.Set();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await context.Request.Body.ReadAsync(new byte[1], 0, 1);
|
||||||
|
}
|
||||||
|
catch (BadHttpRequestException ex) when (ex.StatusCode == 408)
|
||||||
|
{
|
||||||
|
exceptionSwallowedEvent.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = "hello, world";
|
||||||
|
context.Response.ContentLength = response.Length;
|
||||||
|
await context.Response.WriteAsync("hello, world");
|
||||||
|
}, serviceContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"POST / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"Content-Length: 1",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
Assert.True(appRunningEvent.Wait(TimeSpan.FromSeconds(10)));
|
||||||
|
systemClock.UtcNow += minimumDataRateGracePeriod + TimeSpan.FromSeconds(1);
|
||||||
|
Assert.True(exceptionSwallowedEvent.Wait(TimeSpan.FromSeconds(10)));
|
||||||
|
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
"");
|
||||||
|
await connection.ReceiveForcedEnd(
|
||||||
|
$"Date: {serviceContext.DateHeaderValue}",
|
||||||
|
"Content-Length: 12",
|
||||||
|
"",
|
||||||
|
"hello, world");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -103,7 +103,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
ServerOptions =
|
ServerOptions =
|
||||||
{
|
{
|
||||||
AddServerHeader = false,
|
AddServerHeader = false,
|
||||||
Limits = { RequestHeadersTimeout = RequestHeadersTimeout }
|
Limits =
|
||||||
|
{
|
||||||
|
RequestHeadersTimeout = RequestHeadersTimeout,
|
||||||
|
RequestBodyMinimumDataRate = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,9 +59,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
Assert.True(bufferLength % 256 == 0, $"{nameof(bufferLength)} must be evenly divisible by 256");
|
Assert.True(bufferLength % 256 == 0, $"{nameof(bufferLength)} must be evenly divisible by 256");
|
||||||
|
|
||||||
var builder = new WebHostBuilder()
|
var builder = new WebHostBuilder()
|
||||||
.UseKestrel(o =>
|
.UseKestrel(options =>
|
||||||
{
|
{
|
||||||
o.Limits.MaxRequestBodySize = contentLength;
|
options.Limits.MaxRequestBodySize = contentLength;
|
||||||
|
options.Limits.RequestBodyMinimumDataRate = null;
|
||||||
})
|
})
|
||||||
.UseUrls("http://127.0.0.1:0/")
|
.UseUrls("http://127.0.0.1:0/")
|
||||||
.Configure(app =>
|
.Configure(app =>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks
|
||||||
{
|
{
|
||||||
public class MockTimeoutControl : ITimeoutControl
|
public class MockTimeoutControl : ITimeoutControl
|
||||||
{
|
{
|
||||||
|
public bool TimedOut { get; }
|
||||||
|
|
||||||
public void CancelTimeout()
|
public void CancelTimeout()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -18,5 +20,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks
|
||||||
public void SetTimeout(long ticks, TimeoutAction timeoutAction)
|
public void SetTimeout(long ticks, TimeoutAction timeoutAction)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void StartTimingReads()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopTimingReads()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PauseTimingReads()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResumeTimingReads()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BytesRead(int count)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,5 +36,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
public void RequestProcessingError(string connectionId, Exception ex) { }
|
public void RequestProcessingError(string connectionId, Exception ex) { }
|
||||||
public void HeartbeatSlow(TimeSpan interval, DateTimeOffset now) { }
|
public void HeartbeatSlow(TimeSpan interval, DateTimeOffset now) { }
|
||||||
public void ApplicationNeverCompleted(string connectionId) { }
|
public void ApplicationNeverCompleted(string connectionId) { }
|
||||||
|
public void RequestBodyStart(string connectionId, string traceIdentifier) { }
|
||||||
|
public void RequestBodyDone(string connectionId, string traceIdentifier) { }
|
||||||
|
public void RequestBodyMininumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,25 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Testing
|
namespace Microsoft.AspNetCore.Testing
|
||||||
{
|
{
|
||||||
public class MockSystemClock : ISystemClock
|
public class MockSystemClock : ISystemClock
|
||||||
{
|
{
|
||||||
private DateTimeOffset _utcNow = DateTimeOffset.Now;
|
private long _utcNowTicks = DateTimeOffset.UtcNow.Ticks;
|
||||||
|
|
||||||
public DateTimeOffset UtcNow
|
public DateTimeOffset UtcNow
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
UtcNowCalled++;
|
UtcNowCalled++;
|
||||||
return _utcNow;
|
return new DateTimeOffset(Interlocked.Read(ref _utcNowTicks), TimeSpan.Zero);
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_utcNow = value;
|
Interlocked.Exchange(ref _utcNowTicks, value.Ticks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
SystemClock = new MockSystemClock();
|
SystemClock = new MockSystemClock();
|
||||||
DateHeaderValueManager = new DateHeaderValueManager(SystemClock);
|
DateHeaderValueManager = new DateHeaderValueManager(SystemClock);
|
||||||
ConnectionManager = new FrameConnectionManager(Log, ResourceCounter.Unlimited, ResourceCounter.Unlimited);
|
ConnectionManager = new FrameConnectionManager(Log, ResourceCounter.Unlimited, ResourceCounter.Unlimited);
|
||||||
DateHeaderValue = DateHeaderValueManager.GetDateHeaderValues().String;
|
|
||||||
HttpParserFactory = frameAdapter => new HttpParser<FrameAdapter>(frameAdapter.Frame.ServiceContext.Log.IsEnabled(LogLevel.Information));
|
HttpParserFactory = frameAdapter => new HttpParser<FrameAdapter>(frameAdapter.Frame.ServiceContext.Log.IsEnabled(LogLevel.Information));
|
||||||
ServerOptions = new KestrelServerOptions
|
ServerOptions = new KestrelServerOptions
|
||||||
{
|
{
|
||||||
|
|
@ -39,6 +38,6 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
|
|
||||||
public ILoggerFactory LoggerFactory { get; }
|
public ILoggerFactory LoggerFactory { get; }
|
||||||
|
|
||||||
public string DateHeaderValue { get; }
|
public string DateHeaderValue => DateHeaderValueManager.GetDateHeaderValues().String;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||||
|
|
||||||
namespace CodeGenerator
|
namespace CodeGenerator
|
||||||
{
|
{
|
||||||
|
|
@ -45,12 +46,13 @@ namespace CodeGenerator
|
||||||
typeof(ITlsConnectionFeature),
|
typeof(ITlsConnectionFeature),
|
||||||
typeof(IHttpWebSocketFeature),
|
typeof(IHttpWebSocketFeature),
|
||||||
typeof(ISessionFeature),
|
typeof(ISessionFeature),
|
||||||
typeof(IHttpMaxRequestBodySizeFeature)
|
typeof(IHttpMaxRequestBodySizeFeature),
|
||||||
|
typeof(IHttpRequestBodyMinimumDataRateFeature),
|
||||||
};
|
};
|
||||||
|
|
||||||
var rareFeatures = new[]
|
var rareFeatures = new[]
|
||||||
{
|
{
|
||||||
typeof(IHttpSendFileFeature)
|
typeof(IHttpSendFileFeature),
|
||||||
};
|
};
|
||||||
|
|
||||||
var allFeatures = alwaysFeatures.Concat(commonFeatures).Concat(sometimesFeatures).Concat(rareFeatures);
|
var allFeatures = alwaysFeatures.Concat(commonFeatures).Concat(sometimesFeatures).Concat(rareFeatures);
|
||||||
|
|
@ -65,7 +67,8 @@ namespace CodeGenerator
|
||||||
typeof(IHttpRequestIdentifierFeature),
|
typeof(IHttpRequestIdentifierFeature),
|
||||||
typeof(IHttpRequestLifetimeFeature),
|
typeof(IHttpRequestLifetimeFeature),
|
||||||
typeof(IHttpConnectionFeature),
|
typeof(IHttpConnectionFeature),
|
||||||
typeof(IHttpMaxRequestBodySizeFeature)
|
typeof(IHttpMaxRequestBodySizeFeature),
|
||||||
|
typeof(IHttpRequestBodyMinimumDataRateFeature),
|
||||||
};
|
};
|
||||||
|
|
||||||
return $@"// Copyright (c) .NET Foundation. All rights reserved.
|
return $@"// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue