Skip to content

Commit 93a24b0

Browse files
authored
Disable AllowSynchronousIO by default in all servers #4774 (#5120)
* Disable AllowSynchronousIO by default in all servers
1 parent 7daa0e0 commit 93a24b0

20 files changed

+292
-137
lines changed

src/Hosting/TestHost/src/AsyncStreamWrapper.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,17 @@ internal AsyncStreamWrapper(Stream inner, Func<bool> allowSynchronousIO)
2121

2222
public override bool CanRead => _inner.CanRead;
2323

24-
public override bool CanSeek => _inner.CanSeek;
24+
public override bool CanSeek => false;
2525

2626
public override bool CanWrite => _inner.CanWrite;
2727

28-
public override long Length => _inner.Length;
28+
public override long Length => throw new NotSupportedException("The stream is not seekable.");
2929

30-
public override long Position { get => _inner.Position; set => _inner.Position = value; }
30+
public override long Position
31+
{
32+
get => throw new NotSupportedException("The stream is not seekable.");
33+
set => throw new NotSupportedException("The stream is not seekable.");
34+
}
3135

3236
public override void Flush()
3337
{
@@ -72,12 +76,12 @@ public override int EndRead(IAsyncResult asyncResult)
7276

7377
public override long Seek(long offset, SeekOrigin origin)
7478
{
75-
return _inner.Seek(offset, origin);
79+
throw new NotSupportedException("The stream is not seekable.");
7680
}
7781

7882
public override void SetLength(long value)
7983
{
80-
_inner.SetLength(value);
84+
throw new NotSupportedException("The stream is not seekable.");
8185
}
8286

8387
public override void Write(byte[] buffer, int offset, int count)

src/Hosting/TestHost/src/TestServer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ public IWebHost Host
8181
/// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/>
8282
/// </summary>
8383
/// <remarks>
84-
/// Defaults to true.
84+
/// Defaults to false.
8585
/// </remarks>
86-
public bool AllowSynchronousIO { get; set; } = true;
86+
public bool AllowSynchronousIO { get; set; } = false;
8787

8888
private IHttpApplication<Context> Application
8989
{

src/Mvc/Mvc.Formatters.Xml/src/XmlDataContractSerializerOutputFormatter.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,13 @@ public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext co
254254

255255
var dataContractSerializer = GetCachedSerializer(wrappingType);
256256

257+
// Opt into sync IO support until we can work out an alternative https://github.com/aspnet/AspNetCore/issues/6397
258+
var syncIOFeature = context.HttpContext.Features.Get<Http.Features.IHttpBodyControlFeature>();
259+
if (syncIOFeature != null)
260+
{
261+
syncIOFeature.AllowSynchronousIO = true;
262+
}
263+
257264
using (var textWriter = context.WriterFactory(context.HttpContext.Response.Body, writerSettings.Encoding))
258265
{
259266
using (var xmlWriter = CreateXmlWriter(context, textWriter, writerSettings))

src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerOutputFormatter.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,13 @@ public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext co
230230

231231
var xmlSerializer = GetCachedSerializer(wrappingType);
232232

233+
// Opt into sync IO support until we can work out an alternative https://github.com/aspnet/AspNetCore/issues/6397
234+
var syncIOFeature = context.HttpContext.Features.Get<Http.Features.IHttpBodyControlFeature>();
235+
if (syncIOFeature != null)
236+
{
237+
syncIOFeature.AllowSynchronousIO = true;
238+
}
239+
233240
using (var textWriter = context.WriterFactory(context.HttpContext.Response.Body, writerSettings.Encoding))
234241
{
235242
using (var xmlWriter = CreateXmlWriter(context, textWriter, writerSettings))

src/Mvc/Mvc.ViewFeatures/src/ViewComponentResultExecutor.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ public virtual async Task ExecuteAsync(ActionContext context, ViewComponentResul
105105
response.StatusCode = result.StatusCode.Value;
106106
}
107107

108+
// Opt into sync IO support until we can work out an alternative https://github.com/aspnet/AspNetCore/issues/6397
109+
var syncIOFeature = context.HttpContext.Features.Get<Http.Features.IHttpBodyControlFeature>();
110+
if (syncIOFeature != null)
111+
{
112+
syncIOFeature.AllowSynchronousIO = true;
113+
}
114+
108115
using (var writer = new HttpResponseStreamWriter(response.Body, resolvedContentTypeEncoding))
109116
{
110117
var viewContext = new ViewContext(

src/Mvc/test/WebSites/BasicWebSite/StartupRequestLimitSize.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System;
55
using System.IO;
6+
using System.Threading;
7+
using System.Threading.Tasks;
68
using Microsoft.AspNetCore.Builder;
79
using Microsoft.AspNetCore.Http.Features;
810
using Microsoft.AspNetCore.Mvc;
@@ -48,6 +50,7 @@ private class RequestBodySizeCheckingStream : Stream
4850
{
4951
private readonly Stream _innerStream;
5052
private readonly IHttpMaxRequestBodySizeFeature _maxRequestBodySizeFeature;
53+
private long _totalRead;
5154

5255
public RequestBodySizeCheckingStream(
5356
Stream innerStream,
@@ -78,12 +81,39 @@ public override void Flush()
7881
public override int Read(byte[] buffer, int offset, int count)
7982
{
8083
if (_maxRequestBodySizeFeature.MaxRequestBodySize != null
81-
&& _innerStream.Length > _maxRequestBodySizeFeature.MaxRequestBodySize)
84+
&& _innerStream.CanSeek && _innerStream.Length > _maxRequestBodySizeFeature.MaxRequestBodySize)
8285
{
8386
throw new InvalidOperationException("Request content size is greater than the limit size");
8487
}
8588

86-
return _innerStream.Read(buffer, offset, count);
89+
var read = _innerStream.Read(buffer, offset, count);
90+
_totalRead += read;
91+
92+
if (_maxRequestBodySizeFeature.MaxRequestBodySize != null
93+
&& _totalRead > _maxRequestBodySizeFeature.MaxRequestBodySize)
94+
{
95+
throw new InvalidOperationException("Request content size is greater than the limit size");
96+
}
97+
return read;
98+
}
99+
100+
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
101+
{
102+
if (_maxRequestBodySizeFeature.MaxRequestBodySize != null
103+
&& _innerStream.CanSeek && _innerStream.Length > _maxRequestBodySizeFeature.MaxRequestBodySize)
104+
{
105+
throw new InvalidOperationException("Request content size is greater than the limit size");
106+
}
107+
108+
var read = await _innerStream.ReadAsync(buffer, offset, count, cancellationToken);
109+
_totalRead += read;
110+
111+
if (_maxRequestBodySizeFeature.MaxRequestBodySize != null
112+
&& _totalRead > _maxRequestBodySizeFeature.MaxRequestBodySize)
113+
{
114+
throw new InvalidOperationException("Request content size is greater than the limit size");
115+
}
116+
return read;
87117
}
88118

89119
public override long Seek(long offset, SeekOrigin origin)

src/Mvc/test/WebSites/FormatterWebSite/StringInputFormatter.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ public StringInputFormatter()
2020
SupportedEncodings.Add(Encoding.Unicode);
2121
}
2222

23-
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding)
23+
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding)
2424
{
2525
var request = context.HttpContext.Request;
2626
using (var reader = new StreamReader(request.Body, effectiveEncoding))
2727
{
28-
var stringContent = reader.ReadToEnd();
29-
return InputFormatterResult.SuccessAsync(stringContent);
28+
var stringContent = await reader.ReadToEndAsync();
29+
return await InputFormatterResult.SuccessAsync(stringContent);
3030
}
3131
}
3232
}

src/Servers/HttpSys/src/HttpSysOptions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -136,9 +136,9 @@ public long? MaxRequestBodySize
136136

137137
/// <summary>
138138
/// Gets or sets a value that controls whether synchronous IO is allowed for the HttpContext.Request.Body and HttpContext.Response.Body.
139-
/// The default is `true`.
139+
/// The default is `false`.
140140
/// </summary>
141-
public bool AllowSynchronousIO { get; set; } = true;
141+
public bool AllowSynchronousIO { get; set; } = false;
142142

143143
/// <summary>
144144
/// Gets or sets a value that controls how http.sys reacts when rejecting requests due to throttling conditions - like when the request

src/Servers/HttpSys/test/FunctionalTests/HttpsTests.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,13 @@ public async Task Https_SendHelloWorld_Success()
5151
[ConditionalFact]
5252
public async Task Https_EchoHelloWorld_Success()
5353
{
54-
using (Utilities.CreateDynamicHttpsServer(out var address, httpContext =>
54+
using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext =>
5555
{
56-
string input = new StreamReader(httpContext.Request.Body).ReadToEnd();
56+
var input = await new StreamReader(httpContext.Request.Body).ReadToEndAsync();
5757
Assert.Equal("Hello World", input);
58-
byte[] body = Encoding.UTF8.GetBytes("Hello World");
58+
var body = Encoding.UTF8.GetBytes("Hello World");
5959
httpContext.Response.ContentLength = body.Length;
60-
httpContext.Response.Body.Write(body, 0, body.Length);
61-
return Task.FromResult(0);
60+
await httpContext.Response.Body.WriteAsync(body, 0, body.Length);
6261
}))
6362
{
6463
string response = await SendRequestAsync(address, "Hello World");

src/Servers/HttpSys/test/FunctionalTests/Listener/RequestBodyTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -17,26 +17,26 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
1717
public class RequestBodyTests
1818
{
1919
[ConditionalFact]
20-
public async Task RequestBody_SyncReadEnabledByDefault_ThrowsWhenDisabled()
20+
public async Task RequestBody_SyncReadDisabledByDefault_WorksWhenEnabled()
2121
{
2222
string address;
2323
using (var server = Utilities.CreateHttpServer(out address))
2424
{
2525
Task<string> responseTask = SendRequestAsync(address, "Hello World");
2626

27-
Assert.True(server.Options.AllowSynchronousIO);
27+
Assert.False(server.Options.AllowSynchronousIO);
2828

2929
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
3030
byte[] input = new byte[100];
31+
Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(input, 0, input.Length));
32+
33+
context.AllowSynchronousIO = true;
3134

3235
Assert.True(context.AllowSynchronousIO);
3336
var read = context.Request.Body.Read(input, 0, input.Length);
3437
context.Response.ContentLength = read;
3538
context.Response.Body.Write(input, 0, read);
3639

37-
context.AllowSynchronousIO = false;
38-
Assert.Throws<InvalidOperationException>(() => context.Request.Body.Read(input, 0, input.Length));
39-
4040
string response = await responseTask;
4141
Assert.Equal("Hello World", response);
4242
}

src/Servers/HttpSys/test/FunctionalTests/Listener/ResponseBodyTests.cs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
1717
public class ResponseBodyTests
1818
{
1919
[ConditionalFact]
20-
public async Task ResponseBody_SyncWriteEnabledByDefault_ThrowsWhenDisabled()
20+
public async Task ResponseBody_SyncWriteDisabledByDefault_WorksWhenEnabled()
2121
{
2222
string address;
2323
using (var server = Utilities.CreateHttpServer(out address))
@@ -26,19 +26,17 @@ public async Task ResponseBody_SyncWriteEnabledByDefault_ThrowsWhenDisabled()
2626

2727
var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
2828

29-
Assert.True(context.AllowSynchronousIO);
30-
31-
context.Response.Body.Flush();
32-
context.Response.Body.Write(new byte[10], 0, 10);
33-
context.Response.Body.Flush();
34-
35-
context.AllowSynchronousIO = false;
29+
Assert.False(context.AllowSynchronousIO);
3630

3731
Assert.Throws<InvalidOperationException>(() => context.Response.Body.Flush());
3832
Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(new byte[10], 0, 10));
3933
Assert.Throws<InvalidOperationException>(() => context.Response.Body.Flush());
4034

41-
await context.Response.Body.WriteAsync(new byte[10], 0, 10);
35+
context.AllowSynchronousIO = true;
36+
37+
context.Response.Body.Flush();
38+
context.Response.Body.Write(new byte[10], 0, 10);
39+
context.Response.Body.Flush();
4240
context.Dispose();
4341

4442
var response = await responseTask;
@@ -47,7 +45,7 @@ public async Task ResponseBody_SyncWriteEnabledByDefault_ThrowsWhenDisabled()
4745
IEnumerable<string> ignored;
4846
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
4947
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
50-
Assert.Equal(new byte[20], await response.Content.ReadAsByteArrayAsync());
48+
Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync());
5149
}
5250
}
5351

@@ -477,4 +475,4 @@ public async Task ResponseBody_ClientDisconnectsBeforeSecondWriteAsync_WriteComp
477475
}
478476
}
479477
}
480-
}
478+
}

