Add feature to optionally disallow synchronous IO (#1919)
* Allow synchronous IO by default
This commit is contained in:
parent
6e45de2205
commit
e9ffcdb414
|
|
@ -336,4 +336,10 @@
|
||||||
<data name="MinimumGracePeriodRequired" xml:space="preserve">
|
<data name="MinimumGracePeriodRequired" xml:space="preserve">
|
||||||
<value>The request body rate enforcement grace period must be greater than {heartbeatInterval} second.</value>
|
<value>The request body rate enforcement grace period must be greater than {heartbeatInterval} second.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<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>
|
</root>
|
||||||
|
|
@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
IHttpConnectionFeature,
|
IHttpConnectionFeature,
|
||||||
IHttpRequestLifetimeFeature,
|
IHttpRequestLifetimeFeature,
|
||||||
IHttpRequestIdentifierFeature,
|
IHttpRequestIdentifierFeature,
|
||||||
|
IHttpBodyControlFeature,
|
||||||
IHttpMaxRequestBodySizeFeature,
|
IHttpMaxRequestBodySizeFeature,
|
||||||
IHttpMinRequestBodyDataRateFeature
|
IHttpMinRequestBodyDataRateFeature
|
||||||
{
|
{
|
||||||
|
|
@ -205,6 +206,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
set => TraceIdentifier = value;
|
set => TraceIdentifier = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IHttpBodyControlFeature.AllowSynchronousIO
|
||||||
|
{
|
||||||
|
get => AllowSynchronousIO;
|
||||||
|
set => AllowSynchronousIO = value;
|
||||||
|
}
|
||||||
|
|
||||||
bool IHttpMaxRequestBodySizeFeature.IsReadOnly => HasStartedConsumingRequestBody || _wasUpgraded;
|
bool IHttpMaxRequestBodySizeFeature.IsReadOnly => HasStartedConsumingRequestBody || _wasUpgraded;
|
||||||
|
|
||||||
long? IHttpMaxRequestBodySizeFeature.MaxRequestBodySize
|
long? IHttpMaxRequestBodySizeFeature.MaxRequestBodySize
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
|
private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
|
||||||
private static readonly Type IHttpMaxRequestBodySizeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature);
|
private static readonly Type IHttpMaxRequestBodySizeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature);
|
||||||
private static readonly Type IHttpMinRequestBodyDataRateFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature);
|
private static readonly Type IHttpMinRequestBodyDataRateFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature);
|
||||||
|
private static readonly Type IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
|
||||||
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
||||||
|
|
||||||
private object _currentIHttpRequestFeature;
|
private object _currentIHttpRequestFeature;
|
||||||
|
|
@ -44,6 +45,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private object _currentISessionFeature;
|
private object _currentISessionFeature;
|
||||||
private object _currentIHttpMaxRequestBodySizeFeature;
|
private object _currentIHttpMaxRequestBodySizeFeature;
|
||||||
private object _currentIHttpMinRequestBodyDataRateFeature;
|
private object _currentIHttpMinRequestBodyDataRateFeature;
|
||||||
|
private object _currentIHttpBodyControlFeature;
|
||||||
private object _currentIHttpSendFileFeature;
|
private object _currentIHttpSendFileFeature;
|
||||||
|
|
||||||
private void FastReset()
|
private void FastReset()
|
||||||
|
|
@ -56,6 +58,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
_currentIHttpConnectionFeature = this;
|
_currentIHttpConnectionFeature = this;
|
||||||
_currentIHttpMaxRequestBodySizeFeature = this;
|
_currentIHttpMaxRequestBodySizeFeature = this;
|
||||||
_currentIHttpMinRequestBodyDataRateFeature = this;
|
_currentIHttpMinRequestBodyDataRateFeature = this;
|
||||||
|
_currentIHttpBodyControlFeature = this;
|
||||||
|
|
||||||
_currentIServiceProvidersFeature = null;
|
_currentIServiceProvidersFeature = null;
|
||||||
_currentIHttpAuthenticationFeature = null;
|
_currentIHttpAuthenticationFeature = null;
|
||||||
|
|
@ -139,6 +142,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
return _currentIHttpMinRequestBodyDataRateFeature;
|
return _currentIHttpMinRequestBodyDataRateFeature;
|
||||||
}
|
}
|
||||||
|
if (key == IHttpBodyControlFeatureType)
|
||||||
|
{
|
||||||
|
return _currentIHttpBodyControlFeature;
|
||||||
|
}
|
||||||
if (key == IHttpSendFileFeatureType)
|
if (key == IHttpSendFileFeatureType)
|
||||||
{
|
{
|
||||||
return _currentIHttpSendFileFeature;
|
return _currentIHttpSendFileFeature;
|
||||||
|
|
@ -235,6 +242,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
_currentIHttpMinRequestBodyDataRateFeature = feature;
|
_currentIHttpMinRequestBodyDataRateFeature = feature;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (key == IHttpBodyControlFeatureType)
|
||||||
|
{
|
||||||
|
_currentIHttpBodyControlFeature = feature;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (key == IHttpSendFileFeatureType)
|
if (key == IHttpSendFileFeatureType)
|
||||||
{
|
{
|
||||||
_currentIHttpSendFileFeature = feature;
|
_currentIHttpSendFileFeature = feature;
|
||||||
|
|
@ -313,6 +325,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
yield return new KeyValuePair<Type, object>(IHttpMinRequestBodyDataRateFeatureType, _currentIHttpMinRequestBodyDataRateFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature);
|
yield return new KeyValuePair<Type, object>(IHttpMinRequestBodyDataRateFeatureType, _currentIHttpMinRequestBodyDataRateFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature);
|
||||||
}
|
}
|
||||||
|
if (_currentIHttpBodyControlFeature != null)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<Type, object>(IHttpBodyControlFeatureType, _currentIHttpBodyControlFeature as global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
|
||||||
|
}
|
||||||
if (_currentIHttpSendFileFeature != null)
|
if (_currentIHttpSendFileFeature != null)
|
||||||
{
|
{
|
||||||
yield return new KeyValuePair<Type, object>(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
yield return new KeyValuePair<Type, object>(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
public string ConnectionIdFeature { get; set; }
|
public string ConnectionIdFeature { get; set; }
|
||||||
public bool HasStartedConsumingRequestBody { get; set; }
|
public bool HasStartedConsumingRequestBody { get; set; }
|
||||||
public long? MaxRequestBodySize { get; set; }
|
public long? MaxRequestBodySize { get; set; }
|
||||||
|
public bool AllowSynchronousIO { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The request id. <seealso cref="HttpContext.TraceIdentifier"/>
|
/// The request id. <seealso cref="HttpContext.TraceIdentifier"/>
|
||||||
|
|
@ -305,7 +306,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
if (_frameStreams == null)
|
if (_frameStreams == null)
|
||||||
{
|
{
|
||||||
_frameStreams = new Streams(this);
|
_frameStreams = new Streams(bodyControl: this, frameControl: this);
|
||||||
}
|
}
|
||||||
|
|
||||||
(RequestBody, ResponseBody) = _frameStreams.Start(messageBody);
|
(RequestBody, ResponseBody) = _frameStreams.Start(messageBody);
|
||||||
|
|
@ -329,6 +330,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
HasStartedConsumingRequestBody = false;
|
HasStartedConsumingRequestBody = false;
|
||||||
MaxRequestBodySize = ServerOptions.Limits.MaxRequestBodySize;
|
MaxRequestBodySize = ServerOptions.Limits.MaxRequestBodySize;
|
||||||
|
AllowSynchronousIO = ServerOptions.AllowSynchronousIO;
|
||||||
TraceIdentifier = null;
|
TraceIdentifier = null;
|
||||||
Scheme = null;
|
Scheme = null;
|
||||||
Method = null;
|
Method = null;
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,20 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
internal class FrameRequestStream : ReadOnlyStream
|
internal class FrameRequestStream : ReadOnlyStream
|
||||||
{
|
{
|
||||||
|
private readonly IHttpBodyControlFeature _bodyControl;
|
||||||
private MessageBody _body;
|
private MessageBody _body;
|
||||||
private FrameStreamState _state;
|
private FrameStreamState _state;
|
||||||
private Exception _error;
|
private Exception _error;
|
||||||
|
|
||||||
public FrameRequestStream()
|
public FrameRequestStream(IHttpBodyControlFeature bodyControl)
|
||||||
{
|
{
|
||||||
|
_bodyControl = bodyControl;
|
||||||
_state = FrameStreamState.Closed;
|
_state = FrameStreamState.Closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,13 +37,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
public override void Flush()
|
public override void Flush()
|
||||||
{
|
{
|
||||||
// No-op.
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// No-op.
|
throw new NotSupportedException();
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
|
@ -55,8 +57,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
// ValueTask uses .GetAwaiter().GetResult() if necessary
|
if (!_bodyControl.AllowSynchronousIO)
|
||||||
return ReadAsync(buffer, offset, count).Result;
|
{
|
||||||
|
throw new InvalidOperationException(CoreStrings.SynchronousReadsDisallowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,19 @@ using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
internal class FrameResponseStream : WriteOnlyStream
|
internal class FrameResponseStream : WriteOnlyStream
|
||||||
{
|
{
|
||||||
private IFrameControl _frameControl;
|
private readonly IHttpBodyControlFeature _bodyControl;
|
||||||
|
private readonly IFrameControl _frameControl;
|
||||||
private FrameStreamState _state;
|
private FrameStreamState _state;
|
||||||
|
|
||||||
public FrameResponseStream(IFrameControl frameControl)
|
public FrameResponseStream(IHttpBodyControlFeature bodyControl, IFrameControl frameControl)
|
||||||
{
|
{
|
||||||
|
_bodyControl = bodyControl;
|
||||||
_frameControl = frameControl;
|
_frameControl = frameControl;
|
||||||
_state = FrameStreamState.Closed;
|
_state = FrameStreamState.Closed;
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +61,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
|
if (!_bodyControl.AllowSynchronousIO)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed);
|
||||||
|
}
|
||||||
|
|
||||||
WriteAsync(buffer, offset, count, default(CancellationToken)).GetAwaiter().GetResult();
|
WriteAsync(buffer, offset, count, default(CancellationToken)).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
|
|
@ -17,11 +18,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
private readonly FrameRequestStream _emptyRequest;
|
private readonly FrameRequestStream _emptyRequest;
|
||||||
private readonly Stream _upgradeStream;
|
private readonly Stream _upgradeStream;
|
||||||
|
|
||||||
public Streams(IFrameControl frameControl)
|
public Streams(IHttpBodyControlFeature bodyControl, IFrameControl frameControl)
|
||||||
{
|
{
|
||||||
_request = new FrameRequestStream();
|
_request = new FrameRequestStream(bodyControl);
|
||||||
_emptyRequest = new FrameRequestStream();
|
_emptyRequest = new FrameRequestStream(bodyControl);
|
||||||
_response = new FrameResponseStream(frameControl);
|
_response = new FrameResponseStream(bodyControl, frameControl);
|
||||||
_upgradeableResponse = new WrappingStream(_response);
|
_upgradeableResponse = new WrappingStream(_response);
|
||||||
_upgradeStream = new FrameDuplexStream(_request, _response);
|
_upgradeStream = new FrameDuplexStream(_request, _response);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
|
|
@ -35,6 +36,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
/// <remarks>The default mode is <see cref="SchedulingMode.Default"/></remarks>
|
/// <remarks>The default mode is <see cref="SchedulingMode.Default"/></remarks>
|
||||||
public SchedulingMode ApplicationSchedulingMode { get; set; } = SchedulingMode.Default;
|
public SchedulingMode ApplicationSchedulingMode { get; set; } = SchedulingMode.Default;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Defaults to true.
|
||||||
|
/// </remarks>
|
||||||
|
public bool AllowSynchronousIO { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables the Listen options callback to resolve and use services registered by the application during startup.
|
/// Enables the Listen options callback to resolve and use services registered by the application during startup.
|
||||||
/// Typically initialized by UseKestrel()"/>.
|
/// Typically initialized by UseKestrel()"/>.
|
||||||
|
|
|
||||||
|
|
@ -1019,7 +1019,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
=> GetString("NonNegativeTimeSpanRequired");
|
=> GetString("NonNegativeTimeSpanRequired");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The request body rate enforcement grace period must be greater than {heartbeatInterval} seconds.
|
/// The request body rate enforcement grace period must be greater than {heartbeatInterval} second.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string MinimumGracePeriodRequired
|
internal static string MinimumGracePeriodRequired
|
||||||
{
|
{
|
||||||
|
|
@ -1027,11 +1027,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The request body rate enforcement grace period must be greater than {heartbeatInterval} seconds.
|
/// The request body rate enforcement grace period must be greater than {heartbeatInterval} second.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatMinimumGracePeriodRequired(object heartbeatInterval)
|
internal static string FormatMinimumGracePeriodRequired(object heartbeatInterval)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("MinimumGracePeriodRequired", "heartbeatInterval"), heartbeatInterval);
|
=> string.Format(CultureInfo.CurrentCulture, GetString("MinimumGracePeriodRequired", "heartbeatInterval"), heartbeatInterval);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
|
||||||
|
/// </summary>
|
||||||
|
internal static string SynchronousReadsDisallowed
|
||||||
|
{
|
||||||
|
get => GetString("SynchronousReadsDisallowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatSynchronousReadsDisallowed()
|
||||||
|
=> GetString("SynchronousReadsDisallowed");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
|
||||||
|
/// </summary>
|
||||||
|
internal static string SynchronousWritesDisallowed
|
||||||
|
{
|
||||||
|
get => GetString("SynchronousWritesDisallowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatSynchronousWritesDisallowed()
|
||||||
|
=> GetString("SynchronousWritesDisallowed");
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -16,49 +17,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanReadReturnsTrue()
|
public void CanReadReturnsTrue()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
Assert.True(stream.CanRead);
|
Assert.True(stream.CanRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanSeekReturnsFalse()
|
public void CanSeekReturnsFalse()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
Assert.False(stream.CanSeek);
|
Assert.False(stream.CanSeek);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanWriteReturnsFalse()
|
public void CanWriteReturnsFalse()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
Assert.False(stream.CanWrite);
|
Assert.False(stream.CanWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SeekThrows()
|
public void SeekThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Seek(0, SeekOrigin.Begin));
|
Assert.Throws<NotSupportedException>(() => stream.Seek(0, SeekOrigin.Begin));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void LengthThrows()
|
public void LengthThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Length);
|
Assert.Throws<NotSupportedException>(() => stream.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SetLengthThrows()
|
public void SetLengthThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.SetLength(0));
|
Assert.Throws<NotSupportedException>(() => stream.SetLength(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PositionThrows()
|
public void PositionThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Position);
|
Assert.Throws<NotSupportedException>(() => stream.Position);
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Position = 0);
|
Assert.Throws<NotSupportedException>(() => stream.Position = 0);
|
||||||
}
|
}
|
||||||
|
|
@ -66,21 +67,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void WriteThrows()
|
public void WriteThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Write(new byte[1], 0, 1));
|
Assert.Throws<NotSupportedException>(() => stream.Write(new byte[1], 0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void WriteByteThrows()
|
public void WriteByteThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.WriteByte(0));
|
Assert.Throws<NotSupportedException>(() => stream.WriteByte(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task WriteAsyncThrows()
|
public async Task WriteAsyncThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
await Assert.ThrowsAsync<NotSupportedException>(() => stream.WriteAsync(new byte[1], 0, 1));
|
await Assert.ThrowsAsync<NotSupportedException>(() => stream.WriteAsync(new byte[1], 0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void BeginWriteThrows()
|
public void BeginWriteThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.BeginWrite(new byte[1], 0, 1, null, null));
|
Assert.Throws<NotSupportedException>(() => stream.BeginWrite(new byte[1], 0, 1, null, null));
|
||||||
}
|
}
|
||||||
#elif NETCOREAPP2_0
|
#elif NETCOREAPP2_0
|
||||||
|
|
@ -97,23 +98,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FlushDoesNotThrow()
|
public void FlushThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.Flush();
|
Assert.Throws<NotSupportedException>(() => stream.Flush());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task FlushAsyncDoesNotThrow()
|
public async Task FlushAsyncThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
await stream.FlushAsync();
|
await Assert.ThrowsAsync<NotSupportedException>(() => stream.FlushAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SynchronousReadsThrowIfDisallowedByIHttpBodyControlFeature()
|
||||||
|
{
|
||||||
|
var allowSynchronousIO = false;
|
||||||
|
var mockBodyControl = new Mock<IHttpBodyControlFeature>();
|
||||||
|
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(() => allowSynchronousIO);
|
||||||
|
var mockMessageBody = new Mock<MessageBody>((Frame)null);
|
||||||
|
mockMessageBody.Setup(m => m.ReadAsync(It.IsAny<ArraySegment<byte>>(), CancellationToken.None)).ReturnsAsync(0);
|
||||||
|
|
||||||
|
var stream = new FrameRequestStream(mockBodyControl.Object);
|
||||||
|
stream.StartAcceptingReads(mockMessageBody.Object);
|
||||||
|
|
||||||
|
Assert.Equal(0, await stream.ReadAsync(new byte[1], 0, 1));
|
||||||
|
|
||||||
|
var ioEx = Assert.Throws<InvalidOperationException>(() => stream.Read(new byte[1], 0, 1));
|
||||||
|
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
|
||||||
|
|
||||||
|
var ioEx2 = Assert.Throws<InvalidOperationException>(() => stream.CopyTo(Stream.Null));
|
||||||
|
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx2.Message);
|
||||||
|
|
||||||
|
allowSynchronousIO = true;
|
||||||
|
Assert.Equal(0, stream.Read(new byte[1], 0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AbortCausesReadToCancel()
|
public void AbortCausesReadToCancel()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(null);
|
stream.StartAcceptingReads(null);
|
||||||
stream.Abort();
|
stream.Abort();
|
||||||
var task = stream.ReadAsync(new byte[1], 0, 1);
|
var task = stream.ReadAsync(new byte[1], 0, 1);
|
||||||
|
|
@ -123,7 +148,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AbortWithErrorCausesReadToCancel()
|
public void AbortWithErrorCausesReadToCancel()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(null);
|
stream.StartAcceptingReads(null);
|
||||||
var error = new Exception();
|
var error = new Exception();
|
||||||
stream.Abort(error);
|
stream.Abort(error);
|
||||||
|
|
@ -135,7 +160,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StopAcceptingReadsCausesReadToThrowObjectDisposedException()
|
public void StopAcceptingReadsCausesReadToThrowObjectDisposedException()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(null);
|
stream.StartAcceptingReads(null);
|
||||||
stream.StopAcceptingReads();
|
stream.StopAcceptingReads();
|
||||||
Assert.Throws<ObjectDisposedException>(() => { stream.ReadAsync(new byte[1], 0, 1); });
|
Assert.Throws<ObjectDisposedException>(() => { stream.ReadAsync(new byte[1], 0, 1); });
|
||||||
|
|
@ -144,7 +169,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AbortCausesCopyToAsyncToCancel()
|
public void AbortCausesCopyToAsyncToCancel()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(null);
|
stream.StartAcceptingReads(null);
|
||||||
stream.Abort();
|
stream.Abort();
|
||||||
var task = stream.CopyToAsync(Mock.Of<Stream>());
|
var task = stream.CopyToAsync(Mock.Of<Stream>());
|
||||||
|
|
@ -154,7 +179,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AbortWithErrorCausesCopyToAsyncToCancel()
|
public void AbortWithErrorCausesCopyToAsyncToCancel()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(null);
|
stream.StartAcceptingReads(null);
|
||||||
var error = new Exception();
|
var error = new Exception();
|
||||||
stream.Abort(error);
|
stream.Abort(error);
|
||||||
|
|
@ -166,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StopAcceptingReadsCausesCopyToAsyncToThrowObjectDisposedException()
|
public void StopAcceptingReadsCausesCopyToAsyncToThrowObjectDisposedException()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(null);
|
stream.StartAcceptingReads(null);
|
||||||
stream.StopAcceptingReads();
|
stream.StopAcceptingReads();
|
||||||
Assert.Throws<ObjectDisposedException>(() => { stream.CopyToAsync(Mock.Of<Stream>()); });
|
Assert.Throws<ObjectDisposedException>(() => { stream.CopyToAsync(Mock.Of<Stream>()); });
|
||||||
|
|
@ -175,7 +200,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void NullDestinationCausesCopyToAsyncToThrowArgumentNullException()
|
public void NullDestinationCausesCopyToAsyncToThrowArgumentNullException()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(null);
|
stream.StartAcceptingReads(null);
|
||||||
Assert.Throws<ArgumentNullException>(() => { stream.CopyToAsync(null); });
|
Assert.Throws<ArgumentNullException>(() => { stream.CopyToAsync(null); });
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +208,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ZeroBufferSizeCausesCopyToAsyncToThrowArgumentException()
|
public void ZeroBufferSizeCausesCopyToAsyncToThrowArgumentException()
|
||||||
{
|
{
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(null);
|
stream.StartAcceptingReads(null);
|
||||||
Assert.Throws<ArgumentException>(() => { stream.CopyToAsync(Mock.Of<Stream>(), 0); });
|
Assert.Throws<ArgumentException>(() => { stream.CopyToAsync(Mock.Of<Stream>(), 0); });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,12 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Tests.TestHelpers;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Tests.TestHelpers;
|
||||||
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
@ -15,79 +18,111 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanReadReturnsFalse()
|
public void CanReadReturnsFalse()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream(new MockFrameControl());
|
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
|
||||||
Assert.False(stream.CanRead);
|
Assert.False(stream.CanRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanSeekReturnsFalse()
|
public void CanSeekReturnsFalse()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream(new MockFrameControl());
|
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
|
||||||
Assert.False(stream.CanSeek);
|
Assert.False(stream.CanSeek);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanWriteReturnsTrue()
|
public void CanWriteReturnsTrue()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream(new MockFrameControl());
|
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
|
||||||
Assert.True(stream.CanWrite);
|
Assert.True(stream.CanWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ReadThrows()
|
public void ReadThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream(new MockFrameControl());
|
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Read(new byte[1], 0, 1));
|
Assert.Throws<NotSupportedException>(() => stream.Read(new byte[1], 0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ReadByteThrows()
|
public void ReadByteThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream(new MockFrameControl());
|
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.ReadByte());
|
Assert.Throws<NotSupportedException>(() => stream.ReadByte());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ReadAsyncThrows()
|
public async Task ReadAsyncThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream(new MockFrameControl());
|
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
|
||||||
await Assert.ThrowsAsync<NotSupportedException>(() => stream.ReadAsync(new byte[1], 0, 1));
|
await Assert.ThrowsAsync<NotSupportedException>(() => stream.ReadAsync(new byte[1], 0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void BeginReadThrows()
|
public void BeginReadThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream(new MockFrameControl());
|
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.BeginRead(new byte[1], 0, 1, null, null));
|
Assert.Throws<NotSupportedException>(() => stream.BeginRead(new byte[1], 0, 1, null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SeekThrows()
|
public void SeekThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream(new MockFrameControl());
|
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Seek(0, SeekOrigin.Begin));
|
Assert.Throws<NotSupportedException>(() => stream.Seek(0, SeekOrigin.Begin));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void LengthThrows()
|
public void LengthThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream(new MockFrameControl());
|
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Length);
|
Assert.Throws<NotSupportedException>(() => stream.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SetLengthThrows()
|
public void SetLengthThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream(new MockFrameControl());
|
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.SetLength(0));
|
Assert.Throws<NotSupportedException>(() => stream.SetLength(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PositionThrows()
|
public void PositionThrows()
|
||||||
{
|
{
|
||||||
var stream = new FrameResponseStream(new MockFrameControl());
|
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), new MockFrameControl());
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Position);
|
Assert.Throws<NotSupportedException>(() => stream.Position);
|
||||||
Assert.Throws<NotSupportedException>(() => stream.Position = 0);
|
Assert.Throws<NotSupportedException>(() => stream.Position = 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void StopAcceptingWritesCausesWriteToThrowObjectDisposedException()
|
||||||
|
{
|
||||||
|
var stream = new FrameResponseStream(Mock.Of<IHttpBodyControlFeature>(), Mock.Of<IFrameControl>());
|
||||||
|
stream.StartAcceptingWrites();
|
||||||
|
stream.StopAcceptingWrites();
|
||||||
|
Assert.Throws<ObjectDisposedException>(() => { stream.WriteAsync(new byte[1], 0, 1); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SynchronousWritesThrowIfDisallowedByIHttpBodyControlFeature()
|
||||||
|
{
|
||||||
|
var allowSynchronousIO = false;
|
||||||
|
var mockBodyControl = new Mock<IHttpBodyControlFeature>();
|
||||||
|
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(() => allowSynchronousIO);
|
||||||
|
var mockFrameControl = new Mock<IFrameControl>();
|
||||||
|
mockFrameControl.Setup(m => m.WriteAsync(It.IsAny<ArraySegment<byte>>(), CancellationToken.None)).Returns(Task.CompletedTask);
|
||||||
|
|
||||||
|
var stream = new FrameResponseStream(mockBodyControl.Object, mockFrameControl.Object);
|
||||||
|
stream.StartAcceptingWrites();
|
||||||
|
|
||||||
|
// WriteAsync doesn't throw.
|
||||||
|
await stream.WriteAsync(new byte[1], 0, 1);
|
||||||
|
|
||||||
|
var ioEx = Assert.Throws<InvalidOperationException>(() => stream.Write(new byte[1], 0, 1));
|
||||||
|
Assert.Equal("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
|
||||||
|
|
||||||
|
allowSynchronousIO = true;
|
||||||
|
// If IHttpBodyControlFeature.AllowSynchronousIO is true, Write no longer throws.
|
||||||
|
stream.Write(new byte[1], 0, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
Assert.True(o1.ListenOptions[0].NoDelay);
|
Assert.True(o1.ListenOptions[0].NoDelay);
|
||||||
Assert.False(o1.ListenOptions[1].NoDelay);
|
Assert.False(o1.ListenOptions[1].NoDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AllowSynchronousIODefaultsToTrue()
|
||||||
|
{
|
||||||
|
var options = new KestrelServerOptions();
|
||||||
|
|
||||||
|
Assert.True(options.AllowSynchronousIO);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
|
@ -28,7 +29,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
|
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var mockBodyControl = new Mock<IHttpBodyControlFeature>();
|
||||||
|
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(true);
|
||||||
|
var stream = new FrameRequestStream(mockBodyControl.Object);
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("Hello");
|
input.Add("Hello");
|
||||||
|
|
@ -54,7 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
|
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderContentLength = "5" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("Hello");
|
input.Add("Hello");
|
||||||
|
|
@ -78,7 +81,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var mockBodyControl = new Mock<IHttpBodyControlFeature>();
|
||||||
|
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(true);
|
||||||
|
var stream = new FrameRequestStream(mockBodyControl.Object);
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("5\r\nHello\r\n");
|
input.Add("5\r\nHello\r\n");
|
||||||
|
|
@ -104,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("5\r\nHello\r\n");
|
input.Add("5\r\nHello\r\n");
|
||||||
|
|
@ -130,7 +135,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("5;\r\0");
|
input.Add("5;\r\0");
|
||||||
|
|
@ -155,7 +160,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("80000000\r\n");
|
input.Add("80000000\r\n");
|
||||||
|
|
@ -176,7 +181,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("012345678\r");
|
input.Add("012345678\r");
|
||||||
|
|
@ -199,7 +204,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame);
|
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var mockBodyControl = new Mock<IHttpBodyControlFeature>();
|
||||||
|
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(true);
|
||||||
|
var stream = new FrameRequestStream(mockBodyControl.Object);
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("Hello");
|
input.Add("Hello");
|
||||||
|
|
@ -224,7 +231,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame);
|
var body = MessageBody.For(httpVersion, new FrameRequestHeaders { HeaderConnection = "upgrade" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("Hello");
|
input.Add("Hello");
|
||||||
|
|
@ -249,7 +256,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame);
|
var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var mockBodyControl = new Mock<IHttpBodyControlFeature>();
|
||||||
|
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(true);
|
||||||
|
var stream = new FrameRequestStream(mockBodyControl.Object);
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("Hello");
|
input.Add("Hello");
|
||||||
|
|
@ -269,7 +278,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame);
|
var body = MessageBody.For(httpVersion, new FrameRequestHeaders(), input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("Hello");
|
input.Add("Hello");
|
||||||
|
|
@ -287,7 +296,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "8197" }, input.Frame);
|
var body = MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderContentLength = "8197" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
// Input needs to be greater than 4032 bytes to allocate a block not backed by a slab.
|
// Input needs to be greater than 4032 bytes to allocate a block not backed by a slab.
|
||||||
|
|
@ -479,13 +488,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderConnection = headerConnection }, input.Frame);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderConnection = headerConnection }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
input.Add("Hello");
|
input.Add("Hello");
|
||||||
|
|
||||||
var buffer = new byte[1024];
|
var buffer = new byte[1024];
|
||||||
Assert.Equal(5, stream.Read(buffer, 0, buffer.Length));
|
Assert.Equal(5, await stream.ReadAsync(buffer, 0, buffer.Length));
|
||||||
AssertASCII("Hello", new ArraySegment<byte>(buffer, 0, 5));
|
AssertASCII("Hello", new ArraySegment<byte>(buffer, 0, 5));
|
||||||
|
|
||||||
input.Fin();
|
input.Fin();
|
||||||
|
|
@ -500,7 +509,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
// Add some input and consume it to ensure PumpAsync is running
|
// Add some input and consume it to ensure PumpAsync is running
|
||||||
|
|
@ -523,7 +532,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
using (var input = new TestInput())
|
using (var input = new TestInput())
|
||||||
{
|
{
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
// Add some input and consume it to ensure PumpAsync is running
|
// Add some input and consume it to ensure PumpAsync is running
|
||||||
|
|
@ -642,7 +651,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
input.Frame.TraceIdentifier = "RequestId";
|
input.Frame.TraceIdentifier = "RequestId";
|
||||||
|
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
// Add some input and consume it to ensure PumpAsync is running
|
// Add some input and consume it to ensure PumpAsync is running
|
||||||
|
|
@ -672,7 +681,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
input.Frame.TraceIdentifier = "RequestId";
|
input.Frame.TraceIdentifier = "RequestId";
|
||||||
|
|
||||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
|
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderContentLength = "2" }, input.Frame);
|
||||||
var stream = new FrameRequestStream();
|
var stream = new FrameRequestStream(Mock.Of<IHttpBodyControlFeature>());
|
||||||
stream.StartAcceptingReads(body);
|
stream.StartAcceptingReads(body);
|
||||||
|
|
||||||
// Add some input and consume it to ensure PumpAsync is running
|
// Add some input and consume it to ensure PumpAsync is running
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
|
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -17,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task StreamsThrowAfterAbort()
|
public async Task StreamsThrowAfterAbort()
|
||||||
{
|
{
|
||||||
var streams = new Streams(Mock.Of<IFrameControl>());
|
var streams = new Streams(Mock.Of<IHttpBodyControlFeature>(), Mock.Of<IFrameControl>());
|
||||||
var (request, response) = streams.Start(new MockMessageBody());
|
var (request, response) = streams.Start(new MockMessageBody());
|
||||||
|
|
||||||
var ex = new Exception("My error");
|
var ex = new Exception("My error");
|
||||||
|
|
@ -31,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task StreamsThrowOnAbortAfterUpgrade()
|
public async Task StreamsThrowOnAbortAfterUpgrade()
|
||||||
{
|
{
|
||||||
var streams = new Streams(Mock.Of<IFrameControl>());
|
var streams = new Streams(Mock.Of<IHttpBodyControlFeature>(), Mock.Of<IFrameControl>());
|
||||||
var (request, response) = streams.Start(new MockMessageBody(upgradeable: true));
|
var (request, response) = streams.Start(new MockMessageBody(upgradeable: true));
|
||||||
|
|
||||||
var upgrade = streams.Upgrade();
|
var upgrade = streams.Upgrade();
|
||||||
|
|
@ -53,7 +55,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task StreamsThrowOnUpgradeAfterAbort()
|
public async Task StreamsThrowOnUpgradeAfterAbort()
|
||||||
{
|
{
|
||||||
var streams = new Streams(Mock.Of<IFrameControl>());
|
var streams = new Streams(Mock.Of<IHttpBodyControlFeature>(), Mock.Of<IFrameControl>());
|
||||||
|
|
||||||
var (request, response) = streams.Start(new MockMessageBody(upgradeable: true));
|
var (request, response) = streams.Start(new MockMessageBody(upgradeable: true));
|
||||||
var ex = new Exception("My error");
|
var ex = new Exception("My error");
|
||||||
|
|
|
||||||
|
|
@ -291,7 +291,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
await clientFinishedSendingRequestBody.Task.TimeoutAfter(TimeSpan.FromSeconds(120));
|
await clientFinishedSendingRequestBody.Task.TimeoutAfter(TimeSpan.FromSeconds(120));
|
||||||
|
|
||||||
// Verify client didn't send extra bytes
|
// Verify client didn't send extra bytes
|
||||||
if (context.Request.Body.ReadByte() != -1)
|
if (await context.Request.Body.ReadAsync(new byte[1], 0, 1) != 0)
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||||
await context.Response.WriteAsync("Client sent more bytes than expectedBody.Length");
|
await context.Response.WriteAsync("Client sent more bytes than expectedBody.Length");
|
||||||
|
|
|
||||||
|
|
@ -1480,6 +1480,130 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SynchronousReadsAllowedByDefault()
|
||||||
|
{
|
||||||
|
var firstRequest = true;
|
||||||
|
|
||||||
|
using (var server = new TestServer(async context =>
|
||||||
|
{
|
||||||
|
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
|
||||||
|
Assert.True(bodyControlFeature.AllowSynchronousIO);
|
||||||
|
|
||||||
|
var buffer = new byte[6];
|
||||||
|
var offset = 0;
|
||||||
|
|
||||||
|
// The request body is 5 bytes long. The 6th byte (buffer[5]) is only used for writing the response body.
|
||||||
|
buffer[5] = (byte)(firstRequest ? '1' : '2');
|
||||||
|
|
||||||
|
if (firstRequest)
|
||||||
|
{
|
||||||
|
while (offset < 5)
|
||||||
|
{
|
||||||
|
offset += context.Request.Body.Read(buffer, offset, 5 - offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
firstRequest = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bodyControlFeature.AllowSynchronousIO = false;
|
||||||
|
|
||||||
|
// Synchronous reads now throw.
|
||||||
|
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(new byte[1], 0, 1));
|
||||||
|
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
|
||||||
|
|
||||||
|
var ioEx2 = Assert.Throws<InvalidOperationException>(() => context.Request.Body.CopyTo(Stream.Null));
|
||||||
|
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx2.Message);
|
||||||
|
|
||||||
|
while (offset < 5)
|
||||||
|
{
|
||||||
|
offset += await context.Request.Body.ReadAsync(buffer, offset, 5 - offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(0, await context.Request.Body.ReadAsync(new byte[1], 0, 1));
|
||||||
|
Assert.Equal("Hello", Encoding.ASCII.GetString(buffer, 0, 5));
|
||||||
|
|
||||||
|
context.Response.ContentLength = 6;
|
||||||
|
await context.Response.Body.WriteAsync(buffer, 0, 6);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"POST / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"Content-Length: 5",
|
||||||
|
"",
|
||||||
|
"HelloPOST / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"Content-Length: 5",
|
||||||
|
"",
|
||||||
|
"Hello");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Content-Length: 6",
|
||||||
|
"",
|
||||||
|
"Hello1HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Content-Length: 6",
|
||||||
|
"",
|
||||||
|
"Hello2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SynchronousReadsCanBeDisallowedGlobally()
|
||||||
|
{
|
||||||
|
var testContext = new TestServiceContext
|
||||||
|
{
|
||||||
|
ServerOptions = { AllowSynchronousIO = false }
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var server = new TestServer(async context =>
|
||||||
|
{
|
||||||
|
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
|
||||||
|
Assert.False(bodyControlFeature.AllowSynchronousIO);
|
||||||
|
|
||||||
|
// Synchronous reads now throw.
|
||||||
|
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(new byte[1], 0, 1));
|
||||||
|
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
|
||||||
|
|
||||||
|
var ioEx2 = Assert.Throws<InvalidOperationException>(() => context.Request.Body.CopyTo(Stream.Null));
|
||||||
|
Assert.Equal("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.", ioEx2.Message);
|
||||||
|
|
||||||
|
var buffer = new byte[5];
|
||||||
|
var offset = 0;
|
||||||
|
while (offset < 5)
|
||||||
|
{
|
||||||
|
offset += await context.Request.Body.ReadAsync(buffer, offset, 5 - offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(0, await context.Request.Body.ReadAsync(new byte[1], 0, 1));
|
||||||
|
Assert.Equal("Hello", Encoding.ASCII.GetString(buffer));
|
||||||
|
}, testContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"POST / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"Content-Length: 5",
|
||||||
|
"",
|
||||||
|
"Hello");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Content-Length: 0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task TestRemoteIPAddress(string registerAddress, string requestAddress, string expectAddress)
|
private async Task TestRemoteIPAddress(string registerAddress, string requestAddress, string expectAddress)
|
||||||
{
|
{
|
||||||
var builder = new WebHostBuilder()
|
var builder = new WebHostBuilder()
|
||||||
|
|
|
||||||
|
|
@ -508,7 +508,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
public async Task ThrowsAndClosesConnectionWhenAppWritesMoreThanContentLengthWrite()
|
public async Task ThrowsAndClosesConnectionWhenAppWritesMoreThanContentLengthWrite()
|
||||||
{
|
{
|
||||||
var testLogger = new TestApplicationErrorLogger();
|
var testLogger = new TestApplicationErrorLogger();
|
||||||
var serviceContext = new TestServiceContext { Log = new TestKestrelTrace(testLogger) };
|
var serviceContext = new TestServiceContext
|
||||||
|
{
|
||||||
|
Log = new TestKestrelTrace(testLogger),
|
||||||
|
ServerOptions = { AllowSynchronousIO = true }
|
||||||
|
};
|
||||||
|
|
||||||
using (var server = new TestServer(httpContext =>
|
using (var server = new TestServer(httpContext =>
|
||||||
{
|
{
|
||||||
|
|
@ -536,7 +540,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var logMessage = Assert.Single(testLogger.Messages, message => message.LogLevel == LogLevel.Error);
|
var logMessage = Assert.Single(testLogger.Messages, message => message.LogLevel == LogLevel.Error);
|
||||||
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
|
|
@ -584,7 +587,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
public async Task InternalServerErrorAndConnectionClosedOnWriteWithMoreThanContentLengthAndResponseNotStarted()
|
public async Task InternalServerErrorAndConnectionClosedOnWriteWithMoreThanContentLengthAndResponseNotStarted()
|
||||||
{
|
{
|
||||||
var testLogger = new TestApplicationErrorLogger();
|
var testLogger = new TestApplicationErrorLogger();
|
||||||
var serviceContext = new TestServiceContext { Log = new TestKestrelTrace(testLogger) };
|
var serviceContext = new TestServiceContext
|
||||||
|
{
|
||||||
|
Log = new TestKestrelTrace(testLogger),
|
||||||
|
ServerOptions = { AllowSynchronousIO = true }
|
||||||
|
};
|
||||||
|
|
||||||
using (var server = new TestServer(httpContext =>
|
using (var server = new TestServer(httpContext =>
|
||||||
{
|
{
|
||||||
|
|
@ -966,6 +973,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
public async Task HeadResponseBodyNotWrittenWithSyncWrite()
|
public async Task HeadResponseBodyNotWrittenWithSyncWrite()
|
||||||
{
|
{
|
||||||
var flushed = new SemaphoreSlim(0, 1);
|
var flushed = new SemaphoreSlim(0, 1);
|
||||||
|
var serviceContext = new TestServiceContext { ServerOptions = { AllowSynchronousIO = true } };
|
||||||
|
|
||||||
using (var server = new TestServer(httpContext =>
|
using (var server = new TestServer(httpContext =>
|
||||||
{
|
{
|
||||||
|
|
@ -973,7 +981,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
httpContext.Response.Body.Write(Encoding.ASCII.GetBytes("hello, world"), 0, 12);
|
httpContext.Response.Body.Write(Encoding.ASCII.GetBytes("hello, world"), 0, 12);
|
||||||
flushed.Wait();
|
flushed.Wait();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}))
|
}, serviceContext))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
{
|
{
|
||||||
|
|
@ -1248,6 +1256,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task FirstWriteVerifiedAfterOnStarting()
|
public async Task FirstWriteVerifiedAfterOnStarting()
|
||||||
{
|
{
|
||||||
|
var serviceContext = new TestServiceContext { ServerOptions = { AllowSynchronousIO = true } };
|
||||||
|
|
||||||
using (var server = new TestServer(httpContext =>
|
using (var server = new TestServer(httpContext =>
|
||||||
{
|
{
|
||||||
httpContext.Response.OnStarting(() =>
|
httpContext.Response.OnStarting(() =>
|
||||||
|
|
@ -1263,7 +1273,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
// If OnStarting is not run before verifying writes, an error response will be sent.
|
// If OnStarting is not run before verifying writes, an error response will be sent.
|
||||||
httpContext.Response.Body.Write(response, 0, response.Length);
|
httpContext.Response.Body.Write(response, 0, response.Length);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}))
|
}, serviceContext))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
{
|
{
|
||||||
|
|
@ -1289,6 +1299,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SubsequentWriteVerifiedAfterOnStarting()
|
public async Task SubsequentWriteVerifiedAfterOnStarting()
|
||||||
{
|
{
|
||||||
|
var serviceContext = new TestServiceContext { ServerOptions = { AllowSynchronousIO = true } };
|
||||||
|
|
||||||
using (var server = new TestServer(httpContext =>
|
using (var server = new TestServer(httpContext =>
|
||||||
{
|
{
|
||||||
httpContext.Response.OnStarting(() =>
|
httpContext.Response.OnStarting(() =>
|
||||||
|
|
@ -1305,7 +1317,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
httpContext.Response.Body.Write(response, 0, response.Length / 2);
|
httpContext.Response.Body.Write(response, 0, response.Length / 2);
|
||||||
httpContext.Response.Body.Write(response, response.Length / 2, response.Length - response.Length / 2);
|
httpContext.Response.Body.Write(response, response.Length / 2, response.Length - response.Length / 2);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}))
|
}, serviceContext))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
{
|
{
|
||||||
|
|
@ -2335,6 +2347,96 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
Assert.Equal(2, callOrder.Pop());
|
Assert.Equal(2, callOrder.Pop());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SynchronousWritesAllowedByDefault()
|
||||||
|
{
|
||||||
|
var firstRequest = true;
|
||||||
|
|
||||||
|
using (var server = new TestServer(async context =>
|
||||||
|
{
|
||||||
|
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
|
||||||
|
Assert.True(bodyControlFeature.AllowSynchronousIO);
|
||||||
|
|
||||||
|
context.Response.ContentLength = 6;
|
||||||
|
|
||||||
|
if (firstRequest)
|
||||||
|
{
|
||||||
|
context.Response.Body.Write(Encoding.ASCII.GetBytes("Hello1"), 0, 6);
|
||||||
|
firstRequest = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bodyControlFeature.AllowSynchronousIO = false;
|
||||||
|
|
||||||
|
// Synchronous writes now throw.
|
||||||
|
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(Encoding.ASCII.GetBytes("What!?"), 0, 6));
|
||||||
|
Assert.Equal("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
|
||||||
|
|
||||||
|
await context.Response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello2"), 0, 6);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.SendEmptyGet();
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Content-Length: 6",
|
||||||
|
"",
|
||||||
|
"Hello1");
|
||||||
|
|
||||||
|
await connection.SendEmptyGet();
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Content-Length: 6",
|
||||||
|
"",
|
||||||
|
"Hello2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SynchronousWritesCanBeDisallowedGlobally()
|
||||||
|
{
|
||||||
|
var testContext = new TestServiceContext
|
||||||
|
{
|
||||||
|
ServerOptions = { AllowSynchronousIO = false }
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var server = new TestServer(context =>
|
||||||
|
{
|
||||||
|
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
|
||||||
|
Assert.False(bodyControlFeature.AllowSynchronousIO);
|
||||||
|
|
||||||
|
context.Response.ContentLength = 6;
|
||||||
|
|
||||||
|
// Synchronous writes now throw.
|
||||||
|
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(Encoding.ASCII.GetBytes("What!?"), 0, 6));
|
||||||
|
Assert.Equal("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.", ioEx.Message);
|
||||||
|
|
||||||
|
return context.Response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello!"), 0, 6);
|
||||||
|
}, testContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Content-Length: 6",
|
||||||
|
"",
|
||||||
|
"Hello!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static TheoryData<string, StringValues, string> NullHeaderData
|
public static TheoryData<string, StringValues, string> NullHeaderData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
var feature = context.Features.Get<IHttpUpgradeFeature>();
|
var feature = context.Features.Get<IHttpUpgradeFeature>();
|
||||||
var stream = await feature.UpgradeAsync();
|
var stream = await feature.UpgradeAsync();
|
||||||
|
|
||||||
var ex = Assert.Throws<InvalidOperationException>(() => context.Response.Body.WriteByte((byte)' '));
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.Body.WriteAsync(new byte[1], 0, 1));
|
||||||
Assert.Equal(CoreStrings.ResponseStreamWasUpgraded, ex.Message);
|
Assert.Equal(CoreStrings.ResponseStreamWasUpgraded, ex.Message);
|
||||||
|
|
||||||
using (var writer = new StreamWriter(stream))
|
using (var writer = new StreamWriter(stream))
|
||||||
{
|
{
|
||||||
writer.WriteLine("New protocol data");
|
await writer.WriteLineAsync("New protocol data");
|
||||||
|
await writer.FlushAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
upgrade.TrySetResult(true);
|
upgrade.TrySetResult(true);
|
||||||
|
|
@ -82,6 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
var line = await reader.ReadLineAsync();
|
var line = await reader.ReadLineAsync();
|
||||||
Assert.Equal(send, line);
|
Assert.Equal(send, line);
|
||||||
await writer.WriteLineAsync(recv);
|
await writer.WriteLineAsync(recv);
|
||||||
|
await writer.FlushAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
upgrade.TrySetResult(true);
|
upgrade.TrySetResult(true);
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,14 @@ namespace CodeGenerator
|
||||||
typeof(IHttpRequestIdentifierFeature),
|
typeof(IHttpRequestIdentifierFeature),
|
||||||
typeof(IServiceProvidersFeature),
|
typeof(IServiceProvidersFeature),
|
||||||
typeof(IHttpRequestLifetimeFeature),
|
typeof(IHttpRequestLifetimeFeature),
|
||||||
typeof(IHttpConnectionFeature)
|
typeof(IHttpConnectionFeature),
|
||||||
};
|
};
|
||||||
|
|
||||||
var commonFeatures = new[]
|
var commonFeatures = new[]
|
||||||
{
|
{
|
||||||
typeof(IHttpAuthenticationFeature),
|
typeof(IHttpAuthenticationFeature),
|
||||||
typeof(IQueryFeature),
|
typeof(IQueryFeature),
|
||||||
typeof(IFormFeature)
|
typeof(IFormFeature),
|
||||||
};
|
};
|
||||||
|
|
||||||
var sometimesFeatures = new[]
|
var sometimesFeatures = new[]
|
||||||
|
|
@ -48,6 +48,7 @@ namespace CodeGenerator
|
||||||
typeof(ISessionFeature),
|
typeof(ISessionFeature),
|
||||||
typeof(IHttpMaxRequestBodySizeFeature),
|
typeof(IHttpMaxRequestBodySizeFeature),
|
||||||
typeof(IHttpMinRequestBodyDataRateFeature),
|
typeof(IHttpMinRequestBodyDataRateFeature),
|
||||||
|
typeof(IHttpBodyControlFeature),
|
||||||
};
|
};
|
||||||
|
|
||||||
var rareFeatures = new[]
|
var rareFeatures = new[]
|
||||||
|
|
@ -69,6 +70,7 @@ namespace CodeGenerator
|
||||||
typeof(IHttpConnectionFeature),
|
typeof(IHttpConnectionFeature),
|
||||||
typeof(IHttpMaxRequestBodySizeFeature),
|
typeof(IHttpMaxRequestBodySizeFeature),
|
||||||
typeof(IHttpMinRequestBodyDataRateFeature),
|
typeof(IHttpMinRequestBodyDataRateFeature),
|
||||||
|
typeof(IHttpBodyControlFeature),
|
||||||
};
|
};
|
||||||
|
|
||||||
return $@"// Copyright (c) .NET Foundation. All rights reserved.
|
return $@"// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue