* Disable AllowSynchronousIO by default in all servers
This commit is contained in:
parent
7daa0e0145
commit
93a24b03bb
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue