Disable AllowSynchronousIO by default in all servers #4774 (#5120)

* Disable AllowSynchronousIO by default in all servers
This commit is contained in:
Chris Ross 2019-02-15 16:05:49 -08:00 committed by GitHub
parent 7daa0e0145
commit 93a24b03bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 294 additions and 139 deletions

View File

@ -21,13 +21,17 @@ namespace Microsoft.AspNetCore.TestHost
public override bool CanRead => _inner.CanRead; public override bool CanRead => _inner.CanRead;
public override bool CanSeek => _inner.CanSeek; public override bool CanSeek => false;
public override bool CanWrite => _inner.CanWrite; public override bool CanWrite => _inner.CanWrite;
public override long Length => _inner.Length; public override long Length => throw new NotSupportedException("The stream is not seekable.");
public override long Position { get => _inner.Position; set => _inner.Position = value; } public override long Position
{
get => throw new NotSupportedException("The stream is not seekable.");
set => throw new NotSupportedException("The stream is not seekable.");
}
public override void Flush() public override void Flush()
{ {
@ -72,12 +76,12 @@ namespace Microsoft.AspNetCore.TestHost
public override long Seek(long offset, SeekOrigin origin) public override long Seek(long offset, SeekOrigin origin)
{ {
return _inner.Seek(offset, origin); throw new NotSupportedException("The stream is not seekable.");
} }
public override void SetLength(long value) public override void SetLength(long value)
{ {
_inner.SetLength(value); throw new NotSupportedException("The stream is not seekable.");
} }
public override void Write(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count)

View File

@ -81,9 +81,9 @@ namespace Microsoft.AspNetCore.TestHost
/// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/> /// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/>
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Defaults to true. /// Defaults to false.
/// </remarks> /// </remarks>
public bool AllowSynchronousIO { get; set; } = true; public bool AllowSynchronousIO { get; set; } = false;
private IHttpApplication<Context> Application private IHttpApplication<Context> Application
{ {

View File

@ -254,6 +254,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var dataContractSerializer = GetCachedSerializer(wrappingType); var dataContractSerializer = GetCachedSerializer(wrappingType);
// Opt into sync IO support until we can work out an alternative https://github.com/aspnet/AspNetCore/issues/6397
var syncIOFeature = context.HttpContext.Features.Get<Http.Features.IHttpBodyControlFeature>();
if (syncIOFeature != null)
{
syncIOFeature.AllowSynchronousIO = true;
}
using (var textWriter = context.WriterFactory(context.HttpContext.Response.Body, writerSettings.Encoding)) using (var textWriter = context.WriterFactory(context.HttpContext.Response.Body, writerSettings.Encoding))
{ {
using (var xmlWriter = CreateXmlWriter(context, textWriter, writerSettings)) using (var xmlWriter = CreateXmlWriter(context, textWriter, writerSettings))

View File

@ -230,6 +230,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var xmlSerializer = GetCachedSerializer(wrappingType); var xmlSerializer = GetCachedSerializer(wrappingType);
// Opt into sync IO support until we can work out an alternative https://github.com/aspnet/AspNetCore/issues/6397
var syncIOFeature = context.HttpContext.Features.Get<Http.Features.IHttpBodyControlFeature>();
if (syncIOFeature != null)
{
syncIOFeature.AllowSynchronousIO = true;
}
using (var textWriter = context.WriterFactory(context.HttpContext.Response.Body, writerSettings.Encoding)) using (var textWriter = context.WriterFactory(context.HttpContext.Response.Body, writerSettings.Encoding))
{ {
using (var xmlWriter = CreateXmlWriter(context, textWriter, writerSettings)) using (var xmlWriter = CreateXmlWriter(context, textWriter, writerSettings))

View File

@ -105,6 +105,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
response.StatusCode = result.StatusCode.Value; response.StatusCode = result.StatusCode.Value;
} }
// Opt into sync IO support until we can work out an alternative https://github.com/aspnet/AspNetCore/issues/6397
var syncIOFeature = context.HttpContext.Features.Get<Http.Features.IHttpBodyControlFeature>();
if (syncIOFeature != null)
{
syncIOFeature.AllowSynchronousIO = true;
}
using (var writer = new HttpResponseStreamWriter(response.Body, resolvedContentTypeEncoding)) using (var writer = new HttpResponseStreamWriter(response.Body, resolvedContentTypeEncoding))
{ {
var viewContext = new ViewContext( var viewContext = new ViewContext(

View File

@ -3,6 +3,8 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -48,6 +50,7 @@ namespace BasicWebSite
{ {
private readonly Stream _innerStream; private readonly Stream _innerStream;
private readonly IHttpMaxRequestBodySizeFeature _maxRequestBodySizeFeature; private readonly IHttpMaxRequestBodySizeFeature _maxRequestBodySizeFeature;
private long _totalRead;
public RequestBodySizeCheckingStream( public RequestBodySizeCheckingStream(
Stream innerStream, Stream innerStream,
@ -78,12 +81,39 @@ namespace BasicWebSite
public override int Read(byte[] buffer, int offset, int count) public override int Read(byte[] buffer, int offset, int count)
{ {
if (_maxRequestBodySizeFeature.MaxRequestBodySize != null if (_maxRequestBodySizeFeature.MaxRequestBodySize != null
&& _innerStream.Length > _maxRequestBodySizeFeature.MaxRequestBodySize) && _innerStream.CanSeek && _innerStream.Length > _maxRequestBodySizeFeature.MaxRequestBodySize)
{ {
throw new InvalidOperationException("Request content size is greater than the limit size"); throw new InvalidOperationException("Request content size is greater than the limit size");
} }
return _innerStream.Read(buffer, offset, count); var read = _innerStream.Read(buffer, offset, count);
_totalRead += read;
if (_maxRequestBodySizeFeature.MaxRequestBodySize != null
&& _totalRead > _maxRequestBodySizeFeature.MaxRequestBodySize)
{
throw new InvalidOperationException("Request content size is greater than the limit size");
}
return read;
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (_maxRequestBodySizeFeature.MaxRequestBodySize != null
&& _innerStream.CanSeek && _innerStream.Length > _maxRequestBodySizeFeature.MaxRequestBodySize)
{
throw new InvalidOperationException("Request content size is greater than the limit size");
}
var read = await _innerStream.ReadAsync(buffer, offset, count, cancellationToken);
_totalRead += read;
if (_maxRequestBodySizeFeature.MaxRequestBodySize != null
&& _totalRead > _maxRequestBodySizeFeature.MaxRequestBodySize)
{
throw new InvalidOperationException("Request content size is greater than the limit size");
}
return read;
} }
public override long Seek(long offset, SeekOrigin origin) public override long Seek(long offset, SeekOrigin origin)

View File

@ -20,13 +20,13 @@ namespace FormatterWebSite
SupportedEncodings.Add(Encoding.Unicode); SupportedEncodings.Add(Encoding.Unicode);
} }
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding) public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding)
{ {
var request = context.HttpContext.Request; var request = context.HttpContext.Request;
using (var reader = new StreamReader(request.Body, effectiveEncoding)) using (var reader = new StreamReader(request.Body, effectiveEncoding))
{ {
var stringContent = reader.ReadToEnd(); var stringContent = await reader.ReadToEndAsync();
return InputFormatterResult.SuccessAsync(stringContent); return await InputFormatterResult.SuccessAsync(stringContent);
} }
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -136,9 +136,9 @@ namespace Microsoft.AspNetCore.Server.HttpSys
/// <summary> /// <summary>
/// Gets or sets a value that controls whether synchronous IO is allowed for the HttpContext.Request.Body and HttpContext.Response.Body. /// Gets or sets a value that controls whether synchronous IO is allowed for the HttpContext.Request.Body and HttpContext.Response.Body.
/// The default is `true`. /// The default is `false`.
/// </summary> /// </summary>
public bool AllowSynchronousIO { get; set; } = true; public bool AllowSynchronousIO { get; set; } = false;
/// <summary> /// <summary>
/// Gets or sets a value that controls how http.sys reacts when rejecting requests due to throttling conditions - like when the request /// Gets or sets a value that controls how http.sys reacts when rejecting requests due to throttling conditions - like when the request

View File

@ -51,14 +51,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys
[ConditionalFact] [ConditionalFact]
public async Task Https_EchoHelloWorld_Success() public async Task Https_EchoHelloWorld_Success()
{ {
using (Utilities.CreateDynamicHttpsServer(out var address, httpContext => using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext =>
{ {
string input = new StreamReader(httpContext.Request.Body).ReadToEnd(); var input = await new StreamReader(httpContext.Request.Body).ReadToEndAsync();
Assert.Equal("Hello World", input); Assert.Equal("Hello World", input);
byte[] body = Encoding.UTF8.GetBytes("Hello World"); var body = Encoding.UTF8.GetBytes("Hello World");
httpContext.Response.ContentLength = body.Length; httpContext.Response.ContentLength = body.Length;
httpContext.Response.Body.Write(body, 0, body.Length); await httpContext.Response.Body.WriteAsync(body, 0, body.Length);
return Task.FromResult(0);
})) }))
{ {
string response = await SendRequestAsync(address, "Hello World"); string response = await SendRequestAsync(address, "Hello World");

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -17,26 +17,26 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
public class RequestBodyTests public class RequestBodyTests
{ {
[ConditionalFact] [ConditionalFact]
public async Task RequestBody_SyncReadEnabledByDefault_ThrowsWhenDisabled() public async Task RequestBody_SyncReadDisabledByDefault_WorksWhenEnabled()
{ {
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
Task<string> responseTask = SendRequestAsync(address, "Hello World"); Task<string> responseTask = SendRequestAsync(address, "Hello World");
Assert.True(server.Options.AllowSynchronousIO); Assert.False(server.Options.AllowSynchronousIO);
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask); var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
byte[] input = new byte[100]; byte[] input = new byte[100];
Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(input, 0, input.Length));
context.AllowSynchronousIO = true;
Assert.True(context.AllowSynchronousIO); Assert.True(context.AllowSynchronousIO);
var read = context.Request.Body.Read(input, 0, input.Length); var read = context.Request.Body.Read(input, 0, input.Length);
context.Response.ContentLength = read; context.Response.ContentLength = read;
context.Response.Body.Write(input, 0, read); context.Response.Body.Write(input, 0, read);
context.AllowSynchronousIO = false;
Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(input, 0, input.Length));
string response = await responseTask; string response = await responseTask;
Assert.Equal("Hello World", response); Assert.Equal("Hello World", response);
} }

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
public class ResponseBodyTests public class ResponseBodyTests
{ {
[ConditionalFact] [ConditionalFact]
public async Task ResponseBody_SyncWriteEnabledByDefault_ThrowsWhenDisabled() public async Task ResponseBody_SyncWriteDisabledByDefault_WorksWhenEnabled()
{ {
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
@ -26,19 +26,17 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask); var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
Assert.True(context.AllowSynchronousIO); Assert.False(context.AllowSynchronousIO);
context.Response.Body.Flush();
context.Response.Body.Write(new byte[10], 0, 10);
context.Response.Body.Flush();
context.AllowSynchronousIO = false;
Assert.Throws<InvalidOperationException>(() => context.Response.Body.Flush()); Assert.Throws<InvalidOperationException>(() => context.Response.Body.Flush());
Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(new byte[10], 0, 10)); Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(new byte[10], 0, 10));
Assert.Throws<InvalidOperationException>(() => context.Response.Body.Flush()); Assert.Throws<InvalidOperationException>(() => context.Response.Body.Flush());
await context.Response.Body.WriteAsync(new byte[10], 0, 10); context.AllowSynchronousIO = true;
context.Response.Body.Flush();
context.Response.Body.Write(new byte[10], 0, 10);
context.Response.Body.Flush();
context.Dispose(); context.Dispose();
var response = await responseTask; var response = await responseTask;
@ -47,7 +45,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
IEnumerable<string> ignored; IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length"); Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked"); Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.Equal(new byte[20], await response.Content.ReadAsByteArrayAsync()); Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync());
} }
} }
@ -477,4 +475,4 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
} }
} }
} }
} }

View File

@ -320,7 +320,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator();
bool IHttpBodyControlFeature.AllowSynchronousIO { get; set; } = true; bool IHttpBodyControlFeature.AllowSynchronousIO { get; set; }
void IHttpBufferingFeature.DisableRequestBuffering() void IHttpBufferingFeature.DisableRequestBuffering()
{ {

View File

@ -11,9 +11,9 @@ namespace Microsoft.AspNetCore.Builder
/// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/> /// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/>
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Defaults to true. /// Defaults to false.
/// </remarks> /// </remarks>
public bool AllowSynchronousIO { get; set; } = true; public bool AllowSynchronousIO { get; set; } = false;
/// <summary> /// <summary>
/// If true the server should set HttpContext.User. If false the server will only provide an /// If true the server should set HttpContext.User. If false the server will only provide an

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
@ -11,44 +10,22 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{ {
[SkipIfHostableWebCoreNotAvailable] [SkipIfHostableWebCoreNotAvailable]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
public class HttpBodyControlFeatureTests : StrictTestServerTests public class ConnectionIdFeatureTests : StrictTestServerTests
{ {
[ConditionalFact] [ConditionalFact]
public async Task ThrowsOnSyncReadOrWrite() public async Task ProvidesConnectionId()
{ {
Exception writeException = null; string connectionId = null;
Exception readException = null; using (var testServer = await TestServer.Create(ctx => {
using (var testServer = await TestServer.Create( var connectionIdFeature = ctx.Features.Get<IHttpConnectionFeature>();
ctx => { connectionId = connectionIdFeature.ConnectionId;
var bodyControl = ctx.Features.Get<IHttpBodyControlFeature>(); return Task.CompletedTask;
bodyControl.AllowSynchronousIO = false; }, LoggerFactory))
try
{
ctx.Response.Body.Write(new byte[10]);
}
catch (Exception ex)
{
writeException = ex;
}
try
{
ctx.Request.Body.Read(new byte[10]);
}
catch (Exception ex)
{
readException = ex;
}
return Task.CompletedTask;
}, LoggerFactory))
{ {
await testServer.HttpClient.GetStringAsync("/"); await testServer.HttpClient.GetStringAsync("/");
} }
Assert.IsType<InvalidOperationException>(readException); Assert.NotNull(connectionId);
Assert.IsType<InvalidOperationException>(writeException);
} }
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
@ -10,22 +11,44 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{ {
[SkipIfHostableWebCoreNotAvailable] [SkipIfHostableWebCoreNotAvailable]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
public class ConnectionIdFeatureTests : StrictTestServerTests public class HttpBodyControlFeatureTests : StrictTestServerTests
{ {
[ConditionalFact] [ConditionalFact]
public async Task ProvidesConnectionId() public async Task ThrowsOnSyncReadOrWrite()
{ {
string connectionId = null; Exception writeException = null;
using (var testServer = await TestServer.Create(ctx => { Exception readException = null;
var connectionIdFeature = ctx.Features.Get<IHttpConnectionFeature>(); using (var testServer = await TestServer.Create(
connectionId = connectionIdFeature.ConnectionId; ctx => {
var bodyControl = ctx.Features.Get<IHttpBodyControlFeature>();
Assert.False(bodyControl.AllowSynchronousIO);
try
{
ctx.Response.Body.Write(new byte[10]);
}
catch (Exception ex)
{
writeException = ex;
}
try
{
ctx.Request.Body.Read(new byte[10]);
}
catch (Exception ex)
{
readException = ex;
}
return Task.CompletedTask; return Task.CompletedTask;
}, LoggerFactory)) }, LoggerFactory))
{ {
await testServer.HttpClient.GetStringAsync("/"); await testServer.HttpClient.GetStringAsync("/");
} }
Assert.NotNull(connectionId); Assert.IsType<InvalidOperationException>(readException);
Assert.IsType<InvalidOperationException>(writeException);
} }
} }
} }

View File

@ -50,9 +50,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/> /// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/>
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Defaults to true. /// Defaults to false.
/// </remarks> /// </remarks>
public bool AllowSynchronousIO { get; set; } = true; public bool AllowSynchronousIO { get; set; } = false;
/// <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.

View File

@ -23,11 +23,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
} }
[Fact] [Fact]
public void AllowSynchronousIODefaultsToTrue() public void AllowSynchronousIODefaultsToFalse()
{ {
var options = new KestrelServerOptions(); var options = new KestrelServerOptions();
Assert.True(options.AllowSynchronousIO); Assert.False(options.AllowSynchronousIO);
} }
[Fact] [Fact]
@ -65,4 +65,4 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.False(options.ListenOptions[3].NoDelay); Assert.False(options.ListenOptions[3].NoDelay);
} }
} }
} }

