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 CanSeek => _inner.CanSeek;
public override bool CanSeek => false;
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()
{
@ -72,12 +76,12 @@ namespace Microsoft.AspNetCore.TestHost
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)
{
_inner.SetLength(value);
throw new NotSupportedException("The stream is not seekable.");
}
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"/>
/// </summary>
/// <remarks>
/// Defaults to true.
/// Defaults to false.
/// </remarks>
public bool AllowSynchronousIO { get; set; } = true;
public bool AllowSynchronousIO { get; set; } = false;
private IHttpApplication<Context> Application
{

View File

@ -254,6 +254,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
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 xmlWriter = CreateXmlWriter(context, textWriter, writerSettings))

View File

@ -230,6 +230,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
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 xmlWriter = CreateXmlWriter(context, textWriter, writerSettings))

View File

@ -105,6 +105,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
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))
{
var viewContext = new ViewContext(

View File

@ -3,6 +3,8 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
@ -48,6 +50,7 @@ namespace BasicWebSite
{
private readonly Stream _innerStream;
private readonly IHttpMaxRequestBodySizeFeature _maxRequestBodySizeFeature;
private long _totalRead;
public RequestBodySizeCheckingStream(
Stream innerStream,
@ -78,12 +81,39 @@ namespace BasicWebSite
public override int Read(byte[] buffer, int offset, int count)
{
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");
}
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)

View File

@ -20,13 +20,13 @@ namespace FormatterWebSite
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;
using (var reader = new StreamReader(request.Body, effectiveEncoding))
{
var stringContent = reader.ReadToEnd();
return InputFormatterResult.SuccessAsync(stringContent);
var stringContent = await reader.ReadToEndAsync();
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.
using System;
@ -136,9 +136,9 @@ namespace Microsoft.AspNetCore.Server.HttpSys
/// <summary>
/// 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>
public bool AllowSynchronousIO { get; set; } = true;
public bool AllowSynchronousIO { get; set; } = false;
/// <summary>
/// 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]
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);
byte[] body = Encoding.UTF8.GetBytes("Hello World");
var body = Encoding.UTF8.GetBytes("Hello World");
httpContext.Response.ContentLength = body.Length;
httpContext.Response.Body.Write(body, 0, body.Length);
return Task.FromResult(0);
await httpContext.Response.Body.WriteAsync(body, 0, body.Length);
}))
{
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.
using System;
@ -17,26 +17,26 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
public class RequestBodyTests
{
[ConditionalFact]
public async Task RequestBody_SyncReadEnabledByDefault_ThrowsWhenDisabled()
public async Task RequestBody_SyncReadDisabledByDefault_WorksWhenEnabled()
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
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);
byte[] input = new byte[100];
Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(input, 0, input.Length));
context.AllowSynchronousIO = true;
Assert.True(context.AllowSynchronousIO);
var read = context.Request.Body.Read(input, 0, input.Length);
context.Response.ContentLength = 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;
Assert.Equal("Hello World", response);
}

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
public class ResponseBodyTests
{
[ConditionalFact]
public async Task ResponseBody_SyncWriteEnabledByDefault_ThrowsWhenDisabled()
public async Task ResponseBody_SyncWriteDisabledByDefault_WorksWhenEnabled()
{
string 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);
Assert.True(context.AllowSynchronousIO);
context.Response.Body.Flush();
context.Response.Body.Write(new byte[10], 0, 10);
context.Response.Body.Flush();
context.AllowSynchronousIO = false;
Assert.False(context.AllowSynchronousIO);
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.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();
var response = await responseTask;
@ -47,7 +45,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
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();
bool IHttpBodyControlFeature.AllowSynchronousIO { get; set; } = true;
bool IHttpBodyControlFeature.AllowSynchronousIO { get; set; }
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"/>
/// </summary>
/// <remarks>
/// Defaults to true.
/// Defaults to false.
/// </remarks>
public bool AllowSynchronousIO { get; set; } = true;
public bool AllowSynchronousIO { get; set; } = false;
/// <summary>
/// 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.
// 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 Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing.xunit;
@ -11,44 +10,22 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
[SkipIfHostableWebCoreNotAvailable]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
public class HttpBodyControlFeatureTests : StrictTestServerTests
public class ConnectionIdFeatureTests : StrictTestServerTests
{
[ConditionalFact]
public async Task ThrowsOnSyncReadOrWrite()
public async Task ProvidesConnectionId()
{
Exception writeException = null;
Exception readException = null;
using (var testServer = await TestServer.Create(
ctx => {
var bodyControl = ctx.Features.Get<IHttpBodyControlFeature>();
bodyControl.AllowSynchronousIO = false;
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))
string connectionId = null;
using (var testServer = await TestServer.Create(ctx => {
var connectionIdFeature = ctx.Features.Get<IHttpConnectionFeature>();
connectionId = connectionIdFeature.ConnectionId;
return Task.CompletedTask;
}, LoggerFactory))
{
await testServer.HttpClient.GetStringAsync("/");
}
Assert.IsType<InvalidOperationException>(readException);
Assert.IsType<InvalidOperationException>(writeException);
Assert.NotNull(connectionId);
}
}
}

View File

@ -1,6 +1,7 @@
// 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.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing.xunit;
@ -10,22 +11,44 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
[SkipIfHostableWebCoreNotAvailable]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
public class ConnectionIdFeatureTests : StrictTestServerTests
public class HttpBodyControlFeatureTests : StrictTestServerTests
{
[ConditionalFact]
public async Task ProvidesConnectionId()
public async Task ThrowsOnSyncReadOrWrite()
{
string connectionId = null;
using (var testServer = await TestServer.Create(ctx => {
var connectionIdFeature = ctx.Features.Get<IHttpConnectionFeature>();
connectionId = connectionIdFeature.ConnectionId;
Exception writeException = null;
Exception readException = null;
using (var testServer = await TestServer.Create(
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;
}, LoggerFactory))
{
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"/>
/// </summary>
/// <remarks>
/// Defaults to true.
/// Defaults to false.
/// </remarks>
public bool AllowSynchronousIO { get; set; } = true;
public bool AllowSynchronousIO { get; set; } = false;
/// <summary>
/// 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]
public void AllowSynchronousIODefaultsToTrue()
public void AllowSynchronousIODefaultsToFalse()
{
var options = new KestrelServerOptions();
Assert.True(options.AllowSynchronousIO);
Assert.False(options.AllowSynchronousIO);
}
[Fact]
@ -65,4 +65,4 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.False(options.ListenOptions[3].NoDelay);
}
}
}
}

View File

@ -1077,45 +1077,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
}
[Fact]
public async Task SynchronousReadsAllowedByDefault()
public async Task SynchronousReadsDisallowedByDefault()
{
var firstRequest = true;
using (var server = new TestServer(async context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.True(bodyControlFeature.AllowSynchronousIO);
Assert.False(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');
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 += 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);
}
offset += await context.Request.Body.ReadAsync(buffer, offset, 5 - offset);
}
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:",
"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:",
"Content-Length: 5",
"",
@ -1140,13 +1163,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6",
"Content-Length: 5",
"",
"Hello1HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 6",
"",
"Hello2");
"Hello");
}
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;
}
}

View File

@ -3042,34 +3042,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
Assert.Equal(2, callOrder.Pop());
}
[Fact]
public async Task SynchronousWritesAllowedByDefault()
public async Task SynchronousWritesDisallowedByDefault()
{
var firstRequest = true;
using (var server = new TestServer(async context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
Assert.True(bodyControlFeature.AllowSynchronousIO);
Assert.False(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(CoreStrings.SynchronousWritesDisallowed, ioEx.Message);
await context.Response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello1"), 0, 6);
// 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)))
{
using (var connection = server.CreateConnection())
@ -3081,14 +3068,67 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
"Content-Length: 6",
"",
"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.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"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();
}

View File

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