Add feature to optionally disallow synchronous IO (#1919)

* Allow synchronous IO by default
This commit is contained in:
Stephen Halter 2017-07-03 11:07:17 -07:00 committed by GitHub
parent 6e45de2205
commit e9ffcdb414
19 changed files with 503 additions and 111 deletions

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
Version 2.0 Version 2.0
The primary goals of this format is to allow a simple XML format The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes various data types are done through the TypeConverter classes
associated with the data types. associated with the data types.
Example: Example:
... ado.net/XML headers & schema ... ... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader> <resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader> <resheader name="version">2.0</resheader>
@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment> <comment>This is a comment</comment>
</data> </data>
There are any number of "resheader" rows that contain simple There are any number of "resheader" rows that contain simple
name/value pairs. name/value pairs.
Each data row contains a name, and value. The row also contains a Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture. text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the Classes that don't support this are serialized and stored with the
mimetype set. mimetype set.
The mimetype is used for serialized objects, and tells the The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly: extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below. read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64 mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64 mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64 mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter : using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
--> -->
@ -336,4 +336,10 @@
<data name="MinimumGracePeriodRequired" xml:space="preserve"> <data name="MinimumGracePeriodRequired" xml:space="preserve">
<value>The request body rate enforcement grace period must be greater than {heartbeatInterval} second.</value> <value>The request body rate enforcement grace period must be greater than {heartbeatInterval} second.</value>
</data> </data>
</root> <data name="SynchronousReadsDisallowed" xml:space="preserve">
<value>Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.</value>
</data>
<data name="SynchronousWritesDisallowed" xml:space="preserve">
<value>Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.</value>
</data>
</root>

View File

@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
IHttpConnectionFeature, IHttpConnectionFeature,
IHttpRequestLifetimeFeature, IHttpRequestLifetimeFeature,
IHttpRequestIdentifierFeature, IHttpRequestIdentifierFeature,
IHttpBodyControlFeature,
IHttpMaxRequestBodySizeFeature, IHttpMaxRequestBodySizeFeature,
IHttpMinRequestBodyDataRateFeature IHttpMinRequestBodyDataRateFeature
{ {
@ -205,6 +206,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
set => TraceIdentifier = value; set => TraceIdentifier = value;
} }
bool IHttpBodyControlFeature.AllowSynchronousIO
{
get => AllowSynchronousIO;
set => AllowSynchronousIO = value;
}
bool IHttpMaxRequestBodySizeFeature.IsReadOnly => HasStartedConsumingRequestBody || _wasUpgraded; bool IHttpMaxRequestBodySizeFeature.IsReadOnly => HasStartedConsumingRequestBody || _wasUpgraded;
long? IHttpMaxRequestBodySizeFeature.MaxRequestBodySize long? IHttpMaxRequestBodySizeFeature.MaxRequestBodySize

View File

@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature); private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
private static readonly Type IHttpMaxRequestBodySizeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature); private static readonly Type IHttpMaxRequestBodySizeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature);
private static readonly Type IHttpMinRequestBodyDataRateFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature); private static readonly Type IHttpMinRequestBodyDataRateFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature);
private static readonly Type IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature); private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
private object _currentIHttpRequestFeature; private object _currentIHttpRequestFeature;
@ -44,6 +45,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private object _currentISessionFeature; private object _currentISessionFeature;
private object _currentIHttpMaxRequestBodySizeFeature; private object _currentIHttpMaxRequestBodySizeFeature;
private object _currentIHttpMinRequestBodyDataRateFeature; private object _currentIHttpMinRequestBodyDataRateFeature;
private object _currentIHttpBodyControlFeature;
private object _currentIHttpSendFileFeature; private object _currentIHttpSendFileFeature;
private void FastReset() private void FastReset()
@ -56,6 +58,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_currentIHttpConnectionFeature = this; _currentIHttpConnectionFeature = this;
_currentIHttpMaxRequestBodySizeFeature = this; _currentIHttpMaxRequestBodySizeFeature = this;
_currentIHttpMinRequestBodyDataRateFeature = this; _currentIHttpMinRequestBodyDataRateFeature = this;
_currentIHttpBodyControlFeature = this;
_currentIServiceProvidersFeature = null; _currentIServiceProvidersFeature = null;
_currentIHttpAuthenticationFeature = null; _currentIHttpAuthenticationFeature = null;
@ -139,6 +142,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
return _currentIHttpMinRequestBodyDataRateFeature; return _currentIHttpMinRequestBodyDataRateFeature;
} }
if (key == IHttpBodyControlFeatureType)
{
return _currentIHttpBodyControlFeature;
}
if (key == IHttpSendFileFeatureType) if (key == IHttpSendFileFeatureType)
{ {
return _currentIHttpSendFileFeature; return _currentIHttpSendFileFeature;
@ -235,6 +242,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_currentIHttpMinRequestBodyDataRateFeature = feature; _currentIHttpMinRequestBodyDataRateFeature = feature;
return; return;
} }
if (key == IHttpBodyControlFeatureType)
{
_currentIHttpBodyControlFeature = feature;
return;
}
if (key == IHttpSendFileFeatureType) if (key == IHttpSendFileFeatureType)
{ {
_currentIHttpSendFileFeature = feature; _currentIHttpSendFileFeature = feature;
@ -313,6 +325,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
yield return new KeyValuePair<Type, object>(IHttpMinRequestBodyDataRateFeatureType, _currentIHttpMinRequestBodyDataRateFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature); yield return new KeyValuePair<Type, object>(IHttpMinRequestBodyDataRateFeatureType, _currentIHttpMinRequestBodyDataRateFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature);
} }
if (_currentIHttpBodyControlFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpBodyControlFeatureType, _currentIHttpBodyControlFeature as global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
}
if (_currentIHttpSendFileFeature != null) if (_currentIHttpSendFileFeature != null)
{ {
yield return new KeyValuePair<Type, object>(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature); yield return new KeyValuePair<Type, object>(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);

View File

@ -122,6 +122,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public string ConnectionIdFeature { get; set; } public string ConnectionIdFeature { get; set; }
public bool HasStartedConsumingRequestBody { get; set; } public bool HasStartedConsumingRequestBody { get; set; }
public long? MaxRequestBodySize { get; set; } public long? MaxRequestBodySize { get; set; }
public bool AllowSynchronousIO { get; set; }
/// <summary> /// <summary>
/// The request id. <seealso cref="HttpContext.TraceIdentifier"/> /// The request id. <seealso cref="HttpContext.TraceIdentifier"/>
@ -305,7 +306,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
if (_frameStreams == null) if (_frameStreams == null)
{ {
_frameStreams = new Streams(this); _frameStreams = new Streams(bodyControl: this, frameControl: this);
} }
(RequestBody, ResponseBody) = _frameStreams.Start(messageBody); (RequestBody, ResponseBody) = _frameStreams.Start(messageBody);
@ -329,6 +330,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
HasStartedConsumingRequestBody = false; HasStartedConsumingRequestBody = false;
MaxRequestBodySize = ServerOptions.Limits.MaxRequestBodySize; MaxRequestBodySize = ServerOptions.Limits.MaxRequestBodySize;
AllowSynchronousIO = ServerOptions.AllowSynchronousIO;
TraceIdentifier = null; TraceIdentifier = null;
Scheme = null; Scheme = null;
Method = null; Method = null;

View File

@ -7,17 +7,20 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
internal class FrameRequestStream : ReadOnlyStream internal class FrameRequestStream : ReadOnlyStream
{ {
private readonly IHttpBodyControlFeature _bodyControl;
private MessageBody _body; private MessageBody _body;
private FrameStreamState _state; private FrameStreamState _state;
private Exception _error; private Exception _error;
public FrameRequestStream() public FrameRequestStream(IHttpBodyControlFeature bodyControl)
{ {
_bodyControl = bodyControl;
_state = FrameStreamState.Closed; _state = FrameStreamState.Closed;
} }
@ -34,13 +37,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public override void Flush() public override void Flush()
{ {
// No-op. throw new NotSupportedException();
} }
public override Task FlushAsync(CancellationToken cancellationToken) public override Task FlushAsync(CancellationToken cancellationToken)
{ {
// No-op. throw new NotSupportedException();
return Task.CompletedTask;
} }
public override long Seek(long offset, SeekOrigin origin) public override long Seek(long offset, SeekOrigin origin)
@ -55,8 +57,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public override int Read(byte[] buffer, int offset, int count) public override int Read(byte[] buffer, int offset, int count)
{ {
// ValueTask uses .GetAwaiter().GetResult() if necessary if (!_bodyControl.AllowSynchronousIO)
return ReadAsync(buffer, offset, count).Result; {
throw new InvalidOperationException(CoreStrings.SynchronousReadsDisallowed);
}
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
} }
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)

View File

@ -6,16 +6,19 @@ using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
internal class FrameResponseStream : WriteOnlyStream internal class FrameResponseStream : WriteOnlyStream
{ {
private IFrameControl _frameControl; private readonly IHttpBodyControlFeature _bodyControl;
private readonly IFrameControl _frameControl;
private FrameStreamState _state; private FrameStreamState _state;
public FrameResponseStream(IFrameControl frameControl) public FrameResponseStream(IHttpBodyControlFeature bodyControl, IFrameControl frameControl)
{ {
_bodyControl = bodyControl;
_frameControl = frameControl; _frameControl = frameControl;
_state = FrameStreamState.Closed; _state = FrameStreamState.Closed;
} }
@ -58,6 +61,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public override void Write(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count)
{ {
if (!_bodyControl.AllowSynchronousIO)
{
throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed);
}
WriteAsync(buffer, offset, count, default(CancellationToken)).GetAwaiter().GetResult(); WriteAsync(buffer, offset, count, default(CancellationToken)).GetAwaiter().GetResult();
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
@ -17,11 +18,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
private readonly FrameRequestStream _emptyRequest; private readonly FrameRequestStream _emptyRequest;
private readonly Stream _upgradeStream; private readonly Stream _upgradeStream;
public Streams(IFrameControl frameControl) public Streams(IHttpBodyControlFeature bodyControl, IFrameControl frameControl)
{ {
_request = new FrameRequestStream(); _request = new FrameRequestStream(bodyControl);
_emptyRequest = new FrameRequestStream(); _emptyRequest = new FrameRequestStream(bodyControl);
_response = new FrameResponseStream(frameControl); _response = new FrameResponseStream(bodyControl, frameControl);
_upgradeableResponse = new WrappingStream(_response); _upgradeableResponse = new WrappingStream(_response);
_upgradeStream = new FrameDuplexStream(_request, _response); _upgradeStream = new FrameDuplexStream(_request, _response);
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Core namespace Microsoft.AspNetCore.Server.Kestrel.Core
@ -35,6 +36,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// <remarks>The default mode is <see cref="SchedulingMode.Default"/></remarks> /// <remarks>The default mode is <see cref="SchedulingMode.Default"/></remarks>
public SchedulingMode ApplicationSchedulingMode { get; set; } = SchedulingMode.Default; public SchedulingMode ApplicationSchedulingMode { get; set; } = SchedulingMode.Default;
/// <summary>
/// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/>
/// </summary>
/// <remarks>
/// Defaults to true.
/// </remarks>
public bool AllowSynchronousIO { get; set; } = true;
/// <summary> /// <summary>
/// Enables the Listen options callback to resolve and use services registered by the application during startup. /// Enables the Listen options callback to resolve and use services registered by the application during startup.
/// Typically initialized by UseKestrel()"/>. /// Typically initialized by UseKestrel()"/>.

View File

@ -1019,7 +1019,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
=> GetString("NonNegativeTimeSpanRequired"); => GetString("NonNegativeTimeSpanRequired");
/// <summary> /// <summary>
/// The request body rate enforcement grace period must be greater than {heartbeatInterval} seconds. /// The request body rate enforcement grace period must be greater than {heartbeatInterval} second.
/// </summary> /// </summary>
internal static string MinimumGracePeriodRequired internal static string MinimumGracePeriodRequired
{ {
@ -1027,11 +1027,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
} }
/// <summary> /// <summary>
/// The request body rate enforcement grace period must be greater than {heartbeatInterval} seconds. /// The request body rate enforcement grace period must be greater than {heartbeatInterval} second.
/// </summary> /// </summary>
internal static string FormatMinimumGracePeriodRequired(object heartbeatInterval) internal static string FormatMinimumGracePeriodRequired(object heartbeatInterval)
=> string.Format(CultureInfo.CurrentCulture, GetString("MinimumGracePeriodRequired", "heartbeatInterval"), heartbeatInterval); => string.Format(CultureInfo.CurrentCulture, GetString("MinimumGracePeriodRequired", "heartbeatInterval"), heartbeatInterval);
/// <summary>
/// Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
/// </summary>
internal static string SynchronousReadsDisallowed
{
get => GetString("SynchronousReadsDisallowed");
}
/// <summary>
/// Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
/// </summary>
internal static string FormatSynchronousReadsDisallowed()
=> GetString("SynchronousReadsDisallowed");
/// <summary>
/// Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
/// </summary>
internal static string SynchronousWritesDisallowed
{
get => GetString("SynchronousWritesDisallowed");
}
/// <summary>
/// Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
/// </summary>
internal static string FormatSynchronousWritesDisallowed()
=> GetString("SynchronousWritesDisallowed");
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -5,6 +5,7 @@ using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Moq; using Moq;
using Xunit; using Xunit;
@ -16,49 +17,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void CanReadReturnsTrue() public void CanReadReturnsTrue()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.True(stream.CanRead); Assert.True(stream.CanRead);
} }
[Fact] [Fact]
public void CanSeekReturnsFalse() public void CanSeekReturnsFalse()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.False(stream.CanSeek); Assert.False(stream.CanSeek);
} }
[Fact] [Fact]
public void CanWriteReturnsFalse() public void CanWriteReturnsFalse()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.False(stream.CanWrite); Assert.False(stream.CanWrite);
} }
[Fact] [Fact]
public void SeekThrows() public void SeekThrows()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.Seek(0, SeekOrigin.Begin)); Assert.Throws<NotSupportedException>(() => stream.Seek(0, SeekOrigin.Begin));
} }
[Fact] [Fact]
public void LengthThrows() public void LengthThrows()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.Length); Assert.Throws<NotSupportedException>(() => stream.Length);
} }
[Fact] [Fact]
public void SetLengthThrows() public void SetLengthThrows()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.SetLength(0)); Assert.Throws<NotSupportedException>(() => stream.SetLength(0));
} }
[Fact] [Fact]
public void PositionThrows() public void PositionThrows()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.Position); Assert.Throws<NotSupportedException>(() => stream.Position);
Assert.Throws<NotSupportedException>(() => stream.Position = 0); Assert.Throws<NotSupportedException>(() => stream.Position = 0);
} }
@ -66,21 +67,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void WriteThrows() public void WriteThrows()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.Write(new byte[1], 0, 1)); Assert.Throws<NotSupportedException>(() => stream.Write(new byte[1], 0, 1));
} }
[Fact] [Fact]
public void WriteByteThrows() public void WriteByteThrows()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.WriteByte(0)); Assert.Throws<NotSupportedException>(() => stream.WriteByte(0));
} }
[Fact] [Fact]
public async Task WriteAsyncThrows() public async Task WriteAsyncThrows()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
await Assert.ThrowsAsync<NotSupportedException>(() => stream.WriteAsync(new byte[1], 0, 1)); await Assert.ThrowsAsync<NotSupportedException>(() => stream.WriteAsync(new byte[1], 0, 1));
} }
@ -88,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void BeginWriteThrows() public void BeginWriteThrows()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.BeginWrite(new byte[1], 0, 1, null, null)); Assert.Throws<NotSupportedException>(() => stream.BeginWrite(new byte[1], 0, 1, null, null));
} }
#elif NETCOREAPP2_0 #elif NETCOREAPP2_0
@ -97,23 +98,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#endif #endif
[Fact] [Fact]
public void FlushDoesNotThrow() public void FlushThrows()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.Flush(); Assert.Throws<NotSupportedException>(() => stream.Flush());
} }
[Fact] [Fact]
public async Task FlushAsyncDoesNotThrow() public async Task FlushAsyncThrows()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
await stream.FlushAsync(); await Assert.ThrowsAsync<NotSupportedException>(() => stream.FlushAsync());
}
[Fact]
public async Task SynchronousReadsThrowIfDisallowedByIHttpBodyControlFeature()
{
var allowSynchronousIO = false;
var mockBodyControl = new Mock<IHttpBodyControlFeature>();
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(() => allowSynchronousIO);
var mockMessageBody = new Mock<MessageBody>((Frame)null);
mockMessageBody.Setup(m => m.ReadAsync(It.IsAny<ArraySegment<byte>>(), CancellationToken.None)).ReturnsAsync(0);
var stream = new FrameRequestStream(mockBodyControl.Object);
stream.StartAcceptingReads(mockMessageBody.Object);
Assert.Equal(0, await stream.ReadAsync(new byte[1], 0, 1));
var ioEx = Assert.Throws<InvalidOperationException>(() => stream.Read(new byte[1], 0, 1));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
var ioEx2 = Assert.Throws<InvalidOperationException>(() => stream.CopyTo(Stream.Null));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx2.Message);
allowSynchronousIO = true;
Assert.Equal(0, stream.Read(new byte[1], 0, 1));
} }
[Fact] [Fact]
public void AbortCausesReadToCancel() public void AbortCausesReadToCancel()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null); stream.StartAcceptingReads(null);
stream.Abort(); stream.Abort();
var task = stream.ReadAsync(new byte[1], 0, 1); var task = stream.ReadAsync(new byte[1], 0, 1);
@ -123,7 +148,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void AbortWithErrorCausesReadToCancel() public void AbortWithErrorCausesReadToCancel()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null); stream.StartAcceptingReads(null);
var error = new Exception(); var error = new Exception();
stream.Abort(error); stream.Abort(error);
@ -135,7 +160,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void StopAcceptingReadsCausesReadToThrowObjectDisposedException() public void StopAcceptingReadsCausesReadToThrowObjectDisposedException()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null); stream.StartAcceptingReads(null);
stream.StopAcceptingReads(); stream.StopAcceptingReads();
Assert.Throws<ObjectDisposedException>(() => { stream.ReadAsync(new byte[1], 0, 1); }); Assert.Throws<ObjectDisposedException>(() => { stream.ReadAsync(new byte[1], 0, 1); });
@ -144,7 +169,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void AbortCausesCopyToAsyncToCancel() public void AbortCausesCopyToAsyncToCancel()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null); stream.StartAcceptingReads(null);
stream.Abort(); stream.Abort();
var task = stream.CopyToAsync(Mock.Of<Stream>()); var task = stream.CopyToAsync(Mock.Of<Stream>());
@ -154,7 +179,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void AbortWithErrorCausesCopyToAsyncToCancel() public void AbortWithErrorCausesCopyToAsyncToCancel()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null); stream.StartAcceptingReads(null);
var error = new Exception(); var error = new Exception();
stream.Abort(error); stream.Abort(error);
@ -166,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void StopAcceptingReadsCausesCopyToAsyncToThrowObjectDisposedException() public void StopAcceptingReadsCausesCopyToAsyncToThrowObjectDisposedException()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null); stream.StartAcceptingReads(null);
stream.StopAcceptingReads(); stream.StopAcceptingReads();
Assert.Throws<ObjectDisposedException>(() => { stream.CopyToAsync(Mock.Of<Stream>()); }); Assert.Throws<ObjectDisposedException>(() => { stream.CopyToAsync(Mock.Of<Stream>()); });
@ -175,7 +200,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void NullDestinationCausesCopyToAsyncToThrowArgumentNullException() public void NullDestinationCausesCopyToAsyncToThrowArgumentNullException()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null); stream.StartAcceptingReads(null);
Assert.Throws<ArgumentNullException>(() => { stream.CopyToAsync(null); }); Assert.Throws<ArgumentNullException>(() => { stream.CopyToAsync(null); });
} }
@ -183,7 +208,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void ZeroBufferSizeCausesCopyToAsyncToThrowArgumentException() public void ZeroBufferSizeCausesCopyToAsyncToThrowArgumentException()
{ {
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null); stream.StartAcceptingReads(null);
Assert.Throws<ArgumentException>(() => { stream.CopyToAsync(Mock.Of<Stream>(), 0); }); Assert.Throws<ArgumentException>(() => { stream.CopyToAsync(Mock.Of<Stream>(), 0); });
} }

View File

@ -3,9 +3,12 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Tests.TestHelpers; using Microsoft.AspNetCore.Server.Kestrel.Core.Tests.TestHelpers;
using Moq;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
@ -15,79 +18,111 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void CanReadReturnsFalse() public void CanReadReturnsFalse()
{ {
var stream = new FrameResponseStream(new MockFrameControl()); var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.False(stream.CanRead); Assert.False(stream.CanRead);
} }
[Fact] [Fact]
public void CanSeekReturnsFalse() public void CanSeekReturnsFalse()
{ {
var stream = new FrameResponseStream(new MockFrameControl()); var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.False(stream.CanSeek); Assert.False(stream.CanSeek);
} }
[Fact] [Fact]
public void CanWriteReturnsTrue() public void CanWriteReturnsTrue()
{ {
var stream = new FrameResponseStream(new MockFrameControl()); var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.True(stream.CanWrite); Assert.True(stream.CanWrite);
} }
[Fact] [Fact]
public void ReadThrows() public void ReadThrows()
{ {
var stream = new FrameResponseStream(new MockFrameControl()); var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.Throws<NotSupportedException>(() => stream.Read(new byte[1], 0, 1)); Assert.Throws<NotSupportedException>(() => stream.Read(new byte[1], 0, 1));
} }
[Fact] [Fact]
public void ReadByteThrows() public void ReadByteThrows()
{ {
var stream = new FrameResponseStream(new MockFrameControl()); var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.Throws<NotSupportedException>(() => stream.ReadByte()); Assert.Throws<NotSupportedException>(() => stream.ReadByte());
} }
[Fact] [Fact]
public async Task ReadAsyncThrows() public async Task ReadAsyncThrows()
{ {
var stream = new FrameResponseStream(new MockFrameControl()); var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
await Assert.ThrowsAsync<NotSupportedException>(() => stream.ReadAsync(new byte[1], 0, 1)); await Assert.ThrowsAsync<NotSupportedException>(() => stream.ReadAsync(new byte[1], 0, 1));
} }
[Fact] [Fact]
public void BeginReadThrows() public void BeginReadThrows()
{ {
var stream = new FrameResponseStream(new MockFrameControl()); var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.Throws<NotSupportedException>(() => stream.BeginRead(new byte[1], 0, 1, null, null)); Assert.Throws<NotSupportedException>(() => stream.BeginRead(new byte[1], 0, 1, null, null));
} }
[Fact] [Fact]
public void SeekThrows() public void SeekThrows()
{ {
var stream = new FrameResponseStream(new MockFrameControl()); var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.Throws<NotSupportedException>(() => stream.Seek(0, SeekOrigin.Begin)); Assert.Throws<NotSupportedException>(() => stream.Seek(0, SeekOrigin.Begin));
} }
[Fact] [Fact]
public void LengthThrows() public void LengthThrows()
{ {
var stream = new FrameResponseStream(new MockFrameControl()); var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.Throws<NotSupportedException>(() => stream.Length); Assert.Throws<NotSupportedException>(() => stream.Length);
} }
[Fact] [Fact]
public void SetLengthThrows() public void SetLengthThrows()
{ {
var stream = new FrameResponseStream(new MockFrameControl()); var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.Throws<NotSupportedException>(() => stream.SetLength(0)); Assert.Throws<NotSupportedException>(() => stream.SetLength(0));
} }
[Fact] [Fact]
public void PositionThrows() public void PositionThrows()
{ {
var stream = new FrameResponseStream(new MockFrameControl()); var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.Throws<NotSupportedException>(() => stream.Position); Assert.Throws<NotSupportedException>(() => stream.Position);
Assert.Throws<NotSupportedException>(() => stream.Position = 0); Assert.Throws<NotSupportedException>(() => stream.Position = 0);
} }
[Fact]
public void StopAcceptingWritesCausesWriteToThrowObjectDisposedException()
{
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), Mock.Of<IFrameControl>());
stream.StartAcceptingWrites();
stream.StopAcceptingWrites();
Assert.Throws<ObjectDisposedException>(() => { stream.WriteAsync(new byte[1], 0, 1); });
}
[Fact]
public async Task SynchronousWritesThrowIfDisallowedByIHttpBodyControlFeature()
{
var allowSynchronousIO = false;
var mockBodyControl = new Mock<IHttpBodyControlFeature>();
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(() => allowSynchronousIO);
var mockFrameControl = new Mock<IFrameControl>();
mockFrameControl.Setup(m => m.WriteAsync(It.IsAny<ArraySegment<byte>>(), CancellationToken.None)).Returns(Task.CompletedTask);
var stream = new FrameResponseStream(mockBodyControl.Object, mockFrameControl.Object);
stream.StartAcceptingWrites();
// WriteAsync doesn't throw.
await stream.WriteAsync(new byte[1], 0, 1);
var ioEx = Assert.Throws<InvalidOperationException>(() => stream.Write(new byte[1], 0, 1));
Assert.Equal("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
allowSynchronousIO = true;
// If IHttpBodyControlFeature.AllowSynchronousIO is true, Write no longer throws.
stream.Write(new byte[1], 0, 1);
}
} }
} }

