# This is a combination of 2 commits.
# This is the 1st commit message: #539 Implement request body size limit # The commit message #2 will be skipped: # Check exception messages
This commit is contained in:
parent
c13ba3ef0a
commit
7b15720a05
|
|
@ -28,7 +28,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
IHttpRequestLifetimeFeature,
|
IHttpRequestLifetimeFeature,
|
||||||
IHttpAuthenticationFeature,
|
IHttpAuthenticationFeature,
|
||||||
IHttpUpgradeFeature,
|
IHttpUpgradeFeature,
|
||||||
IHttpRequestIdentifierFeature
|
IHttpRequestIdentifierFeature,
|
||||||
|
IHttpMaxRequestBodySizeFeature
|
||||||
{
|
{
|
||||||
private RequestContext _requestContext;
|
private RequestContext _requestContext;
|
||||||
private IFeatureCollection _features;
|
private IFeatureCollection _features;
|
||||||
|
|
@ -62,11 +63,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
private bool _responseStarted;
|
private bool _responseStarted;
|
||||||
private bool _completed;
|
private bool _completed;
|
||||||
|
|
||||||
internal FeatureContext(RequestContext requestContext, bool enableResponseCaching)
|
internal FeatureContext(RequestContext requestContext)
|
||||||
{
|
{
|
||||||
_requestContext = requestContext;
|
_requestContext = requestContext;
|
||||||
_features = new FeatureCollection(new StandardFeatureCollection(this));
|
_features = new FeatureCollection(new StandardFeatureCollection(this));
|
||||||
_enableResponseCaching = enableResponseCaching;
|
_enableResponseCaching = _requestContext.Server.Options.EnableResponseCaching;
|
||||||
|
|
||||||
// Pre-initialize any fields that are not lazy at the lower level.
|
// Pre-initialize any fields that are not lazy at the lower level.
|
||||||
_requestHeaders = Request.Headers;
|
_requestHeaders = Request.Headers;
|
||||||
|
|
@ -78,7 +79,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
_scheme = Request.Scheme;
|
_scheme = Request.Scheme;
|
||||||
_user = _requestContext.User;
|
_user = _requestContext.User;
|
||||||
|
|
||||||
_responseStream = new ResponseStream(requestContext.Response.Body, OnStart);
|
_responseStream = new ResponseStream(requestContext.Response.Body, OnResponseStart);
|
||||||
_responseHeaders = Response.Headers;
|
_responseHeaders = Response.Headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,7 +406,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
async Task IHttpSendFileFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
|
async Task IHttpSendFileFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
|
||||||
{
|
{
|
||||||
await OnStart();
|
await OnResponseStart();
|
||||||
await Response.SendFileAsync(path, offset, length, cancellation);
|
await Response.SendFileAsync(path, offset, length, cancellation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -433,7 +434,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()
|
async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()
|
||||||
{
|
{
|
||||||
await OnStart();
|
await OnResponseStart();
|
||||||
return await _requestContext.UpgradeAsync();
|
return await _requestContext.UpgradeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -463,7 +464,15 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task OnStart()
|
bool IHttpMaxRequestBodySizeFeature.IsReadOnly => Request.HasRequestBodyStarted;
|
||||||
|
|
||||||
|
long? IHttpMaxRequestBodySizeFeature.MaxRequestBodySize
|
||||||
|
{
|
||||||
|
get => Request.MaxRequestBodySize;
|
||||||
|
set => Request.MaxRequestBodySize = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task OnResponseStart()
|
||||||
{
|
{
|
||||||
if (_responseStarted)
|
if (_responseStarted)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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 Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.HttpSys
|
namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
{
|
{
|
||||||
|
|
@ -9,12 +10,16 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
{
|
{
|
||||||
private const long DefaultRequestQueueLength = 1000; // Http.sys default.
|
private const long DefaultRequestQueueLength = 1000; // Http.sys default.
|
||||||
internal static readonly int DefaultMaxAccepts = 5 * Environment.ProcessorCount;
|
internal static readonly int DefaultMaxAccepts = 5 * Environment.ProcessorCount;
|
||||||
|
// Matches the default maxAllowedContentLength in IIS (~28.6 MB)
|
||||||
|
// https://www.iis.net/configreference/system.webserver/security/requestfiltering/requestlimits#005
|
||||||
|
private const long DefaultMaxRequestBodySize = 30000000;
|
||||||
|
|
||||||
// The native request queue
|
// The native request queue
|
||||||
private long _requestQueueLength = DefaultRequestQueueLength;
|
private long _requestQueueLength = DefaultRequestQueueLength;
|
||||||
private long? _maxConnections;
|
private long? _maxConnections;
|
||||||
private RequestQueue _requestQueue;
|
private RequestQueue _requestQueue;
|
||||||
private UrlGroup _urlGroup;
|
private UrlGroup _urlGroup;
|
||||||
|
private long? _maxRequestBodySize = DefaultMaxRequestBodySize;
|
||||||
|
|
||||||
public HttpSysOptions()
|
public HttpSysOptions()
|
||||||
{
|
{
|
||||||
|
|
@ -104,6 +109,28 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum allowed size of any request body in bytes.
|
||||||
|
/// When set to null, the maximum request body size is unlimited.
|
||||||
|
/// This limit has no effect on upgraded connections which are always unlimited.
|
||||||
|
/// This can be overridden per-request via <see cref="IHttpMaxRequestBodySizeFeature"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Defaults to 30,000,000 bytes, which is approximately 28.6MB.
|
||||||
|
/// </remarks>
|
||||||
|
public long? MaxRequestBodySize
|
||||||
|
{
|
||||||
|
get => _maxRequestBodySize;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), value, string.Empty);
|
||||||
|
}
|
||||||
|
_maxRequestBodySize = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal void Apply(UrlGroup urlGroup, RequestQueue requestQueue)
|
internal void Apply(UrlGroup urlGroup, RequestQueue requestQueue)
|
||||||
{
|
{
|
||||||
_urlGroup = urlGroup;
|
_urlGroup = urlGroup;
|
||||||
|
|
|
||||||
|
|
@ -56,14 +56,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
_processRequest = new Action<object>(ProcessRequestAsync);
|
_processRequest = new Action<object>(ProcessRequestAsync);
|
||||||
_maxAccepts = _options.MaxAccepts;
|
_maxAccepts = _options.MaxAccepts;
|
||||||
EnableResponseCaching = _options.EnableResponseCaching;
|
|
||||||
_shutdownSignal = new TaskCompletionSource<object>();
|
_shutdownSignal = new TaskCompletionSource<object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal HttpSysListener Listener { get; }
|
internal HttpSysListener Listener { get; }
|
||||||
|
|
||||||
internal bool EnableResponseCaching { get; set; }
|
|
||||||
|
|
||||||
public IFeatureCollection Features { get; }
|
public IFeatureCollection Features { get; }
|
||||||
|
|
||||||
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
|
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
|
||||||
|
|
@ -199,12 +196,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
Interlocked.Increment(ref _outstandingRequests);
|
Interlocked.Increment(ref _outstandingRequests);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var featureContext = new FeatureContext(requestContext, EnableResponseCaching);
|
var featureContext = new FeatureContext(requestContext);
|
||||||
context = _application.CreateContext(featureContext.Features);
|
context = _application.CreateContext(featureContext.Features);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _application.ProcessRequestAsync(context).SupressContext();
|
await _application.ProcessRequestAsync(context).SupressContext();
|
||||||
await featureContext.OnStart();
|
await featureContext.OnResponseStart();
|
||||||
requestContext.Dispose();
|
requestContext.Dispose();
|
||||||
_application.DisposeContext(context, null);
|
_application.DisposeContext(context, null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
private BoundaryType _contentBoundaryType;
|
private BoundaryType _contentBoundaryType;
|
||||||
private long? _contentLength;
|
private long? _contentLength;
|
||||||
private Stream _nativeStream;
|
private RequestStream _nativeStream;
|
||||||
|
|
||||||
private SocketAddress _localEndPoint;
|
private SocketAddress _localEndPoint;
|
||||||
private SocketAddress _remoteEndPoint;
|
private SocketAddress _remoteEndPoint;
|
||||||
|
|
@ -143,15 +143,32 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
public string Method { get; }
|
public string Method { get; }
|
||||||
|
|
||||||
public Stream Body
|
public Stream Body => EnsureRequestStream() ?? Stream.Null;
|
||||||
|
|
||||||
|
private RequestStream EnsureRequestStream()
|
||||||
{
|
{
|
||||||
get
|
if (_nativeStream == null && HasEntityBody)
|
||||||
{
|
{
|
||||||
if (_nativeStream == null)
|
_nativeStream = new RequestStream(RequestContext)
|
||||||
{
|
{
|
||||||
_nativeStream = HasEntityBody ? new RequestStream(RequestContext) : Stream.Null;
|
MaxSize = RequestContext.Server.Options.MaxRequestBodySize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return _nativeStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasRequestBodyStarted => _nativeStream?.HasStarted ?? false;
|
||||||
|
|
||||||
|
public long? MaxRequestBodySize
|
||||||
|
{
|
||||||
|
get => EnsureRequestStream()?.MaxSize;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
EnsureRequestStream();
|
||||||
|
if (_nativeStream != null)
|
||||||
|
{
|
||||||
|
_nativeStream.MaxSize = value;
|
||||||
}
|
}
|
||||||
return _nativeStream;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,10 +336,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
internal void SwitchToOpaqueMode()
|
internal void SwitchToOpaqueMode()
|
||||||
{
|
{
|
||||||
if (_nativeStream == null || _nativeStream == Stream.Null)
|
if (_nativeStream == null)
|
||||||
{
|
{
|
||||||
_nativeStream = new RequestStream(RequestContext);
|
_nativeStream = new RequestStream(RequestContext);
|
||||||
}
|
}
|
||||||
|
_nativeStream.SwitchToOpaqueMode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -17,6 +18,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
private RequestContext _requestContext;
|
private RequestContext _requestContext;
|
||||||
private uint _dataChunkOffset;
|
private uint _dataChunkOffset;
|
||||||
private int _dataChunkIndex;
|
private int _dataChunkIndex;
|
||||||
|
private long? _maxSize;
|
||||||
|
private long _totalRead;
|
||||||
private bool _closed;
|
private bool _closed;
|
||||||
|
|
||||||
internal RequestStream(RequestContext httpContext)
|
internal RequestStream(RequestContext httpContext)
|
||||||
|
|
@ -35,68 +38,53 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
private ILogger Logger => RequestContext.Server.Logger;
|
private ILogger Logger => RequestContext.Server.Logger;
|
||||||
|
|
||||||
public override bool CanSeek
|
public bool HasStarted { get; private set; }
|
||||||
|
|
||||||
|
public long? MaxSize
|
||||||
{
|
{
|
||||||
get
|
get => _maxSize;
|
||||||
|
set
|
||||||
{
|
{
|
||||||
return false;
|
if (HasStarted)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The maximum request size cannot be changed after the request body has started reading.");
|
||||||
|
}
|
||||||
|
if (value.HasValue && value < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), value, string.Empty);
|
||||||
|
}
|
||||||
|
_maxSize = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanWrite
|
public override bool CanSeek => false;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanRead
|
public override bool CanWrite => false;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override long Length
|
public override bool CanRead => true;
|
||||||
{
|
|
||||||
get
|
public override long Length => throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||||
{
|
|
||||||
throw new NotSupportedException(Resources.Exception_NoSeek);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override long Position
|
public override long Position
|
||||||
{
|
{
|
||||||
get
|
get => throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||||
{
|
set => throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||||
throw new NotSupportedException(Resources.Exception_NoSeek);
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(Resources.Exception_NoSeek);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
{
|
=> throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||||
throw new NotSupportedException(Resources.Exception_NoSeek);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetLength(long value)
|
public override void SetLength(long value) => throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||||
{
|
|
||||||
throw new NotSupportedException(Resources.Exception_NoSeek);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Flush()
|
public override void Flush() => throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
|
||||||
{
|
|
||||||
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||||
|
=> throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
|
||||||
|
|
||||||
|
internal void SwitchToOpaqueMode()
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
|
HasStarted = true;
|
||||||
|
_maxSize = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Abort()
|
internal void Abort()
|
||||||
|
|
@ -124,6 +112,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
public override unsafe int Read([In, Out] byte[] buffer, int offset, int size)
|
public override unsafe int Read([In, Out] byte[] buffer, int offset, int size)
|
||||||
{
|
{
|
||||||
ValidateReadBuffer(buffer, offset, size);
|
ValidateReadBuffer(buffer, offset, size);
|
||||||
|
CheckSizeLimit();
|
||||||
if (_closed)
|
if (_closed)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -177,6 +166,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
}
|
}
|
||||||
UpdateAfterRead(statusCode, dataRead);
|
UpdateAfterRead(statusCode, dataRead);
|
||||||
}
|
}
|
||||||
|
if (TryCheckSizeLimit((int)dataRead, out var ex))
|
||||||
|
{
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Verbose log dump data read
|
// TODO: Verbose log dump data read
|
||||||
return (int)dataRead;
|
return (int)dataRead;
|
||||||
|
|
@ -193,6 +186,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
public override unsafe IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
|
public override unsafe IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
|
||||||
{
|
{
|
||||||
ValidateReadBuffer(buffer, offset, size);
|
ValidateReadBuffer(buffer, offset, size);
|
||||||
|
CheckSizeLimit();
|
||||||
if (_closed)
|
if (_closed)
|
||||||
{
|
{
|
||||||
RequestStreamAsyncResult result = new RequestStreamAsyncResult(this, state, callback);
|
RequestStreamAsyncResult result = new RequestStreamAsyncResult(this, state, callback);
|
||||||
|
|
@ -295,7 +289,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
castedAsyncResult.EndCalled = true;
|
castedAsyncResult.EndCalled = true;
|
||||||
// wait & then check for errors
|
// wait & then check for errors
|
||||||
// Throws on failure
|
// Throws on failure
|
||||||
int dataRead = castedAsyncResult.Task.Result;
|
int dataRead = castedAsyncResult.Task.GetAwaiter().GetResult();
|
||||||
// TODO: Verbose log #dataRead.
|
// TODO: Verbose log #dataRead.
|
||||||
return dataRead;
|
return dataRead;
|
||||||
}
|
}
|
||||||
|
|
@ -303,6 +297,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
public override unsafe Task<int> ReadAsync(byte[] buffer, int offset, int size, CancellationToken cancellationToken)
|
public override unsafe Task<int> ReadAsync(byte[] buffer, int offset, int size, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ValidateReadBuffer(buffer, offset, size);
|
ValidateReadBuffer(buffer, offset, size);
|
||||||
|
CheckSizeLimit();
|
||||||
if (_closed)
|
if (_closed)
|
||||||
{
|
{
|
||||||
return Task.FromResult<int>(0);
|
return Task.FromResult<int>(0);
|
||||||
|
|
@ -323,6 +318,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
if (_dataChunkIndex != -1 && dataRead == size)
|
if (_dataChunkIndex != -1 && dataRead == size)
|
||||||
{
|
{
|
||||||
UpdateAfterRead(UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS, dataRead);
|
UpdateAfterRead(UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS, dataRead);
|
||||||
|
if (TryCheckSizeLimit((int)dataRead, out var exception))
|
||||||
|
{
|
||||||
|
return Task.FromException<int>(exception);
|
||||||
|
}
|
||||||
// TODO: Verbose log #dataRead
|
// TODO: Verbose log #dataRead
|
||||||
return Task.FromResult<int>((int)dataRead);
|
return Task.FromResult<int>((int)dataRead);
|
||||||
}
|
}
|
||||||
|
|
@ -378,6 +377,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
{
|
{
|
||||||
uint totalRead = dataRead + bytesReturned;
|
uint totalRead = dataRead + bytesReturned;
|
||||||
UpdateAfterRead(statusCode, totalRead);
|
UpdateAfterRead(statusCode, totalRead);
|
||||||
|
if (TryCheckSizeLimit((int)totalRead, out var exception))
|
||||||
|
{
|
||||||
|
return Task.FromException<int>(exception);
|
||||||
|
}
|
||||||
// TODO: Verbose log totalRead
|
// TODO: Verbose log totalRead
|
||||||
return Task.FromResult<int>((int)totalRead);
|
return Task.FromResult<int>((int)totalRead);
|
||||||
}
|
}
|
||||||
|
|
@ -396,6 +399,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
asyncResult.Dispose();
|
asyncResult.Dispose();
|
||||||
uint totalRead = dataRead + bytesReturned;
|
uint totalRead = dataRead + bytesReturned;
|
||||||
UpdateAfterRead(statusCode, totalRead);
|
UpdateAfterRead(statusCode, totalRead);
|
||||||
|
if (TryCheckSizeLimit((int)totalRead, out var exception))
|
||||||
|
{
|
||||||
|
return Task.FromException<int>(exception);
|
||||||
|
}
|
||||||
// TODO: Verbose log
|
// TODO: Verbose log
|
||||||
return Task.FromResult<int>((int)totalRead);
|
return Task.FromResult<int>((int)totalRead);
|
||||||
}
|
}
|
||||||
|
|
@ -418,6 +425,40 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
|
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called before each read
|
||||||
|
private void CheckSizeLimit()
|
||||||
|
{
|
||||||
|
// Note SwitchToOpaqueMode sets HasStarted and clears _maxSize, so these limits don't apply.
|
||||||
|
if (!HasStarted)
|
||||||
|
{
|
||||||
|
var contentLength = RequestContext.Request.ContentLength;
|
||||||
|
if (contentLength.HasValue && _maxSize.HasValue && contentLength.Value > _maxSize.Value)
|
||||||
|
{
|
||||||
|
throw new IOException(
|
||||||
|
$"The request's Content-Length {contentLength.Value} is larger than the request body size limit {_maxSize.Value}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
HasStarted = true;
|
||||||
|
}
|
||||||
|
else if (TryCheckSizeLimit(0, out var exception))
|
||||||
|
{
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called after each read.
|
||||||
|
internal bool TryCheckSizeLimit(int bytesRead, out Exception exception)
|
||||||
|
{
|
||||||
|
_totalRead += bytesRead;
|
||||||
|
if (_maxSize.HasValue && _totalRead > _maxSize.Value)
|
||||||
|
{
|
||||||
|
exception = new IOException($"The total number of bytes read {_totalRead} has exceeded the request body size limit {_maxSize.Value}.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
exception = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
asyncResult.Fail(e);
|
asyncResult.Fail(new IOException(string.Empty, e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,7 +112,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
internal void Complete(int read, uint errorCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
|
internal void Complete(int read, uint errorCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
if (_tcs.TrySetResult(read + (int)DataAlreadyRead))
|
if (_requestStream.TryCheckSizeLimit(read + (int)DataAlreadyRead, out var exception))
|
||||||
|
{
|
||||||
|
_tcs.TrySetException(exception);
|
||||||
|
}
|
||||||
|
else if (_tcs.TrySetResult(read + (int)DataAlreadyRead))
|
||||||
{
|
{
|
||||||
RequestStream.UpdateAfterRead((uint)errorCode, (uint)(read + DataAlreadyRead));
|
RequestStream.UpdateAfterRead((uint)errorCode, (uint)(read + DataAlreadyRead));
|
||||||
if (_callback != null)
|
if (_callback != null)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
{ typeof(IHttpAuthenticationFeature), _identityFunc },
|
{ typeof(IHttpAuthenticationFeature), _identityFunc },
|
||||||
{ typeof(IHttpRequestIdentifierFeature), _identityFunc },
|
{ typeof(IHttpRequestIdentifierFeature), _identityFunc },
|
||||||
{ typeof(RequestContext), ctx => ctx.RequestContext },
|
{ typeof(RequestContext), ctx => ctx.RequestContext },
|
||||||
|
{ typeof(IHttpMaxRequestBodySizeFeature), _identityFunc },
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly FeatureContext _featureContext;
|
private readonly FeatureContext _featureContext;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,422 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
{
|
||||||
|
public class RequestBodyLimitTests
|
||||||
|
{
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ContentLengthEqualsLimit_ReadSync_Success()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Equal(11, httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
int read = httpContext.Request.Body.Read(input, 0, input.Length);
|
||||||
|
httpContext.Response.ContentLength = read;
|
||||||
|
httpContext.Response.Body.Write(input, 0, read);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World");
|
||||||
|
Assert.Equal("Hello World", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ContentLengthEqualsLimit_ReadAync_Success()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, async httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Equal(11, httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
||||||
|
httpContext.Response.ContentLength = read;
|
||||||
|
await httpContext.Response.Body.WriteAsync(input, 0, read);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World");
|
||||||
|
Assert.Equal("Hello World", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ContentLengthEqualsLimit_ReadBeginEnd_Success()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Equal(11, httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
int read = httpContext.Request.Body.EndRead(httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
|
||||||
|
httpContext.Response.ContentLength = read;
|
||||||
|
httpContext.Response.Body.EndWrite(httpContext.Response.Body.BeginWrite(input, 0, read, null, null));
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World");
|
||||||
|
Assert.Equal("Hello World", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ChunkedEqualsLimit_ReadSync_Success()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Null(httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
int read = httpContext.Request.Body.Read(input, 0, input.Length);
|
||||||
|
httpContext.Response.ContentLength = read;
|
||||||
|
httpContext.Response.Body.Write(input, 0, read);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World", chunked: true);
|
||||||
|
Assert.Equal("Hello World", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ChunkedEqualsLimit_ReadAync_Success()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, async httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Null(httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
||||||
|
httpContext.Response.ContentLength = read;
|
||||||
|
await httpContext.Response.Body.WriteAsync(input, 0, read);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World", chunked: true);
|
||||||
|
Assert.Equal("Hello World", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ChunkedEqualsLimit_ReadBeginEnd_Success()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Null(httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
int read = httpContext.Request.Body.EndRead(httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
|
||||||
|
httpContext.Response.ContentLength = read;
|
||||||
|
httpContext.Response.Body.EndWrite(httpContext.Response.Body.BeginWrite(input, 0, read, null, null));
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World", chunked: true);
|
||||||
|
Assert.Equal("Hello World", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ContentLengthExceedsLimit_ReadSync_ThrowsImmidately()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Equal(11, httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
var ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
|
||||||
|
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
|
||||||
|
ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
|
||||||
|
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World");
|
||||||
|
Assert.Equal(string.Empty, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ContentLengthExceedsLimit_ReadAsync_ThrowsImmidately()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Equal(11, httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
var ex = Assert.Throws<IOException>(() => { var t = httpContext.Request.Body.ReadAsync(input, 0, input.Length); });
|
||||||
|
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
|
||||||
|
ex = Assert.Throws<IOException>(() => { var t = httpContext.Request.Body.ReadAsync(input, 0, input.Length); });
|
||||||
|
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World");
|
||||||
|
Assert.Equal(string.Empty, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ContentLengthExceedsLimit_ReadBeginEnd_ThrowsImmidately()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Equal(11, httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
var ex = Assert.Throws<IOException>(() => httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
|
||||||
|
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
|
||||||
|
ex = Assert.Throws<IOException>(() => httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
|
||||||
|
Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World");
|
||||||
|
Assert.Equal(string.Empty, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ChunkedExceedsLimit_ReadSync_ThrowsAtLimit()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Null(httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
var ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
|
||||||
|
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
|
||||||
|
ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
|
||||||
|
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World", chunked: true);
|
||||||
|
Assert.Equal(string.Empty, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ChunkedExceedsLimit_ReadAsync_ThrowsAtLimit()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, async httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Null(httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
var ex = await Assert.ThrowsAsync<IOException>(() => httpContext.Request.Body.ReadAsync(input, 0, input.Length));
|
||||||
|
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
|
||||||
|
ex = await Assert.ThrowsAsync<IOException>(() => httpContext.Request.Body.ReadAsync(input, 0, input.Length));
|
||||||
|
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World", chunked: true);
|
||||||
|
Assert.Equal(string.Empty, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ChunkedExceedsLimit_ReadBeginEnd_ThrowsAtLimit()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Null(httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
var body = httpContext.Request.Body;
|
||||||
|
var ex = Assert.Throws<IOException>(() => body.EndRead(body.BeginRead(input, 0, input.Length, null, null)));
|
||||||
|
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
|
||||||
|
ex = Assert.Throws<IOException>(() => body.EndRead(body.BeginRead(input, 0, input.Length, null, null)));
|
||||||
|
Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World", chunked: true);
|
||||||
|
Assert.Equal(string.Empty, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task Chunked_ReadSyncPartialBodyUnderLimit_ThrowsAfterLimit()
|
||||||
|
{
|
||||||
|
var content = new StaggardContent();
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Null(httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
int read = httpContext.Request.Body.Read(input, 0, input.Length);
|
||||||
|
Assert.Equal(10, read);
|
||||||
|
content.Block.Release();
|
||||||
|
var ex = Assert.Throws<IOException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
|
||||||
|
Assert.Equal("The total number of bytes read 20 has exceeded the request body size limit 10.", ex.Message);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
string response = await SendRequestAsync(address, content, chunked: true);
|
||||||
|
Assert.Equal(string.Empty, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task Chunked_ReadAsyncPartialBodyUnderLimit_ThrowsAfterLimit()
|
||||||
|
{
|
||||||
|
var content = new StaggardContent();
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 10, async httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Null(httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
||||||
|
Assert.Equal(10, read);
|
||||||
|
content.Block.Release();
|
||||||
|
var ex = await Assert.ThrowsAsync<IOException>(() => httpContext.Request.Body.ReadAsync(input, 0, input.Length));
|
||||||
|
Assert.Equal("The total number of bytes read 20 has exceeded the request body size limit 10.", ex.Message);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
string response = await SendRequestAsync(address, content, chunked: true);
|
||||||
|
Assert.Equal(string.Empty, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task AdjustLimitPerRequest_ContentLength_ReadAync_Success()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, async httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Equal(11, feature.MaxRequestBodySize);
|
||||||
|
feature.MaxRequestBodySize = 12;
|
||||||
|
Assert.Equal(12, httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
||||||
|
Assert.True(feature.IsReadOnly);
|
||||||
|
httpContext.Response.ContentLength = read;
|
||||||
|
await httpContext.Response.Body.WriteAsync(input, 0, read);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World!");
|
||||||
|
Assert.Equal("Hello World!", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task AdjustLimitPerRequest_Chunked_ReadAync_Success()
|
||||||
|
{
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, options => options.MaxRequestBodySize = 11, async httpContext =>
|
||||||
|
{
|
||||||
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.Equal(11, feature.MaxRequestBodySize);
|
||||||
|
feature.MaxRequestBodySize = 12;
|
||||||
|
Assert.Null(httpContext.Request.ContentLength);
|
||||||
|
byte[] input = new byte[100];
|
||||||
|
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
||||||
|
Assert.True(feature.IsReadOnly);
|
||||||
|
httpContext.Response.ContentLength = read;
|
||||||
|
await httpContext.Response.Body.WriteAsync(input, 0, read);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = await SendRequestAsync(address, "Hello World!", chunked: true);
|
||||||
|
Assert.Equal("Hello World!", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<string> SendRequestAsync(string uri, string upload, bool chunked = false)
|
||||||
|
{
|
||||||
|
return SendRequestAsync(uri, new StringContent(upload), chunked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> SendRequestAsync(string uri, HttpContent content, bool chunked = false)
|
||||||
|
{
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
client.DefaultRequestHeaders.TransferEncodingChunked = chunked;
|
||||||
|
HttpResponseMessage response = await client.PostAsync(uri, content);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StaggardContent : HttpContent
|
||||||
|
{
|
||||||
|
public StaggardContent()
|
||||||
|
{
|
||||||
|
Block = new SemaphoreSlim(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SemaphoreSlim Block { get; private set; }
|
||||||
|
|
||||||
|
protected async override Task SerializeToStreamAsync(Stream stream, TransportContext context)
|
||||||
|
{
|
||||||
|
await stream.WriteAsync(new byte[10], 0, 10);
|
||||||
|
Assert.True(await Block.WaitAsync(TimeSpan.FromSeconds(10)));
|
||||||
|
await stream.WriteAsync(new byte[10], 0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool TryComputeLength(out long length)
|
||||||
|
{
|
||||||
|
length = 10;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,25 +27,41 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
internal static IServer CreateHttpServer(out string baseAddress, RequestDelegate app)
|
internal static IServer CreateHttpServer(out string baseAddress, RequestDelegate app)
|
||||||
{
|
{
|
||||||
string root;
|
string root;
|
||||||
return CreateDynamicHttpServer(string.Empty, AuthenticationSchemes.None, true, out root, out baseAddress, app);
|
return CreateDynamicHttpServer(string.Empty, out root, out baseAddress, options => { }, app);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IServer CreateHttpServer(out string baseAddress, Action<HttpSysOptions> configureOptions, RequestDelegate app)
|
||||||
|
{
|
||||||
|
string root;
|
||||||
|
return CreateDynamicHttpServer(string.Empty, out root, out baseAddress, configureOptions, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IServer CreateHttpServerReturnRoot(string path, out string root, RequestDelegate app)
|
internal static IServer CreateHttpServerReturnRoot(string path, out string root, RequestDelegate app)
|
||||||
{
|
{
|
||||||
string baseAddress;
|
string baseAddress;
|
||||||
return CreateDynamicHttpServer(path, AuthenticationSchemes.None, true, out root, out baseAddress, app);
|
return CreateDynamicHttpServer(path, out root, out baseAddress, options => { }, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IServer CreateHttpAuthServer(AuthenticationSchemes authType, bool allowAnonymous, out string baseAddress, RequestDelegate app)
|
internal static IServer CreateHttpAuthServer(AuthenticationSchemes authType, bool allowAnonymous, out string baseAddress, RequestDelegate app)
|
||||||
{
|
{
|
||||||
string root;
|
string root;
|
||||||
return CreateDynamicHttpServer(string.Empty, authType, allowAnonymous, out root, out baseAddress, app);
|
return CreateDynamicHttpServer(string.Empty, out root, out baseAddress, options =>
|
||||||
|
{
|
||||||
|
options.Authentication.Schemes = authType;
|
||||||
|
options.Authentication.AllowAnonymous = allowAnonymous;
|
||||||
|
}, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IWebHost CreateDynamicHost(AuthenticationSchemes authType, bool allowAnonymous, out string root, RequestDelegate app)
|
internal static IWebHost CreateDynamicHost(AuthenticationSchemes authType, bool allowAnonymous, out string root, RequestDelegate app)
|
||||||
=> CreateDynamicHost(string.Empty, authType, allowAnonymous, out root, out var baseAddress, app);
|
{
|
||||||
|
return CreateDynamicHost(string.Empty, out root, out var baseAddress, options =>
|
||||||
|
{
|
||||||
|
options.Authentication.Schemes = authType;
|
||||||
|
options.Authentication.AllowAnonymous = allowAnonymous;
|
||||||
|
}, app);
|
||||||
|
}
|
||||||
|
|
||||||
internal static IWebHost CreateDynamicHost(string basePath, AuthenticationSchemes authType, bool allowAnonymous, out string root, out string baseAddress, RequestDelegate app)
|
internal static IWebHost CreateDynamicHost(string basePath, out string root, out string baseAddress, Action<HttpSysOptions> configureOptions, RequestDelegate app)
|
||||||
{
|
{
|
||||||
lock (PortLock)
|
lock (PortLock)
|
||||||
{
|
{
|
||||||
|
|
@ -60,8 +76,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
.UseHttpSys(options =>
|
.UseHttpSys(options =>
|
||||||
{
|
{
|
||||||
options.UrlPrefixes.Add(prefix);
|
options.UrlPrefixes.Add(prefix);
|
||||||
options.Authentication.Schemes = authType;
|
configureOptions(options);
|
||||||
options.Authentication.AllowAnonymous = allowAnonymous;
|
|
||||||
})
|
})
|
||||||
.Configure(appBuilder => appBuilder.Run(app));
|
.Configure(appBuilder => appBuilder.Run(app));
|
||||||
|
|
||||||
|
|
@ -86,7 +101,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
internal static MessagePump CreatePump()
|
internal static MessagePump CreatePump()
|
||||||
=> new MessagePump(Options.Create(new HttpSysOptions()), new LoggerFactory(), new AuthenticationSchemeProvider(Options.Create(new AuthenticationOptions())));
|
=> new MessagePump(Options.Create(new HttpSysOptions()), new LoggerFactory(), new AuthenticationSchemeProvider(Options.Create(new AuthenticationOptions())));
|
||||||
|
|
||||||
internal static IServer CreateDynamicHttpServer(string basePath, AuthenticationSchemes authType, bool allowAnonymous, out string root, out string baseAddress, RequestDelegate app)
|
internal static IServer CreateDynamicHttpServer(string basePath, out string root, out string baseAddress, Action<HttpSysOptions> configureOptions, RequestDelegate app)
|
||||||
{
|
{
|
||||||
lock (PortLock)
|
lock (PortLock)
|
||||||
{
|
{
|
||||||
|
|
@ -100,8 +115,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
var server = CreatePump();
|
var server = CreatePump();
|
||||||
server.Features.Get<IServerAddressesFeature>().Addresses.Add(baseAddress);
|
server.Features.Get<IServerAddressesFeature>().Addresses.Add(baseAddress);
|
||||||
server.Listener.Options.Authentication.Schemes = authType;
|
configureOptions(server.Listener.Options);
|
||||||
server.Listener.Options.Authentication.AllowAnonymous = allowAnonymous;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait();
|
server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue