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"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</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>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
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
: and then encoded with base64 encoding.
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
: and then encoded with base64 encoding.
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
: and then encoded with base64 encoding.
-->
@ -336,4 +336,10 @@
<data name="MinimumGracePeriodRequired" xml:space="preserve">
<value>The request body rate enforcement grace period must be greater than {heartbeatInterval} second.</value>
</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,
IHttpRequestLifetimeFeature,
IHttpRequestIdentifierFeature,
IHttpBodyControlFeature,
IHttpMaxRequestBodySizeFeature,
IHttpMinRequestBodyDataRateFeature
{
@ -205,6 +206,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
set => TraceIdentifier = value;
}
bool IHttpBodyControlFeature.AllowSynchronousIO
{
get => AllowSynchronousIO;
set => AllowSynchronousIO = value;
}
bool IHttpMaxRequestBodySizeFeature.IsReadOnly => HasStartedConsumingRequestBody || _wasUpgraded;
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 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 IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
private object _currentIHttpRequestFeature;
@ -44,6 +45,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private object _currentISessionFeature;
private object _currentIHttpMaxRequestBodySizeFeature;
private object _currentIHttpMinRequestBodyDataRateFeature;
private object _currentIHttpBodyControlFeature;
private object _currentIHttpSendFileFeature;
private void FastReset()
@ -56,6 +58,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_currentIHttpConnectionFeature = this;
_currentIHttpMaxRequestBodySizeFeature = this;
_currentIHttpMinRequestBodyDataRateFeature = this;
_currentIHttpBodyControlFeature = this;
_currentIServiceProvidersFeature = null;
_currentIHttpAuthenticationFeature = null;
@ -139,6 +142,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
return _currentIHttpMinRequestBodyDataRateFeature;
}
if (key == IHttpBodyControlFeatureType)
{
return _currentIHttpBodyControlFeature;
}
if (key == IHttpSendFileFeatureType)
{
return _currentIHttpSendFileFeature;
@ -235,6 +242,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_currentIHttpMinRequestBodyDataRateFeature = feature;
return;
}
if (key == IHttpBodyControlFeatureType)
{
_currentIHttpBodyControlFeature = feature;
return;
}
if (key == IHttpSendFileFeatureType)
{
_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);
}
if (_currentIHttpBodyControlFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpBodyControlFeatureType, _currentIHttpBodyControlFeature as global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
}
if (_currentIHttpSendFileFeature != null)
{
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 bool HasStartedConsumingRequestBody { get; set; }
public long? MaxRequestBodySize { get; set; }
public bool AllowSynchronousIO { get; set; }
/// <summary>
/// The request id. <seealso cref="HttpContext.TraceIdentifier"/>
@ -305,7 +306,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
if (_frameStreams == null)
{
_frameStreams = new Streams(this);
_frameStreams = new Streams(bodyControl: this, frameControl: this);
}
(RequestBody, ResponseBody) = _frameStreams.Start(messageBody);
@ -329,6 +330,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
HasStartedConsumingRequestBody = false;
MaxRequestBodySize = ServerOptions.Limits.MaxRequestBodySize;
AllowSynchronousIO = ServerOptions.AllowSynchronousIO;
TraceIdentifier = null;
Scheme = null;
Method = null;

View File

@ -7,17 +7,20 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
internal class FrameRequestStream : ReadOnlyStream
{
private readonly IHttpBodyControlFeature _bodyControl;
private MessageBody _body;
private FrameStreamState _state;
private Exception _error;
public FrameRequestStream()
public FrameRequestStream(IHttpBodyControlFeature bodyControl)
{
_bodyControl = bodyControl;
_state = FrameStreamState.Closed;
}
@ -34,13 +37,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public override void Flush()
{
// No-op.
throw new NotSupportedException();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
// No-op.
return Task.CompletedTask;
throw new NotSupportedException();
}
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)
{
// ValueTask uses .GetAwaiter().GetResult() if necessary
return ReadAsync(buffer, offset, count).Result;
if (!_bodyControl.AllowSynchronousIO)
{
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)

View File

@ -6,16 +6,19 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
internal class FrameResponseStream : WriteOnlyStream
{
private IFrameControl _frameControl;
private readonly IHttpBodyControlFeature _bodyControl;
private readonly IFrameControl _frameControl;
private FrameStreamState _state;
public FrameResponseStream(IFrameControl frameControl)
public FrameResponseStream(IHttpBodyControlFeature bodyControl, IFrameControl frameControl)
{
_bodyControl = bodyControl;
_frameControl = frameControl;
_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)
{
if (!_bodyControl.AllowSynchronousIO)
{
throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed);
}
WriteAsync(buffer, offset, count, default(CancellationToken)).GetAwaiter().GetResult();
}

View File

@ -3,6 +3,7 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
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 Stream _upgradeStream;
public Streams(IFrameControl frameControl)
public Streams(IHttpBodyControlFeature bodyControl, IFrameControl frameControl)
{
_request = new FrameRequestStream();
_emptyRequest = new FrameRequestStream();
_response = new FrameResponseStream(frameControl);
_request = new FrameRequestStream(bodyControl);
_emptyRequest = new FrameRequestStream(bodyControl);
_response = new FrameResponseStream(bodyControl, frameControl);
_upgradeableResponse = new WrappingStream(_response);
_upgradeStream = new FrameDuplexStream(_request, _response);
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
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>
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>
/// Enables the Listen options callback to resolve and use services registered by the application during startup.
/// Typically initialized by UseKestrel()"/>.

View File

@ -1019,7 +1019,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
=> GetString("NonNegativeTimeSpanRequired");
/// <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>
internal static string MinimumGracePeriodRequired
{
@ -1027,11 +1027,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
}
/// <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>
internal static string FormatMinimumGracePeriodRequired(object 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)
{
var value = _resourceManager.GetString(name);

View File

@ -5,6 +5,7 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Moq;
using Xunit;
@ -16,49 +17,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void CanReadReturnsTrue()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.True(stream.CanRead);
}
[Fact]
public void CanSeekReturnsFalse()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.False(stream.CanSeek);
}
[Fact]
public void CanWriteReturnsFalse()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.False(stream.CanWrite);
}
[Fact]
public void SeekThrows()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.Seek(0, SeekOrigin.Begin));
}
[Fact]
public void LengthThrows()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.Length);
}
[Fact]
public void SetLengthThrows()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.SetLength(0));
}
[Fact]
public void PositionThrows()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.Position);
Assert.Throws<NotSupportedException>(() => stream.Position = 0);
}
@ -66,21 +67,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void WriteThrows()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.Write(new byte[1], 0, 1));
}
[Fact]
public void WriteByteThrows()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.WriteByte(0));
}
[Fact]
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));
}
@ -88,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
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));
}
#elif NETCOREAPP2_0
@ -97,23 +98,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
#endif
[Fact]
public void FlushDoesNotThrow()
public void FlushThrows()
{
var stream = new FrameRequestStream();
stream.Flush();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
Assert.Throws<NotSupportedException>(() => stream.Flush());
}
[Fact]
public async Task FlushAsyncDoesNotThrow()
public async Task FlushAsyncThrows()
{
var stream = new FrameRequestStream();
await stream.FlushAsync();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
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]
public void AbortCausesReadToCancel()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null);
stream.Abort();
var task = stream.ReadAsync(new byte[1], 0, 1);
@ -123,7 +148,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void AbortWithErrorCausesReadToCancel()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null);
var error = new Exception();
stream.Abort(error);
@ -135,7 +160,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void StopAcceptingReadsCausesReadToThrowObjectDisposedException()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null);
stream.StopAcceptingReads();
Assert.Throws<ObjectDisposedException>(() => { stream.ReadAsync(new byte[1], 0, 1); });
@ -144,7 +169,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void AbortCausesCopyToAsyncToCancel()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null);
stream.Abort();
var task = stream.CopyToAsync(Mock.Of<Stream>());
@ -154,7 +179,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void AbortWithErrorCausesCopyToAsyncToCancel()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null);
var error = new Exception();
stream.Abort(error);
@ -166,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void StopAcceptingReadsCausesCopyToAsyncToThrowObjectDisposedException()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null);
stream.StopAcceptingReads();
Assert.Throws<ObjectDisposedException>(() => { stream.CopyToAsync(Mock.Of<Stream>()); });
@ -175,7 +200,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void NullDestinationCausesCopyToAsyncToThrowArgumentNullException()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null);
Assert.Throws<ArgumentNullException>(() => { stream.CopyToAsync(null); });
}
@ -183,7 +208,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void ZeroBufferSizeCausesCopyToAsyncToThrowArgumentException()
{
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null);
Assert.Throws<ArgumentException>(() => { stream.CopyToAsync(Mock.Of<Stream>(), 0); });
}