src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ unsafe X509Certificate2 ITlsConnectionFeature.ClientCertificate
320320

321321
IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator();
322322

323-
bool IHttpBodyControlFeature.AllowSynchronousIO { get; set; } = true;
323+
bool IHttpBodyControlFeature.AllowSynchronousIO { get; set; }
324324

325325
void IHttpBufferingFeature.DisableRequestBuffering()
326326
{

src/Servers/IIS/IIS/src/IISServerOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ public class IISServerOptions
1111
/// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/>
1212
/// </summary>
1313
/// <remarks>
14-
/// Defaults to true.
14+
/// Defaults to false.
1515
/// </remarks>
16-
public bool AllowSynchronousIO { get; set; } = true;
16+
public bool AllowSynchronousIO { get; set; } = false;
1717

1818
/// <summary>
1919
/// If true the server should set HttpContext.User. If false the server will only provide an
Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System;
54
using System.Threading.Tasks;
65
using Microsoft.AspNetCore.Http.Features;
76
using Microsoft.AspNetCore.Testing.xunit;
@@ -11,44 +10,22 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
1110
{
1211
[SkipIfHostableWebCoreNotAvailable]
1312
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
14-
public class HttpBodyControlFeatureTests : StrictTestServerTests
13+
public class ConnectionIdFeatureTests : StrictTestServerTests
1514
{
1615
[ConditionalFact]
17-
public async Task ThrowsOnSyncReadOrWrite()
16+
public async Task ProvidesConnectionId()
1817
{
19-
Exception writeException = null;
20-
Exception readException = null;
21-
using (var testServer = await TestServer.Create(
22-
ctx => {
23-
var bodyControl = ctx.Features.Get<IHttpBodyControlFeature>();
24-
bodyControl.AllowSynchronousIO = false;
25-
26-
try
27-
{
28-
ctx.Response.Body.Write(new byte[10]);
29-
}
30-
catch (Exception ex)
31-
{
32-
writeException = ex;
33-
}
34-
35-
try
36-
{
37-
ctx.Request.Body.Read(new byte[10]);
38-
}
39-
catch (Exception ex)
40-
{
41-
readException = ex;
42-
}
43-
44-
return Task.CompletedTask;
45-
}, LoggerFactory))
18+
string connectionId = null;
19+
using (var testServer = await TestServer.Create(ctx => {
20+
var connectionIdFeature = ctx.Features.Get<IHttpConnectionFeature>();
21+
connectionId = connectionIdFeature.ConnectionId;
22+
return Task.CompletedTask;
23+
}, LoggerFactory))
4624
{
4725
await testServer.HttpClient.GetStringAsync("/");
4826
}
4927

50-
Assert.IsType<InvalidOperationException>(readException);
51-
Assert.IsType<InvalidOperationException>(writeException);
28+
Assert.NotNull(connectionId);
5229
}
5330
}
5431
}

0 commit comments

Comments
 (0)