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