View File

@ -3,9 +3,12 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Tests.TestHelpers;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
@ -15,79 +18,111 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void CanReadReturnsFalse()
{
var stream = new FrameResponseStream(new MockFrameControl());
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.False(stream.CanRead);
}
[Fact]
public void CanSeekReturnsFalse()
{
var stream = new FrameResponseStream(new MockFrameControl());
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.False(stream.CanSeek);
}
[Fact]
public void CanWriteReturnsTrue()
{
var stream = new FrameResponseStream(new MockFrameControl());
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.True(stream.CanWrite);
}
[Fact]
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));
}
[Fact]
public void ReadByteThrows()
{
var stream = new FrameResponseStream(new MockFrameControl());
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.Throws<NotSupportedException>(() => stream.ReadByte());
}
[Fact]
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));
}
[Fact]
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));
}
[Fact]
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));
}
[Fact]
public void LengthThrows()
{
var stream = new FrameResponseStream(new MockFrameControl());
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.Throws<NotSupportedException>(() => stream.Length);
}
[Fact]
public void SetLengthThrows()
{
var stream = new FrameResponseStream(new MockFrameControl());
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
Assert.Throws<NotSupportedException>(() => stream.SetLength(0));
}
[Fact]
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 = 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.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.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
@ -28,7 +29,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
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);
input.Add("Hello");
@ -54,7 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
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);
input.Add("Hello");
@ -78,7 +81,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
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);
input.Add("5\r\nHello\r\n");
@ -104,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
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);
input.Add("5\r\nHello\r\n");
@ -130,7 +135,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
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);
input.Add("5;\r\0");
@ -155,7 +160,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
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);
input.Add("80000000\r\n");
@ -176,7 +181,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
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);
input.Add("012345678\r");
@ -199,7 +204,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
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);
input.Add("Hello");
@ -224,7 +231,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
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);
input.Add("Hello");
@ -249,7 +256,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
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);
input.Add("Hello");
@ -269,7 +278,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame);
var stream = new FrameRequestStream();
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(body);
input.Add("Hello");
@ -287,7 +296,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
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);
// 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())
{
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);
input.Add("Hello");
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));
input.Fin();
@ -500,7 +509,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
using (var input = new TestInput())
{
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);
// 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())
{
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);
// 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";
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);
// 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";
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);
// Add some input and consume it to ensure PumpAsync is running

