# 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,
|
||||
IHttpAuthenticationFeature,
|
||||
IHttpUpgradeFeature,
|
||||
IHttpRequestIdentifierFeature
|
||||
IHttpRequestIdentifierFeature,
|
||||
IHttpMaxRequestBodySizeFeature
|
||||
{
|
||||
private RequestContext _requestContext;
|
||||
private IFeatureCollection _features;
|
||||
|
|
@ -62,11 +63,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
private bool _responseStarted;
|
||||
private bool _completed;
|
||||
|
||||
internal FeatureContext(RequestContext requestContext, bool enableResponseCaching)
|
||||
internal FeatureContext(RequestContext requestContext)
|
||||
{
|
||||
_requestContext = requestContext;
|
||||
_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.
|
||||
_requestHeaders = Request.Headers;
|
||||
|
|
@ -78,7 +79,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
_scheme = Request.Scheme;
|
||||
_user = _requestContext.User;
|
||||
|
||||
_responseStream = new ResponseStream(requestContext.Response.Body, OnStart);
|
||||
_responseStream = new ResponseStream(requestContext.Response.Body, OnResponseStart);
|
||||
_responseHeaders = Response.Headers;
|
||||
}
|
||||
|
||||
|
|
@ -405,7 +406,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
|
||||
async Task IHttpSendFileFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
|
||||
{
|
||||
await OnStart();
|
||||
await OnResponseStart();
|
||||
await Response.SendFileAsync(path, offset, length, cancellation);
|
||||
}
|
||||
|
||||
|
|
@ -433,7 +434,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
|
||||
async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()
|
||||
{
|
||||
await OnStart();
|
||||
await OnResponseStart();
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.HttpSys
|
||||
{
|
||||
|
|
@ -9,12 +10,16 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
{
|
||||
private const long DefaultRequestQueueLength = 1000; // Http.sys default.
|
||||
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
|
||||
private long _requestQueueLength = DefaultRequestQueueLength;
|
||||
private long? _maxConnections;
|
||||
private RequestQueue _requestQueue;
|
||||
private UrlGroup _urlGroup;
|
||||
private long? _maxRequestBodySize = DefaultMaxRequestBodySize;
|
||||
|
||||
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)
|
||||
{
|
||||
_urlGroup = urlGroup;
|
||||
|
|
|
|||
|
|
@ -56,14 +56,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
|
||||
_processRequest = new Action<object>(ProcessRequestAsync);
|
||||
_maxAccepts = _options.MaxAccepts;
|
||||
EnableResponseCaching = _options.EnableResponseCaching;
|
||||
_shutdownSignal = new TaskCompletionSource<object>();
|
||||
}
|
||||
|
||||
internal HttpSysListener Listener { get; }
|
||||
|
||||
internal bool EnableResponseCaching { get; set; }
|
||||
|
||||
public IFeatureCollection Features { get; }
|
||||
|
||||
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
|
||||
|
|
@ -199,12 +196,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
Interlocked.Increment(ref _outstandingRequests);
|
||||
try
|
||||
{
|
||||
var featureContext = new FeatureContext(requestContext, EnableResponseCaching);
|
||||
var featureContext = new FeatureContext(requestContext);
|
||||
context = _application.CreateContext(featureContext.Features);
|
||||
try
|
||||
{
|
||||
await _application.ProcessRequestAsync(context).SupressContext();
|
||||
await featureContext.OnStart();
|
||||
await featureContext.OnResponseStart();
|
||||
requestContext.Dispose();
|
||||
_application.DisposeContext(context, null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
|
||||
private BoundaryType _contentBoundaryType;
|
||||
private long? _contentLength;
|
||||
private Stream _nativeStream;
|
||||
private RequestStream _nativeStream;
|
||||
|
||||
private SocketAddress _localEndPoint;
|
||||
private SocketAddress _remoteEndPoint;
|
||||
|
|
@ -143,15 +143,32 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
|
||||
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()
|
||||
{
|
||||
if (_nativeStream == null || _nativeStream == Stream.Null)
|
||||
if (_nativeStream == null)
|
||||
{
|
||||
_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.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
|
@ -17,6 +18,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
private RequestContext _requestContext;
|
||||
private uint _dataChunkOffset;
|
||||
private int _dataChunkIndex;
|
||||
private long? _maxSize;
|
||||
private long _totalRead;
|
||||
private bool _closed;
|
||||
|
||||
internal RequestStream(RequestContext httpContext)
|
||||
|
|
@ -35,68 +38,53 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
|
||||
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
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||
}
|
||||
}
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override long Length => throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||
}
|
||||
get => throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||
set => throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||
}
|
||||
public override void SetLength(long value) => throw new NotSupportedException(Resources.Exception_NoSeek);
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
|
||||
}
|
||||
public override void Flush() => throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
|
||||
|
||||
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()
|
||||
|
|
@ -124,6 +112,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
public override unsafe int Read([In, Out] byte[] buffer, int offset, int size)
|
||||
{
|
||||
ValidateReadBuffer(buffer, offset, size);
|
||||
CheckSizeLimit();
|
||||
if (_closed)
|
||||
{
|
||||
return 0;
|
||||
|
|
@ -177,6 +166,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
}
|
||||
UpdateAfterRead(statusCode, dataRead);
|
||||
}
|
||||
if (TryCheckSizeLimit((int)dataRead, out var ex))
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// TODO: Verbose log dump data read
|
||||
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)
|
||||
{
|
||||
ValidateReadBuffer(buffer, offset, size);
|
||||
CheckSizeLimit();
|
||||
if (_closed)
|
||||
{
|
||||
RequestStreamAsyncResult result = new RequestStreamAsyncResult(this, state, callback);
|
||||
|
|
@ -295,7 +289,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
castedAsyncResult.EndCalled = true;
|
||||
// wait & then check for errors
|
||||
// Throws on failure
|
||||
int dataRead = castedAsyncResult.Task.Result;
|
||||
int dataRead = castedAsyncResult.Task.GetAwaiter().GetResult();
|
||||
// TODO: Verbose log #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)
|
||||
{
|
||||
ValidateReadBuffer(buffer, offset, size);
|
||||
CheckSizeLimit();
|
||||
if (_closed)
|
||||
{
|
||||
return Task.FromResult<int>(0);
|
||||
|
|
@ -323,6 +318,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
if (_dataChunkIndex != -1 && dataRead == size)
|
||||
{
|
||||
UpdateAfterRead(UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS, dataRead);
|
||||
if (TryCheckSizeLimit((int)dataRead, out var exception))
|
||||
{
|
||||
return Task.FromException<int>(exception);
|
||||
}
|
||||
// TODO: Verbose log #dataRead
|
||||
return Task.FromResult<int>((int)dataRead);
|
||||
}
|
||||
|
|
@ -378,6 +377,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
{
|
||||
uint totalRead = dataRead + bytesReturned;
|
||||
UpdateAfterRead(statusCode, totalRead);
|
||||
if (TryCheckSizeLimit((int)totalRead, out var exception))
|
||||
{
|
||||
return Task.FromException<int>(exception);
|
||||
}
|
||||
// TODO: Verbose log totalRead
|
||||
return Task.FromResult<int>((int)totalRead);
|
||||
}
|
||||
|
|
@ -396,6 +399,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
asyncResult.Dispose();
|
||||
uint totalRead = dataRead + bytesReturned;
|
||||
UpdateAfterRead(statusCode, totalRead);
|
||||
if (TryCheckSizeLimit((int)totalRead, out var exception))
|
||||
{
|
||||
return Task.FromException<int>(exception);
|
||||
}
|
||||
// TODO: Verbose log
|
||||
return Task.FromResult<int>((int)totalRead);
|
||||
}
|
||||
|
|
@ -418,6 +425,40 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
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)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
}
|
||||
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)
|
||||
{
|
||||
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));
|
||||
if (_callback != null)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
{ typeof(IHttpAuthenticationFeature), _identityFunc },
|
||||
{ typeof(IHttpRequestIdentifierFeature), _identityFunc },
|
||||
{ typeof(RequestContext), ctx => ctx.RequestContext },
|
||||
{ typeof(IHttpMaxRequestBodySizeFeature), _identityFunc },
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
=> 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)
|
||||
{
|
||||
|
|
@ -60,8 +76,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
.UseHttpSys(options =>
|
||||
{
|
||||
options.UrlPrefixes.Add(prefix);
|
||||
options.Authentication.Schemes = authType;
|
||||
options.Authentication.AllowAnonymous = allowAnonymous;
|
||||
configureOptions(options);
|
||||
})
|
||||
.Configure(appBuilder => appBuilder.Run(app));
|
||||
|
||||
|
|
@ -86,7 +101,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
internal static MessagePump CreatePump()
|
||||
=> 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)
|
||||
{
|
||||
|
|
@ -100,8 +115,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
|
||||
var server = CreatePump();
|
||||
server.Features.Get<IServerAddressesFeature>().Addresses.Add(baseAddress);
|
||||
server.Listener.Options.Authentication.Schemes = authType;
|
||||
server.Listener.Options.Authentication.AllowAnonymous = allowAnonymous;
|
||||
configureOptions(server.Listener.Options);
|
||||
try
|
||||
{
|
||||
server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait();
|
||||
|
|
|
|||
Loading…
Reference in New Issue