View File

@ -1077,45 +1077,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
} }
[Fact] [Fact]
public async Task SynchronousReadsAllowedByDefault() public async Task SynchronousReadsDisallowedByDefault()
{ {
var firstRequest = true;
using (var server = new TestServer(async context => using (var server = new TestServer(async context =>
{ {
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>(); var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.True(bodyControlFeature.AllowSynchronousIO); Assert.False(bodyControlFeature.AllowSynchronousIO);
var buffer = new byte[6]; var buffer = new byte[6];
var offset = 0; var offset = 0;
// The request body is 5 bytes long. The 6th byte (buffer[5]) is only used for writing the response body. // 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'); buffer[5] = (byte)'1';
if (firstRequest) // Synchronous reads throw.
var ioEx = Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(new byte[1], 0, 1));
Assert.Equal(CoreStrings.SynchronousReadsDisallowed, ioEx.Message);
var ioEx2 = Assert.Throws<InvalidOperationException>(() => context.Request.Body.CopyTo(Stream.Null));
Assert.Equal(CoreStrings.SynchronousReadsDisallowed, ioEx2.Message);
while (offset < 5)
{ {
while (offset < 5) offset += await context.Request.Body.ReadAsync(buffer, offset, 5 - offset);
{
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(CoreStrings.SynchronousReadsDisallowed, ioEx.Message);
var ioEx2 = Assert.Throws<InvalidOperationException>(() => context.Request.Body.CopyTo(Stream.Null));
Assert.Equal(CoreStrings.SynchronousReadsDisallowed, 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(0, await context.Request.Body.ReadAsync(new byte[1], 0, 1));
@ -1132,7 +1116,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
"Host:", "Host:",
"Content-Length: 5", "Content-Length: 5",
"", "",
"HelloPOST / HTTP/1.1", "Hello");
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6",
"",
"Hello1");
}
}
}
[Fact]
public async Task SynchronousReadsAllowedByOptIn()
{
using (var server = new TestServer(async context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.False(bodyControlFeature.AllowSynchronousIO);
var buffer = new byte[5];
var offset = 0;
bodyControlFeature.AllowSynchronousIO = true;
while (offset < 5)
{
offset += context.Request.Body.Read(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 = 5;
await context.Response.Body.WriteAsync(buffer, 0, 5);
}, new TestServiceContext(LoggerFactory)))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"POST / HTTP/1.1",
"Host:", "Host:",
"Content-Length: 5", "Content-Length: 5",
"", "",
@ -1140,13 +1163,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
await connection.Receive( await connection.Receive(
"HTTP/1.1 200 OK", "HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}", $"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6", "Content-Length: 5",
"", "",
"Hello1HTTP/1.1 200 OK", "Hello");
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6",
"",
"Hello2");
} }
await server.StopAsync(); await server.StopAsync();
} }
@ -1197,6 +1216,48 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
} }
} }
[Fact]
public async Task SynchronousReadsCanBeAllowedGlobally()
{
var testContext = new TestServiceContext(LoggerFactory)
{
ServerOptions = { AllowSynchronousIO = true }
};
using (var server = new TestServer(async context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.True(bodyControlFeature.AllowSynchronousIO);
int offset = 0;
var buffer = new byte[5];
while (offset < 5)
{
offset += context.Request.Body.Read(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));
}, 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",
"",
"");
}
}
}
public static TheoryData<string, string> HostHeaderData => HttpParsingData.HostHeaderData; public static TheoryData<string, string> HostHeaderData => HttpParsingData.HostHeaderData;
} }
} }