View File

@ -4,9 +4,11 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
using Microsoft.AspNetCore.Testing;
using Moq;
using Xunit;
@ -17,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
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 ex = new Exception("My error");
@ -31,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
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 upgrade = streams.Upgrade();
@ -53,7 +55,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
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 ex = new Exception("My error");

View File

@ -291,7 +291,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await clientFinishedSendingRequestBody.Task.TimeoutAfter(TimeSpan.FromSeconds(120));
// 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;
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)
{
var builder = new WebHostBuilder()

View File

@ -508,7 +508,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task ThrowsAndClosesConnectionWhenAppWritesMoreThanContentLengthWrite()
{
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 =>
{
@ -536,7 +540,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
var logMessage = Assert.Single(testLogger.Messages, message => message.LogLevel == LogLevel.Error);
Assert.Equal(
@ -584,7 +587,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task InternalServerErrorAndConnectionClosedOnWriteWithMoreThanContentLengthAndResponseNotStarted()
{
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 =>
{
@ -966,6 +973,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task HeadResponseBodyNotWrittenWithSyncWrite()
{
var flushed = new SemaphoreSlim(0, 1);
var serviceContext = new TestServiceContext { ServerOptions = { AllowSynchronousIO = true } };
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);
flushed.Wait();
return Task.CompletedTask;
}))
}, serviceContext))
{
using (var connection = server.CreateConnection())
{
@ -1248,6 +1256,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact]
public async Task FirstWriteVerifiedAfterOnStarting()
{
var serviceContext = new TestServiceContext { ServerOptions = { AllowSynchronousIO = true } };
using (var server = new TestServer(httpContext =>
{
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.
httpContext.Response.Body.Write(response, 0, response.Length);
return Task.CompletedTask;
}))
}, serviceContext))
{
using (var connection = server.CreateConnection())
{
@ -1289,6 +1299,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact]
public async Task SubsequentWriteVerifiedAfterOnStarting()
{
var serviceContext = new TestServiceContext { ServerOptions = { AllowSynchronousIO = true } };
using (var server = new TestServer(httpContext =>
{
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, response.Length / 2, response.Length - response.Length / 2);
return Task.CompletedTask;
}))
}, serviceContext))
{
using (var connection = server.CreateConnection())
{
@ -2335,6 +2347,96 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
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
{
get

View File

@ -32,12 +32,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var feature = context.Features.Get<IHttpUpgradeFeature>();
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);
using (var writer = new StreamWriter(stream))
{
writer.WriteLine("New protocol data");
await writer.WriteLineAsync("New protocol data");
await writer.FlushAsync();
}
upgrade.TrySetResult(true);
@ -82,6 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var line = await reader.ReadLineAsync();
Assert.Equal(send, line);
await writer.WriteLineAsync(recv);
await writer.FlushAsync();
}
upgrade.TrySetResult(true);

View File

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