View File

@ -22,5 +22,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.True(o1.ListenOptions[0].NoDelay); Assert.True(o1.ListenOptions[0].NoDelay);
Assert.False(o1.ListenOptions[1].NoDelay); Assert.False(o1.ListenOptions[1].NoDelay);
} }
[Fact]
public void AllowSynchronousIODefaultsToTrue()
{
var options = new KestrelServerOptions();
Assert.True(options.AllowSynchronousIO);
}
} }
} }

View File

@ -9,6 +9,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
@ -28,7 +29,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame); var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
var stream = new FrameRequestStream(); var mockBodyControl = new Mock<IHttpBodyControlFeature>();
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(true);
var stream = new FrameRequestStream(mockBodyControl.Object);
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("Hello"); input.Add("Hello");
@ -54,7 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame); var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("Hello"); input.Add("Hello");
@ -78,7 +81,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame); var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
var stream = new FrameRequestStream(); var mockBodyControl = new Mock<IHttpBodyControlFeature>();
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(true);
var stream = new FrameRequestStream(mockBodyControl.Object);
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("5\r\nHello\r\n"); input.Add("5\r\nHello\r\n");
@ -104,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame); var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("5\r\nHello\r\n"); input.Add("5\r\nHello\r\n");
@ -130,7 +135,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame); var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("5;\r\0"); input.Add("5;\r\0");
@ -155,7 +160,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame); var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("80000000\r\n"); input.Add("80000000\r\n");
@ -176,7 +181,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame); var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("012345678\r"); input.Add("012345678\r");
@ -199,7 +204,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame); var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame);
var stream = new FrameRequestStream(); var mockBodyControl = new Mock<IHttpBodyControlFeature>();
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(true);
var stream = new FrameRequestStream(mockBodyControl.Object);
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("Hello"); input.Add("Hello");
@ -224,7 +231,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame); var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("Hello"); input.Add("Hello");
@ -249,7 +256,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame); var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame);
var stream = new FrameRequestStream(); var mockBodyControl = new Mock<IHttpBodyControlFeature>();
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(true);
var stream = new FrameRequestStream(mockBodyControl.Object);
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("Hello"); input.Add("Hello");
@ -269,7 +278,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame); var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("Hello"); input.Add("Hello");
@ -287,7 +296,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "8197" }, input.Frame); var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "8197" }, input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
// Input needs to be greater than 4032 bytes to allocate a block not backed by a slab. // Input needs to be greater than 4032 bytes to allocate a block not backed by a slab.
@ -479,13 +488,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderConnection = headerConnection }, input.Frame); var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderConnection = headerConnection }, input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
input.Add("Hello"); input.Add("Hello");
var buffer = new byte[1024]; var buffer = new byte[1024];
Assert.Equal(5, stream.Read(buffer, 0, buffer.Length)); Assert.Equal(5, await stream.ReadAsync(buffer, 0, buffer.Length));
AssertASCII("Hello", new ArraySegment<byte>(buffer, 0, 5)); AssertASCII("Hello", new ArraySegment<byte>(buffer, 0, 5));
input.Fin(); input.Fin();
@ -500,7 +509,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame); var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
// Add some input and consume it to ensure PumpAsync is running // Add some input and consume it to ensure PumpAsync is running
@ -523,7 +532,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame); var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
// Add some input and consume it to ensure PumpAsync is running // Add some input and consume it to ensure PumpAsync is running
@ -642,7 +651,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
input.Frame.TraceIdentifier = "RequestId"; input.Frame.TraceIdentifier = "RequestId";
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame); var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
// Add some input and consume it to ensure PumpAsync is running // Add some input and consume it to ensure PumpAsync is running
@ -672,7 +681,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
input.Frame.TraceIdentifier = "RequestId"; input.Frame.TraceIdentifier = "RequestId";
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame); var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
var stream = new FrameRequestStream(); var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body); stream.StartAcceptingReads(body);
// Add some input and consume it to ensure PumpAsync is running // Add some input and consume it to ensure PumpAsync is running

View File

@ -4,9 +4,11 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
using Microsoft.AspNetCore.Testing;
using Moq; using Moq;
using Xunit; using Xunit;
@ -17,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public async Task StreamsThrowAfterAbort() public async Task StreamsThrowAfterAbort()
{ {
var streams = new Streams(Mock.Of<IFrameControl>()); var streams = new Streams(Mock.Of<IHttpBodyControlFeature>(), Mock.Of<IFrameControl>());
var (request, response) = streams.Start(new MockMessageBody()); var (request, response) = streams.Start(new MockMessageBody());
var ex = new Exception("My error"); var ex = new Exception("My error");
@ -31,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public async Task StreamsThrowOnAbortAfterUpgrade() public async Task StreamsThrowOnAbortAfterUpgrade()
{ {
var streams = new Streams(Mock.Of<IFrameControl>()); var streams = new Streams(Mock.Of<IHttpBodyControlFeature>(), Mock.Of<IFrameControl>());
var (request, response) = streams.Start(new MockMessageBody(upgradeable: true)); var (request, response) = streams.Start(new MockMessageBody(upgradeable: true));
var upgrade = streams.Upgrade(); var upgrade = streams.Upgrade();
@ -53,7 +55,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public async Task StreamsThrowOnUpgradeAfterAbort() public async Task StreamsThrowOnUpgradeAfterAbort()
{ {
var streams = new Streams(Mock.Of<IFrameControl>()); var streams = new Streams(Mock.Of<IHttpBodyControlFeature>(), Mock.Of<IFrameControl>());
var (request, response) = streams.Start(new MockMessageBody(upgradeable: true)); var (request, response) = streams.Start(new MockMessageBody(upgradeable: true));
var ex = new Exception("My error"); var ex = new Exception("My error");

View File

@ -291,7 +291,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await clientFinishedSendingRequestBody.Task.TimeoutAfter(TimeSpan.FromSeconds(120)); await clientFinishedSendingRequestBody.Task.TimeoutAfter(TimeSpan.FromSeconds(120));
// Verify client didn't send extra bytes // Verify client didn't send extra bytes
if (context.Request.Body.ReadByte() != -1) if (await context.Request.Body.ReadAsync(new byte[1], 0, 1) != 0)
{ {
context.Response.StatusCode = StatusCodes.Status500InternalServerError; context.Response.StatusCode = StatusCodes.Status500InternalServerError;
await context.Response.WriteAsync("Client sent more bytes than expectedBody.Length"); await context.Response.WriteAsync("Client sent more bytes than expectedBody.Length");

View File

@ -1480,6 +1480,130 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
} }
} }
[Fact]
public async Task SynchronousReadsAllowedByDefault()
{
var firstRequest = true;
using (var server = new TestServer(async context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.True(bodyControlFeature.AllowSynchronousIO);
var buffer = new byte[6];
var offset = 0;
// The request body is 5 bytes long. The 6th byte (buffer[5]) is only used for writing the response body.
buffer[5] = (byte)(firstRequest ? '1' : '2');
if (firstRequest)
{
while (offset < 5)
{
offset += context.Request.Body.Read(buffer, offset, 5 - offset);
}
firstRequest = false;
}
else
{
bodyControlFeature.AllowSynchronousIO = false;
// Synchronous reads now throw.
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(new byte[1], 0, 1));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
var ioEx2 = Assert.Throws<InvalidOperationException>(() => context.Request.Body.CopyTo(Stream.Null));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx2.Message);
while (offset < 5)
{
offset += await context.Request.Body.ReadAsync(buffer, offset, 5 - offset);
}
}
Assert.Equal(0, await context.Request.Body.ReadAsync(new byte[1], 0, 1));
Assert.Equal("Hello", Encoding.ASCII.GetString(buffer, 0, 5));
context.Response.ContentLength = 6;
await context.Response.Body.WriteAsync(buffer, 0, 6);
}))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"POST / HTTP/1.1",
"Host:",
"Content-Length: 5",
"",
"HelloPOST / HTTP/1.1",
"Host:",
"Content-Length: 5",
"",
"Hello");
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6",
"",
"Hello1HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6",
"",
"Hello2");
}
}
}
[Fact]
public async Task SynchronousReadsCanBeDisallowedGlobally()
{
var testContext = new TestServiceContext
{
ServerOptions = { AllowSynchronousIO = false }
};
using (var server = new TestServer(async context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.False(bodyControlFeature.AllowSynchronousIO);
// Synchronous reads now throw.
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(new byte[1], 0, 1));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
var ioEx2 = Assert.Throws<InvalidOperationException>(() => context.Request.Body.CopyTo(Stream.Null));
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx2.Message);
var buffer = new byte[5];
var offset = 0;
while (offset < 5)
{
offset += await context.Request.Body.ReadAsync(buffer, offset, 5 - offset);
}
Assert.Equal(0, await context.Request.Body.ReadAsync(new byte[1], 0, 1));
Assert.Equal("Hello", Encoding.ASCII.GetString(buffer));
}, testContext))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"POST / HTTP/1.1",
"Host:",
"Content-Length: 5",
"",
"Hello");
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 0",
"",
"");
}
}
}
private async Task TestRemoteIPAddress(string registerAddress, string requestAddress, string expectAddress) private async Task TestRemoteIPAddress(string registerAddress, string requestAddress, string expectAddress)
{ {
var builder = new WebHostBuilder() var builder = new WebHostBuilder()

View File

@ -508,7 +508,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task ThrowsAndClosesConnectionWhenAppWritesMoreThanContentLengthWrite() public async Task ThrowsAndClosesConnectionWhenAppWritesMoreThanContentLengthWrite()
{ {
var testLogger = new TestApplicationErrorLogger(); var testLogger = new TestApplicationErrorLogger();
var serviceContext = new TestServiceContext { Log = new TestKestrelTrace(testLogger) }; var serviceContext = new TestServiceContext
{
Log = new TestKestrelTrace(testLogger),
ServerOptions = { AllowSynchronousIO = true }
};
using (var server = new TestServer(httpContext => using (var server = new TestServer(httpContext =>
{ {
@ -536,7 +540,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
} }
} }
var logMessage = Assert.Single(testLogger.Messages, message => message.LogLevel == LogLevel.Error); var logMessage = Assert.Single(testLogger.Messages, message => message.LogLevel == LogLevel.Error);
Assert.Equal( Assert.Equal(
@ -584,7 +587,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task InternalServerErrorAndConnectionClosedOnWriteWithMoreThanContentLengthAndResponseNotStarted() public async Task InternalServerErrorAndConnectionClosedOnWriteWithMoreThanContentLengthAndResponseNotStarted()
{ {
var testLogger = new TestApplicationErrorLogger(); var testLogger = new TestApplicationErrorLogger();
var serviceContext = new TestServiceContext { Log = new TestKestrelTrace(testLogger) }; var serviceContext = new TestServiceContext
{
Log = new TestKestrelTrace(testLogger),
ServerOptions = { AllowSynchronousIO = true }
};
using (var server = new TestServer(httpContext => using (var server = new TestServer(httpContext =>
{ {
@ -966,6 +973,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task HeadResponseBodyNotWrittenWithSyncWrite() public async Task HeadResponseBodyNotWrittenWithSyncWrite()
{ {
var flushed = new SemaphoreSlim(0, 1); var flushed = new SemaphoreSlim(0, 1);
var serviceContext = new TestServiceContext { ServerOptions = { AllowSynchronousIO = true } };
using (var server = new TestServer(httpContext => using (var server = new TestServer(httpContext =>
{ {
@ -973,7 +981,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
httpContext.Response.Body.Write(Encoding.ASCII.GetBytes("hello, world"), 0, 12); httpContext.Response.Body.Write(Encoding.ASCII.GetBytes("hello, world"), 0, 12);
flushed.Wait(); flushed.Wait();
return Task.CompletedTask; return Task.CompletedTask;
})) }, serviceContext))
{ {
using (var connection = server.CreateConnection()) using (var connection = server.CreateConnection())
{ {
@ -1248,6 +1256,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact] [Fact]
public async Task FirstWriteVerifiedAfterOnStarting() public async Task FirstWriteVerifiedAfterOnStarting()
{ {
var serviceContext = new TestServiceContext { ServerOptions = { AllowSynchronousIO = true } };
using (var server = new TestServer(httpContext => using (var server = new TestServer(httpContext =>
{ {
httpContext.Response.OnStarting(() => httpContext.Response.OnStarting(() =>
@ -1263,7 +1273,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
// If OnStarting is not run before verifying writes, an error response will be sent. // If OnStarting is not run before verifying writes, an error response will be sent.
httpContext.Response.Body.Write(response, 0, response.Length); httpContext.Response.Body.Write(response, 0, response.Length);
return Task.CompletedTask; return Task.CompletedTask;
})) }, serviceContext))
{ {
using (var connection = server.CreateConnection()) using (var connection = server.CreateConnection())
{ {
@ -1289,6 +1299,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact] [Fact]
public async Task SubsequentWriteVerifiedAfterOnStarting() public async Task SubsequentWriteVerifiedAfterOnStarting()
{ {
var serviceContext = new TestServiceContext { ServerOptions = { AllowSynchronousIO = true } };
using (var server = new TestServer(httpContext => using (var server = new TestServer(httpContext =>
{ {
httpContext.Response.OnStarting(() => httpContext.Response.OnStarting(() =>
@ -1305,7 +1317,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
httpContext.Response.Body.Write(response, 0, response.Length / 2); httpContext.Response.Body.Write(response, 0, response.Length / 2);
httpContext.Response.Body.Write(response, response.Length / 2, response.Length - response.Length / 2); httpContext.Response.Body.Write(response, response.Length / 2, response.Length - response.Length / 2);
return Task.CompletedTask; return Task.CompletedTask;
})) }, serviceContext))
{ {
using (var connection = server.CreateConnection()) using (var connection = server.CreateConnection())
{ {
@ -2335,6 +2347,96 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
Assert.Equal(2, callOrder.Pop()); Assert.Equal(2, callOrder.Pop());
} }
[Fact]
public async Task SynchronousWritesAllowedByDefault()
{
var firstRequest = true;
using (var server = new TestServer(async context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.True(bodyControlFeature.AllowSynchronousIO);
context.Response.ContentLength = 6;
if (firstRequest)
{
context.Response.Body.Write(Encoding.ASCII.GetBytes("Hello1"), 0, 6);
firstRequest = false;
}
else
{
bodyControlFeature.AllowSynchronousIO = false;
// Synchronous writes now throw.
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(Encoding.ASCII.GetBytes("What!?"), 0, 6));
Assert.Equal("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
await context.Response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello2"), 0, 6);
}
}))
{
using (var connection = server.CreateConnection())
{
await connection.SendEmptyGet();
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6",
"",
"Hello1");
await connection.SendEmptyGet();
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6",
"",
"Hello2");
}
}
}
[Fact]
public async Task SynchronousWritesCanBeDisallowedGlobally()
{
var testContext = new TestServiceContext
{
ServerOptions = { AllowSynchronousIO = false }
};
using (var server = new TestServer(context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.False(bodyControlFeature.AllowSynchronousIO);
context.Response.ContentLength = 6;
// Synchronous writes now throw.
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(Encoding.ASCII.GetBytes("What!?"), 0, 6));
Assert.Equal("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
return context.Response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello!"), 0, 6);
}, testContext))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6",
"",
"Hello!");
}
}
}
public static TheoryData<string, StringValues, string> NullHeaderData public static TheoryData<string, StringValues, string> NullHeaderData
{ {
get get

View File

@ -32,12 +32,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var feature = context.Features.Get<IHttpUpgradeFeature>(); var feature = context.Features.Get<IHttpUpgradeFeature>();
var stream = await feature.UpgradeAsync(); var stream = await feature.UpgradeAsync();
var ex = Assert.Throws<InvalidOperationException>(() => context.Response.Body.WriteByte((byte)' ')); var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.Body.WriteAsync(new byte[1], 0, 1));
Assert.Equal(CoreStrings.ResponseStreamWasUpgraded, ex.Message); Assert.Equal(CoreStrings.ResponseStreamWasUpgraded, ex.Message);
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
{ {
writer.WriteLine("New protocol data"); await writer.WriteLineAsync("New protocol data");
await writer.FlushAsync();
} }
upgrade.TrySetResult(true); upgrade.TrySetResult(true);
@ -82,6 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var line = await reader.ReadLineAsync(); var line = await reader.ReadLineAsync();
Assert.Equal(send, line); Assert.Equal(send, line);
await writer.WriteLineAsync(recv); await writer.WriteLineAsync(recv);
await writer.FlushAsync();
} }
upgrade.TrySetResult(true); upgrade.TrySetResult(true);

View File

@ -28,14 +28,14 @@ namespace CodeGenerator
typeof(IHttpRequestIdentifierFeature), typeof(IHttpRequestIdentifierFeature),
typeof(IServiceProvidersFeature), typeof(IServiceProvidersFeature),
typeof(IHttpRequestLifetimeFeature), typeof(IHttpRequestLifetimeFeature),
typeof(IHttpConnectionFeature) typeof(IHttpConnectionFeature),
}; };
var commonFeatures = new[] var commonFeatures = new[]
{ {
typeof(IHttpAuthenticationFeature), typeof(IHttpAuthenticationFeature),
typeof(IQueryFeature), typeof(IQueryFeature),
typeof(IFormFeature) typeof(IFormFeature),
}; };
var sometimesFeatures = new[] var sometimesFeatures = new[]
@ -48,6 +48,7 @@ namespace CodeGenerator
typeof(ISessionFeature), typeof(ISessionFeature),
typeof(IHttpMaxRequestBodySizeFeature), typeof(IHttpMaxRequestBodySizeFeature),
typeof(IHttpMinRequestBodyDataRateFeature), typeof(IHttpMinRequestBodyDataRateFeature),
typeof(IHttpBodyControlFeature),
}; };
var rareFeatures = new[] var rareFeatures = new[]
@ -69,6 +70,7 @@ namespace CodeGenerator
typeof(IHttpConnectionFeature), typeof(IHttpConnectionFeature),
typeof(IHttpMaxRequestBodySizeFeature), typeof(IHttpMaxRequestBodySizeFeature),
typeof(IHttpMinRequestBodyDataRateFeature), typeof(IHttpMinRequestBodyDataRateFeature),
typeof(IHttpBodyControlFeature),
}; };
return $@"// Copyright (c) .NET Foundation. All rights reserved. return $@"// Copyright (c) .NET Foundation. All rights reserved.