View File

@ -3042,34 +3042,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Assert.Equal(2, callOrder.Pop()); Assert.Equal(2, callOrder.Pop());
} }
[Fact] [Fact]
public async Task SynchronousWritesAllowedByDefault() public async Task SynchronousWritesDisallowedByDefault()
{ {
var firstRequest = true;
using (var server = new TestServer(async context => using (var server = new TestServer(async context =>
{ {
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>(); var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.True(bodyControlFeature.AllowSynchronousIO); Assert.False(bodyControlFeature.AllowSynchronousIO);
context.Response.ContentLength = 6; context.Response.ContentLength = 6;
if (firstRequest) // Synchronous writes now throw.
{ var ioEx = Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(Encoding.ASCII.GetBytes("What!?"), 0, 6));
context.Response.Body.Write(Encoding.ASCII.GetBytes("Hello1"), 0, 6); Assert.Equal(CoreStrings.SynchronousWritesDisallowed, ioEx.Message);
firstRequest = false; await context.Response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello1"), 0, 6);
}
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(CoreStrings.SynchronousWritesDisallowed, ioEx.Message);
await context.Response.BodyPipe.WriteAsync(new Memory<byte>(Encoding.ASCII.GetBytes("Hello2"), 0, 6));
}
}, new TestServiceContext(LoggerFactory))) }, new TestServiceContext(LoggerFactory)))
{ {
using (var connection = server.CreateConnection()) using (var connection = server.CreateConnection())
@ -3081,14 +3068,67 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
"Content-Length: 6", "Content-Length: 6",
"", "",
"Hello1"); "Hello1");
}
}
}
[Fact]
public async Task SynchronousWritesAllowedByOptIn()
{
using (var server = new TestServer(context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.False(bodyControlFeature.AllowSynchronousIO);
bodyControlFeature.AllowSynchronousIO = true;
context.Response.ContentLength = 6;
context.Response.Body.Write(Encoding.ASCII.GetBytes("Hello1"), 0, 6);
return Task.CompletedTask;
}, new TestServiceContext(LoggerFactory)))
{
using (var connection = server.CreateConnection())
{
await connection.SendEmptyGet(); await connection.SendEmptyGet();
await connection.Receive( await connection.Receive(
"HTTP/1.1 200 OK", "HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}", $"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6", "Content-Length: 6",
"", "",
"Hello2"); "Hello1");
}
}
}
[Fact]
public async Task SynchronousWritesCanBeAllowedGlobally()
{
var testContext = new TestServiceContext(LoggerFactory)
{
ServerOptions = { AllowSynchronousIO = true }
};
using (var server = new TestServer(context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.True(bodyControlFeature.AllowSynchronousIO);
context.Response.ContentLength = 6;
context.Response.Body.Write(Encoding.ASCII.GetBytes("Hello!"), 0, 6);
return Task.CompletedTask;
}, 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!");
} }
await server.StopAsync(); await server.StopAsync();
} }

View File

@ -33,6 +33,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{ {
await writer.WriteLineAsync("New protocol data"); await writer.WriteLineAsync("New protocol data");
await writer.FlushAsync(); await writer.FlushAsync();
await writer.DisposeAsync();
} }
upgrade.TrySetResult(true); upgrade.TrySetResult(true);
@ -79,6 +80,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Assert.Equal(send, line); Assert.Equal(send, line);
await writer.WriteLineAsync(recv); await writer.WriteLineAsync(recv);
await writer.FlushAsync(); await writer.FlushAsync();
await writer.DisposeAsync();
} }
upgrade.TrySetResult(true); upgrade.TrySetResult(true);