diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/CoreStrings.resx b/src/Microsoft.AspNetCore.Server.Kestrel.Core/CoreStrings.resx
index 13454c5127..16e59e4c3a 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/CoreStrings.resx
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/CoreStrings.resx
@@ -1,17 +1,17 @@
-
@@ -336,4 +336,10 @@
The request body rate enforcement grace period must be greater than {heartbeatInterval} second.
-
+
+ Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
+
+
+ Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.FeatureCollection.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.FeatureCollection.cs
index e9737e5b21..cbc30427e5 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.FeatureCollection.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.FeatureCollection.cs
@@ -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
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.Generated.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.Generated.cs
index f2b9c90d10..6aba909708 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.Generated.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.Generated.cs
@@ -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(IHttpMinRequestBodyDataRateFeatureType, _currentIHttpMinRequestBodyDataRateFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature);
}
+ if (_currentIHttpBodyControlFeature != null)
+ {
+ yield return new KeyValuePair(IHttpBodyControlFeatureType, _currentIHttpBodyControlFeature as global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
+ }
if (_currentIHttpSendFileFeature != null)
{
yield return new KeyValuePair(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.cs
index 90171989c3..c9e7ba118b 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.cs
@@ -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; }
///
/// The request id.
@@ -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;
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameRequestStream.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameRequestStream.cs
index 6ae4b1c385..aa2e9de5a7 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameRequestStream.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameRequestStream.cs
@@ -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)
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameResponseStream.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameResponseStream.cs
index 3a76f4bf1b..d4c6784b4c 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameResponseStream.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameResponseStream.cs
@@ -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();
}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/Streams.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/Streams.cs
index 994ee2d31c..a7762c6990 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/Streams.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/Streams.cs
@@ -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);
}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/KestrelServerOptions.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/KestrelServerOptions.cs
index b97eecf850..136b983533 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/KestrelServerOptions.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/KestrelServerOptions.cs
@@ -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
/// The default mode is
public SchedulingMode ApplicationSchedulingMode { get; set; } = SchedulingMode.Default;
+ ///
+ /// Gets or sets a value that controls whether synchronous IO is allowed for the and
+ ///
+ ///
+ /// Defaults to true.
+ ///
+ public bool AllowSynchronousIO { get; set; } = true;
+
///
/// Enables the Listen options callback to resolve and use services registered by the application during startup.
/// Typically initialized by UseKestrel()"/>.
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Properties/CoreStrings.Designer.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Properties/CoreStrings.Designer.cs
index 5fb571f55b..aa43e6b644 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Properties/CoreStrings.Designer.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Properties/CoreStrings.Designer.cs
@@ -1019,7 +1019,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
=> GetString("NonNegativeTimeSpanRequired");
///
- /// 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.
///
internal static string MinimumGracePeriodRequired
{
@@ -1027,11 +1027,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
}
///
- /// 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.
///
internal static string FormatMinimumGracePeriodRequired(object heartbeatInterval)
=> string.Format(CultureInfo.CurrentCulture, GetString("MinimumGracePeriodRequired", "heartbeatInterval"), heartbeatInterval);
+ ///
+ /// Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
+ ///
+ internal static string SynchronousReadsDisallowed
+ {
+ get => GetString("SynchronousReadsDisallowed");
+ }
+
+ ///
+ /// Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
+ ///
+ internal static string FormatSynchronousReadsDisallowed()
+ => GetString("SynchronousReadsDisallowed");
+
+ ///
+ /// Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
+ ///
+ internal static string SynchronousWritesDisallowed
+ {
+ get => GetString("SynchronousWritesDisallowed");
+ }
+
+ ///
+ /// Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
+ ///
+ internal static string FormatSynchronousWritesDisallowed()
+ => GetString("SynchronousWritesDisallowed");
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameRequestStreamTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameRequestStreamTests.cs
index 2250523f63..12a1a87c81 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameRequestStreamTests.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameRequestStreamTests.cs
@@ -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());
Assert.True(stream.CanRead);
}
[Fact]
public void CanSeekReturnsFalse()
{
- var stream = new FrameRequestStream();
+ var stream = new FrameRequestStream(Mock.Of());
Assert.False(stream.CanSeek);
}
[Fact]
public void CanWriteReturnsFalse()
{
- var stream = new FrameRequestStream();
+ var stream = new FrameRequestStream(Mock.Of());
Assert.False(stream.CanWrite);
}
[Fact]
public void SeekThrows()
{
- var stream = new FrameRequestStream();
+ var stream = new FrameRequestStream(Mock.Of());
Assert.Throws(() => stream.Seek(0, SeekOrigin.Begin));
}
[Fact]
public void LengthThrows()
{
- var stream = new FrameRequestStream();
+ var stream = new FrameRequestStream(Mock.Of());
Assert.Throws(() => stream.Length);
}
[Fact]
public void SetLengthThrows()
{
- var stream = new FrameRequestStream();
+ var stream = new FrameRequestStream(Mock.Of());
Assert.Throws(() => stream.SetLength(0));
}
[Fact]
public void PositionThrows()
{
- var stream = new FrameRequestStream();
+ var stream = new FrameRequestStream(Mock.Of());
Assert.Throws(() => stream.Position);
Assert.Throws(() => 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());
Assert.Throws(() => stream.Write(new byte[1], 0, 1));
}
[Fact]
public void WriteByteThrows()
{
- var stream = new FrameRequestStream();
+ var stream = new FrameRequestStream(Mock.Of());
Assert.Throws(() => stream.WriteByte(0));
}
[Fact]
public async Task WriteAsyncThrows()
{
- var stream = new FrameRequestStream();
+ var stream = new FrameRequestStream(Mock.Of());
await Assert.ThrowsAsync(() => 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());
Assert.Throws(() => 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());
+ Assert.Throws(() => stream.Flush());
}
[Fact]
- public async Task FlushAsyncDoesNotThrow()
+ public async Task FlushAsyncThrows()
{
- var stream = new FrameRequestStream();
- await stream.FlushAsync();
+ var stream = new FrameRequestStream(Mock.Of());
+ await Assert.ThrowsAsync(() => stream.FlushAsync());
+ }
+
+ [Fact]
+ public async Task SynchronousReadsThrowIfDisallowedByIHttpBodyControlFeature()
+ {
+ var allowSynchronousIO = false;
+ var mockBodyControl = new Mock();
+ mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(() => allowSynchronousIO);
+ var mockMessageBody = new Mock((Frame)null);
+ mockMessageBody.Setup(m => m.ReadAsync(It.IsAny>(), 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(() => 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(() => 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());
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());
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());
stream.StartAcceptingReads(null);
stream.StopAcceptingReads();
Assert.Throws(() => { 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());
stream.StartAcceptingReads(null);
stream.Abort();
var task = stream.CopyToAsync(Mock.Of());
@@ -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());
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());
stream.StartAcceptingReads(null);
stream.StopAcceptingReads();
Assert.Throws(() => { stream.CopyToAsync(Mock.Of()); });
@@ -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());
stream.StartAcceptingReads(null);
Assert.Throws(() => { 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());
stream.StartAcceptingReads(null);
Assert.Throws(() => { stream.CopyToAsync(Mock.Of(), 0); });
}
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameResponseStreamTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameResponseStreamTests.cs
index 12f0e0019c..14cc4ebb7e 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameResponseStreamTests.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameResponseStreamTests.cs
@@ -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(), new MockFrameControl());
Assert.False(stream.CanRead);
}
[Fact]
public void CanSeekReturnsFalse()
{
- var stream = new FrameResponseStream(new MockFrameControl());
+ var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl());
Assert.False(stream.CanSeek);
}
[Fact]
public void CanWriteReturnsTrue()
{
- var stream = new FrameResponseStream(new MockFrameControl());
+ var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl());
Assert.True(stream.CanWrite);
}
[Fact]
public void ReadThrows()
{
- var stream = new FrameResponseStream(new MockFrameControl());
+ var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl());
Assert.Throws(() => stream.Read(new byte[1], 0, 1));
}
[Fact]
public void ReadByteThrows()
{
- var stream = new FrameResponseStream(new MockFrameControl());
+ var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl());
Assert.Throws(() => stream.ReadByte());
}
[Fact]
public async Task ReadAsyncThrows()
{
- var stream = new FrameResponseStream(new MockFrameControl());
+ var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl());
await Assert.ThrowsAsync(() => stream.ReadAsync(new byte[1], 0, 1));
}
[Fact]
public void BeginReadThrows()
{
- var stream = new FrameResponseStream(new MockFrameControl());
+ var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl());
Assert.Throws(() => 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(), new MockFrameControl());
Assert.Throws(() => stream.Seek(0, SeekOrigin.Begin));
}
[Fact]
public void LengthThrows()
{
- var stream = new FrameResponseStream(new MockFrameControl());
+ var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl());
Assert.Throws(() => stream.Length);
}
[Fact]
public void SetLengthThrows()
{
- var stream = new FrameResponseStream(new MockFrameControl());
+ var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl());
Assert.Throws(() => stream.SetLength(0));
}
[Fact]
public void PositionThrows()
{
- var stream = new FrameResponseStream(new MockFrameControl());
+ var stream = new FrameResponseStream(Mock.Of(), new MockFrameControl());
Assert.Throws(() => stream.Position);
Assert.Throws(() => stream.Position = 0);
}
+
+ [Fact]
+ public void StopAcceptingWritesCausesWriteToThrowObjectDisposedException()
+ {
+ var stream = new FrameResponseStream(Mock.Of(), Mock.Of());
+ stream.StartAcceptingWrites();
+ stream.StopAcceptingWrites();
+ Assert.Throws(() => { stream.WriteAsync(new byte[1], 0, 1); });
+ }
+
+ [Fact]
+ public async Task SynchronousWritesThrowIfDisallowedByIHttpBodyControlFeature()
+ {
+ var allowSynchronousIO = false;
+ var mockBodyControl = new Mock();
+ mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(() => allowSynchronousIO);
+ var mockFrameControl = new Mock();
+ mockFrameControl.Setup(m => m.WriteAsync(It.IsAny>(), 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(() => 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);
+ }
}
}
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/KestrelServerOptionsTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/KestrelServerOptionsTests.cs
index e0e97f2aac..f0209cbea4 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/KestrelServerOptionsTests.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/KestrelServerOptionsTests.cs
@@ -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);
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/MessageBodyTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/MessageBodyTests.cs
index 655582dcf1..c6d2283301 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/MessageBodyTests.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/MessageBodyTests.cs
@@ -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();
+ 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());
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();
+ 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());
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());
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());
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());
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();
+ 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());
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();
+ 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());
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());
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());
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(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());
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());
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());
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());
stream.StartAcceptingReads(body);
// Add some input and consume it to ensure PumpAsync is running
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/StreamsTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/StreamsTests.cs
index 6a7b108228..b2657bc60a 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/StreamsTests.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/StreamsTests.cs
@@ -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());
+ var streams = new Streams(Mock.Of(), Mock.Of());
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());
+ var streams = new Streams(Mock.Of(), Mock.Of());
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());
+ var streams = new Streams(Mock.Of(), Mock.Of());
var (request, response) = streams.Start(new MockMessageBody(upgradeable: true));
var ex = new Exception("My error");
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs
index 40536f5d28..6b3d66de1a 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs
@@ -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");
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/RequestTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/RequestTests.cs
index c808346744..a130aadde3 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/RequestTests.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/RequestTests.cs
@@ -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();
+ 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(() => 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(() => 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();
+ Assert.False(bodyControlFeature.AllowSynchronousIO);
+
+ // Synchronous reads now throw.
+ var ioEx = Assert.Throws(() => 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(() => 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()
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs
index 155a1dac95..f43a1e5c2b 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs
@@ -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();
+ 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(() => 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();
+ Assert.False(bodyControlFeature.AllowSynchronousIO);
+
+ context.Response.ContentLength = 6;
+
+ // Synchronous writes now throw.
+ var ioEx = Assert.Throws(() => 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 NullHeaderData
{
get
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/UpgradeTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/UpgradeTests.cs
index 5fa497dd0e..8a24951173 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/UpgradeTests.cs
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/UpgradeTests.cs
@@ -32,12 +32,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var feature = context.Features.Get();
var stream = await feature.UpgradeAsync();
- var ex = Assert.Throws(() => context.Response.Body.WriteByte((byte)' '));
+ var ex = await Assert.ThrowsAsync(() => 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);
diff --git a/tools/CodeGenerator/FrameFeatureCollection.cs b/tools/CodeGenerator/FrameFeatureCollection.cs
index 636f333854..5d5f83c11a 100644
--- a/tools/CodeGenerator/FrameFeatureCollection.cs
+++ b/tools/CodeGenerator/FrameFeatureCollection.cs
@@ -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.