Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Commit

Permalink
Add HTTP/2 request body data rate limit (#3051)
Browse files Browse the repository at this point in the history
  • Loading branch information
halter73 authored Oct 26, 2018
1 parent d9aba75 commit 395b681
Show file tree
Hide file tree
Showing 27 changed files with 752 additions and 256 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features
{
/// <summary>
/// Feature to set the minimum data rate at which the the request body must be sent by the client.
/// This feature is not available for HTTP/2 requests. Instead, use <see cref="KestrelServerLimits.MinRequestBodyDataRate"/>
/// for server-wide configuration which applies to both HTTP/2 and HTTP/1.x.
/// </summary>
public interface IHttpMinRequestBodyDataRateFeature
{
/// <summary>
/// The minimum data rate in bytes/second at which the request body must be sent by the client.
/// 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 feature is not available for HTTP/2 requests. Instead, use <see cref="KestrelServerLimits.MinRequestBodyDataRate"/>
/// for server-wide configuration which applies to both HTTP/2 and HTTP/1.x.
/// </summary>
MinDataRate MinDataRate { get; set; }
}
Expand Down
4 changes: 4 additions & 0 deletions src/Kestrel.Core/Features/IHttpMinResponseDataRateFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features
{
/// <summary>
/// Feature to set the minimum data rate at which the response must be received by the client.
/// This feature is not available for HTTP/2 requests. Instead, use <see cref="KestrelServerLimits.MinResponseDataRate"/>
/// for server-wide configuration which applies to both HTTP/2 and HTTP/1.x.
/// </summary>
public interface IHttpMinResponseDataRateFeature
{
/// <summary>
/// The minimum data rate in bytes/second at which the response must be received by the client.
/// 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 feature is not available for HTTP/2 requests. Instead, use <see cref="KestrelServerLimits.MinResponseDataRate"/>
/// for server-wide configuration which applies to both HTTP/2 and HTTP/1.x.
/// </summary>
MinDataRate MinDataRate { get; set; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public partial class Http1Connection : IHttpUpgradeFeature
public partial class Http1Connection : IHttpUpgradeFeature,
IHttpMinRequestBodyDataRateFeature,
IHttpMinResponseDataRateFeature
{
bool IHttpUpgradeFeature.IsUpgradableRequest => IsUpgradableRequest;

Expand Down Expand Up @@ -44,5 +46,17 @@ async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()

return _streams.Upgrade();
}

MinDataRate IHttpMinRequestBodyDataRateFeature.MinDataRate
{
get => MinRequestBodyDataRate;
set => MinRequestBodyDataRate = value;
}

MinDataRate IHttpMinResponseDataRateFeature.MinDataRate
{
get => MinResponseDataRate;
set => MinResponseDataRate = value;
}
}
}
16 changes: 14 additions & 2 deletions src/Kestrel.Core/Internal/Http/Http1Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,14 @@ public Http1Connection(HttpConnectionContext context)

public PipeReader Input => _context.Transport.Input;

public ITimeoutControl TimeoutControl => _context.TimeoutControl;
public bool RequestTimedOut => _requestTimedOut;

public override bool IsUpgradableRequest => _upgradeAvailable;

public MinDataRate MinRequestBodyDataRate { get; set; }

public MinDataRate MinResponseDataRate { get; set; }

protected override void OnRequestProcessingEnded()
{
Input.Complete();
Expand Down Expand Up @@ -125,6 +128,12 @@ public void SendTimeoutResponse()
public void HandleRequestHeadersTimeout()
=> SendTimeoutResponse();

public void HandleReadDataRateTimeout()
{
Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, TraceIdentifier, MinRequestBodyDataRate.BytesPerSecond);
SendTimeoutResponse();
}

public void ParseRequest(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
{
consumed = buffer.Start;
Expand Down Expand Up @@ -423,13 +432,16 @@ private void ValidateNonOriginHostHeader(string hostText)

protected override void OnReset()
{
ResetIHttpUpgradeFeature();
ResetHttp1Features();

_requestTimedOut = false;
_requestTargetForm = HttpRequestTarget.Unknown;
_absoluteRequestTarget = null;
_remainingRequestHeadersBytesAllowed = ServerOptions.Limits.MaxRequestHeadersTotalSize + 2;
_requestCount++;

MinRequestBodyDataRate = ServerOptions.Limits.MinRequestBodyDataRate;
MinResponseDataRate = ServerOptions.Limits.MinResponseDataRate;
}

protected override void OnRequestProcessingEnding()
Expand Down
75 changes: 3 additions & 72 deletions src/Kestrel.Core/Internal/Http/Http1MessageBody.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ public abstract class Http1MessageBody : MessageBody

private volatile bool _canceled;
private Task _pumpTask;
private bool _timingReads;

protected Http1MessageBody(Http1Connection context)
: base(context)
: base(context, context.MinRequestBodyDataRate)
{
_context = context;
}
Expand All @@ -39,8 +38,6 @@ private async Task PumpAsync()
TryProduceContinue();
}

TryStartTimingReads();

while (true)
{
var result = await awaitable;
Expand All @@ -66,22 +63,7 @@ private async Task PumpAsync()
bool done;
done = Read(readableBuffer, _context.RequestBodyPipe.Writer, out consumed, out examined);

var writeAwaitable = _context.RequestBodyPipe.Writer.FlushAsync();
var backpressure = false;

if (!writeAwaitable.IsCompleted)
{
// Backpressure, stop controlling incoming data rate until data is read.
backpressure = true;
TryPauseTimingReads();
}

await writeAwaitable;

if (backpressure)
{
TryResumeTimingReads();
}
await _context.RequestBodyPipe.Writer.FlushAsync();

if (done)
{
Expand Down Expand Up @@ -109,7 +91,6 @@ private async Task PumpAsync()

BadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent);
}

}
finally
{
Expand All @@ -126,11 +107,10 @@ private async Task PumpAsync()
finally
{
_context.RequestBodyPipe.Writer.Complete(error);
TryStopTimingReads();
}
}

public override Task StopAsync()
protected override Task OnStopAsync()
{
if (!_context.HasStartedConsumingRequestBody)
{
Expand Down Expand Up @@ -219,8 +199,6 @@ private async Task OnConsumeAsyncAwaited()

protected void Copy(ReadOnlySequence<byte> readableBuffer, PipeWriter writableBuffer)
{
_context.TimeoutControl.BytesRead(readableBuffer.Length);

if (readableBuffer.IsSingleSegment)
{
writableBuffer.Write(readableBuffer.First.Span);
Expand All @@ -244,53 +222,6 @@ protected virtual bool Read(ReadOnlySequence<byte> readableBuffer, PipeWriter wr
throw new NotImplementedException();
}

private void TryStartTimingReads()
{
if (!RequestUpgrade)
{
Log.RequestBodyStart(_context.ConnectionIdFeature, _context.TraceIdentifier);

// REVIEW: This makes it no longer effective to change the min rate after the app starts reading.
// Is this OK? Should we throw from the MinRequestBodyDataRate setter in this case?
var minRate = _context.MinRequestBodyDataRate;

if (minRate != null)
{
_timingReads = true;
_context.TimeoutControl.StartTimingReads(minRate);
}
}
}

private void TryPauseTimingReads()
{
if (_timingReads)
{
_context.TimeoutControl.PauseTimingReads();
}
}

private void TryResumeTimingReads()
{
if (_timingReads)
{
_context.TimeoutControl.ResumeTimingReads();
}
}

private void TryStopTimingReads()
{
if (!RequestUpgrade)
{
Log.RequestBodyDone(_context.ConnectionIdFeature, _context.TraceIdentifier);

if (_timingReads)
{
_context.TimeoutControl.StopTimingReads();
}
}
}

public static MessageBody For(
HttpVersion httpVersion,
HttpRequestHeaders headers,
Expand Down
20 changes: 4 additions & 16 deletions src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ public partial class HttpProtocol : IHttpRequestFeature,
IHttpRequestLifetimeFeature,
IHttpRequestIdentifierFeature,
IHttpBodyControlFeature,
IHttpMaxRequestBodySizeFeature,
IHttpMinRequestBodyDataRateFeature,
IHttpMinResponseDataRateFeature
IHttpMaxRequestBodySizeFeature
{
// NOTE: When feature interfaces are added to or removed from this HttpProtocol class implementation,
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
Expand Down Expand Up @@ -192,21 +190,11 @@ bool IHttpBodyControlFeature.AllowSynchronousIO
}
}

MinDataRate IHttpMinRequestBodyDataRateFeature.MinDataRate
{
get => MinRequestBodyDataRate;
set => MinRequestBodyDataRate = value;
}

MinDataRate IHttpMinResponseDataRateFeature.MinDataRate
{
get => MinResponseDataRate;
set => MinResponseDataRate = value;
}

protected void ResetIHttpUpgradeFeature()
protected void ResetHttp1Features()
{
_currentIHttpUpgradeFeature = this;
_currentIHttpMinRequestBodyDataRateFeature = this;
_currentIHttpMinResponseDataRateFeature = this;
}

protected void ResetHttp2Features()
Expand Down
4 changes: 2 additions & 2 deletions src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ private void FastReset()
_currentIHttpRequestLifetimeFeature = this;
_currentIHttpConnectionFeature = this;
_currentIHttpMaxRequestBodySizeFeature = this;
_currentIHttpMinRequestBodyDataRateFeature = this;
_currentIHttpMinResponseDataRateFeature = this;
_currentIHttpBodyControlFeature = this;

_currentIServiceProvidersFeature = null;
Expand All @@ -87,6 +85,8 @@ private void FastReset()
_currentITlsConnectionFeature = null;
_currentIHttpWebSocketFeature = null;
_currentISessionFeature = null;
_currentIHttpMinRequestBodyDataRateFeature = null;
_currentIHttpMinResponseDataRateFeature = null;
_currentIHttpSendFileFeature = null;
}

Expand Down
10 changes: 2 additions & 8 deletions src/Kestrel.Core/Internal/Http/HttpProtocol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public HttpProtocol(HttpConnectionContext context)
public ServiceContext ServiceContext => _context.ServiceContext;
private IPEndPoint LocalEndPoint => _context.LocalEndPoint;
private IPEndPoint RemoteEndPoint => _context.RemoteEndPoint;
public ITimeoutControl TimeoutControl => _context.TimeoutControl;

public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures;
public IHttpOutputProducer Output { get; protected set; }
Expand Down Expand Up @@ -275,10 +276,6 @@ public CancellationToken RequestAborted

protected HttpResponseHeaders HttpResponseHeaders { get; } = new HttpResponseHeaders();

public MinDataRate MinRequestBodyDataRate { get; set; }

public MinDataRate MinResponseDataRate { get; set; }

public void InitializeStreams(MessageBody messageBody)
{
if (_streams == null)
Expand Down Expand Up @@ -363,9 +360,6 @@ public void Reset()

_responseBytesWritten = 0;

MinRequestBodyDataRate = ServerOptions.Limits.MinRequestBodyDataRate;
MinResponseDataRate = ServerOptions.Limits.MinResponseDataRate;

OnReset();
}

Expand Down Expand Up @@ -628,7 +622,7 @@ private async Task ProcessRequests<TContext>(IHttpApplication<TContext> applicat
{
RequestBodyPipe.Reader.Complete();

// Wait for MessageBody.PumpAsync() to call RequestBodyPipe.Writer.Complete().
// Wait for Http1MessageBody.PumpAsync() to call RequestBodyPipe.Writer.Complete().
await messageBody.StopAsync();
}
}
Expand Down
Loading

0 comments on commit 395b681

Please sign in to comment.