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

@ -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>
<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.