Add AllowSynchronousIO to TestServer and IIS, fix tests (#6404)

This commit is contained in:
Chris Ross 2019-01-15 11:48:17 -08:00 committed by GitHub
parent 0defbf74f8
commit 1f892d798d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 253 additions and 84 deletions

View File

@ -0,0 +1,128 @@
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.TestHost
{
internal class AsyncStreamWrapper : Stream
{
private Stream _inner;
private Func<bool> _allowSynchronousIO;
internal AsyncStreamWrapper(Stream inner, Func<bool> allowSynchronousIO)
{
_inner = inner;
_allowSynchronousIO = allowSynchronousIO;
}
public override bool CanRead => _inner.CanRead;
public override bool CanSeek => _inner.CanSeek;
public override bool CanWrite => _inner.CanWrite;
public override long Length => _inner.Length;
public override long Position { get => _inner.Position; set => _inner.Position = value; }
public override void Flush()
{
// Not blocking Flush because things like StreamWriter.Dispose() always call it.
_inner.Flush();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return _inner.FlushAsync(cancellationToken);
}
public override int Read(byte[] buffer, int offset, int count)
{
if (!_allowSynchronousIO())
{
throw new InvalidOperationException("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true.");
}
return _inner.Read(buffer, offset, count);
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _inner.ReadAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
return _inner.ReadAsync(buffer, cancellationToken);
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _inner.BeginRead(buffer, offset, count, callback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
return _inner.EndRead(asyncResult);
}
public override long Seek(long offset, SeekOrigin origin)
{
return _inner.Seek(offset, origin);
}
public override void SetLength(long value)
{
_inner.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
if (!_allowSynchronousIO())
{
throw new InvalidOperationException("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true.");
}
_inner.Write(buffer, offset, count);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _inner.BeginWrite(buffer, offset, count, callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
_inner.EndWrite(asyncResult);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _inner.WriteAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
return _inner.WriteAsync(buffer, cancellationToken);
}
public override void Close()
{
_inner.Close();
}
protected override void Dispose(bool disposing)
{
_inner.Dispose();
}
public override ValueTask DisposeAsync()
{
return _inner.DisposeAsync();
}
}
}

View File

@ -43,6 +43,8 @@ namespace Microsoft.AspNetCore.TestHost
_pathBase = pathBase; _pathBase = pathBase;
} }
internal bool AllowSynchronousIO { get; set; }
/// <summary> /// <summary>
/// This adapts HttpRequestMessages to ASP.NET Core requests, dispatches them through the pipeline, and returns the /// This adapts HttpRequestMessages to ASP.NET Core requests, dispatches them through the pipeline, and returns the
/// associated HttpResponseMessage. /// associated HttpResponseMessage.
@ -59,7 +61,7 @@ namespace Microsoft.AspNetCore.TestHost
throw new ArgumentNullException(nameof(request)); throw new ArgumentNullException(nameof(request));
} }
var contextBuilder = new HttpContextBuilder(_application); var contextBuilder = new HttpContextBuilder(_application, AllowSynchronousIO);
Stream responseBody = null; Stream responseBody = null;
var requestContent = request.Content ?? new StreamContent(Stream.Null); var requestContent = request.Content ?? new StreamContent(Stream.Null);
@ -110,7 +112,7 @@ namespace Microsoft.AspNetCore.TestHost
// This body may have been consumed before, rewind it. // This body may have been consumed before, rewind it.
body.Seek(0, SeekOrigin.Begin); body.Seek(0, SeekOrigin.Begin);
} }
req.Body = body; req.Body = new AsyncStreamWrapper(body, () => contextBuilder.AllowSynchronousIO);
responseBody = context.Response.Body; responseBody = context.Response.Body;
}); });

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;
@ -11,7 +11,7 @@ using static Microsoft.AspNetCore.Hosting.Internal.HostingApplication;
namespace Microsoft.AspNetCore.TestHost namespace Microsoft.AspNetCore.TestHost
{ {
internal class HttpContextBuilder internal class HttpContextBuilder : IHttpBodyControlFeature
{ {
private readonly IHttpApplication<Context> _application; private readonly IHttpApplication<Context> _application;
private readonly HttpContext _httpContext; private readonly HttpContext _httpContext;
@ -23,24 +23,28 @@ namespace Microsoft.AspNetCore.TestHost
private bool _pipelineFinished; private bool _pipelineFinished;
private Context _testContext; private Context _testContext;
internal HttpContextBuilder(IHttpApplication<Context> application) internal HttpContextBuilder(IHttpApplication<Context> application, bool allowSynchronousIO)
{ {
_application = application ?? throw new ArgumentNullException(nameof(application)); _application = application ?? throw new ArgumentNullException(nameof(application));
AllowSynchronousIO = allowSynchronousIO;
_httpContext = new DefaultHttpContext(); _httpContext = new DefaultHttpContext();
var request = _httpContext.Request; var request = _httpContext.Request;
request.Protocol = "HTTP/1.1"; request.Protocol = "HTTP/1.1";
request.Method = HttpMethods.Get; request.Method = HttpMethods.Get;
_httpContext.Features.Set<IHttpBodyControlFeature>(this);
_httpContext.Features.Set<IHttpResponseFeature>(_responseFeature); _httpContext.Features.Set<IHttpResponseFeature>(_responseFeature);
var requestLifetimeFeature = new HttpRequestLifetimeFeature(); var requestLifetimeFeature = new HttpRequestLifetimeFeature();
requestLifetimeFeature.RequestAborted = _requestAbortedSource.Token; requestLifetimeFeature.RequestAborted = _requestAbortedSource.Token;
_httpContext.Features.Set<IHttpRequestLifetimeFeature>(requestLifetimeFeature); _httpContext.Features.Set<IHttpRequestLifetimeFeature>(requestLifetimeFeature);
_responseStream = new ResponseStream(ReturnResponseMessageAsync, AbortRequest); _responseStream = new ResponseStream(ReturnResponseMessageAsync, AbortRequest, () => AllowSynchronousIO);
_responseFeature.Body = _responseStream; _responseFeature.Body = _responseStream;
} }
public bool AllowSynchronousIO { get; set; }
internal void Configure(Action<HttpContext> configureContext) internal void Configure(Action<HttpContext> configureContext)
{ {
if (configureContext == null) if (configureContext == null)
@ -136,4 +140,4 @@ namespace Microsoft.AspNetCore.TestHost
_responseTcs.TrySetException(exception); _responseTcs.TrySetException(exception);
} }
} }
} }

View File

@ -24,13 +24,15 @@ namespace Microsoft.AspNetCore.TestHost
private Func<Task> _onFirstWriteAsync; private Func<Task> _onFirstWriteAsync;
private bool _firstWrite; private bool _firstWrite;
private Action _abortRequest; private Action _abortRequest;
private Func<bool> _allowSynchronousIO;
private Pipe _pipe = new Pipe(); private Pipe _pipe = new Pipe();
internal ResponseStream(Func<Task> onFirstWriteAsync, Action abortRequest) internal ResponseStream(Func<Task> onFirstWriteAsync, Action abortRequest, Func<bool> allowSynchronousIO)
{ {
_onFirstWriteAsync = onFirstWriteAsync ?? throw new ArgumentNullException(nameof(onFirstWriteAsync)); _onFirstWriteAsync = onFirstWriteAsync ?? throw new ArgumentNullException(nameof(onFirstWriteAsync));
_abortRequest = abortRequest ?? throw new ArgumentNullException(nameof(abortRequest)); _abortRequest = abortRequest ?? throw new ArgumentNullException(nameof(abortRequest));
_allowSynchronousIO = allowSynchronousIO ?? throw new ArgumentNullException(nameof(allowSynchronousIO));
_firstWrite = true; _firstWrite = true;
_writeLock = new SemaphoreSlim(1, 1); _writeLock = new SemaphoreSlim(1, 1);
} }
@ -144,6 +146,11 @@ namespace Microsoft.AspNetCore.TestHost
// Write with count 0 will still trigger OnFirstWrite // Write with count 0 will still trigger OnFirstWrite
public override void Write(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count)
{ {
if (!_allowSynchronousIO())
{
throw new InvalidOperationException("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true.");
}
// The Pipe Write method requires calling FlushAsync to notify the reader. Call WriteAsync instead. // The Pipe Write method requires calling FlushAsync to notify the reader. Call WriteAsync instead.
WriteAsync(buffer, offset, count).GetAwaiter().GetResult(); WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
} }

View File

@ -77,6 +77,14 @@ namespace Microsoft.AspNetCore.TestHost
public IFeatureCollection Features { get; } public IFeatureCollection Features { get; }
/// <summary>
/// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/>
/// </summary>
/// <remarks>
/// Defaults to true.
/// </remarks>
public bool AllowSynchronousIO { get; set; } = true;
private IHttpApplication<Context> Application private IHttpApplication<Context> Application
{ {
get => _application ?? throw new InvalidOperationException("The server has not been started or no web application was configured."); get => _application ?? throw new InvalidOperationException("The server has not been started or no web application was configured.");
@ -85,7 +93,7 @@ namespace Microsoft.AspNetCore.TestHost
public HttpMessageHandler CreateHandler() public HttpMessageHandler CreateHandler()
{ {
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress); var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
return new ClientHandler(pathBase, Application); return new ClientHandler(pathBase, Application) { AllowSynchronousIO = AllowSynchronousIO };
} }
public HttpClient CreateClient() public HttpClient CreateClient()
@ -96,7 +104,7 @@ namespace Microsoft.AspNetCore.TestHost
public WebSocketClient CreateWebSocketClient() public WebSocketClient CreateWebSocketClient()
{ {
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress); var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
return new WebSocketClient(pathBase, Application); return new WebSocketClient(pathBase, Application) { AllowSynchronousIO = AllowSynchronousIO };
} }
/// <summary> /// <summary>
@ -120,7 +128,7 @@ namespace Microsoft.AspNetCore.TestHost
throw new ArgumentNullException(nameof(configureContext)); throw new ArgumentNullException(nameof(configureContext));
} }
var builder = new HttpContextBuilder(Application); var builder = new HttpContextBuilder(Application, AllowSynchronousIO);
builder.Configure(context => builder.Configure(context =>
{ {
var request = context.Request; var request = context.Request;
@ -138,6 +146,7 @@ namespace Microsoft.AspNetCore.TestHost
request.PathBase = pathBase; request.PathBase = pathBase;
}); });
builder.Configure(configureContext); builder.Configure(configureContext);
// TODO: Wrap the request body if any?
return await builder.SendAsync(cancellationToken).ConfigureAwait(false); return await builder.SendAsync(cancellationToken).ConfigureAwait(false);
} }

View File

@ -46,10 +46,12 @@ namespace Microsoft.AspNetCore.TestHost
set; set;
} }
internal bool AllowSynchronousIO { get; set; }
public async Task<WebSocket> ConnectAsync(Uri uri, CancellationToken cancellationToken) public async Task<WebSocket> ConnectAsync(Uri uri, CancellationToken cancellationToken)
{ {
WebSocketFeature webSocketFeature = null; WebSocketFeature webSocketFeature = null;
var contextBuilder = new HttpContextBuilder(_application); var contextBuilder = new HttpContextBuilder(_application, AllowSynchronousIO);
contextBuilder.Configure(context => contextBuilder.Configure(context =>
{ {
var request = context.Request; var request = context.Request;
@ -131,4 +133,4 @@ namespace Microsoft.AspNetCore.TestHost
} }
} }
} }
} }

View File

@ -92,13 +92,12 @@ namespace Microsoft.AspNetCore.TestHost
public async Task ResubmitRequestWorks() public async Task ResubmitRequestWorks()
{ {
int requestCount = 1; int requestCount = 1;
var handler = new ClientHandler(PathString.Empty, new DummyApplication(context => var handler = new ClientHandler(PathString.Empty, new DummyApplication(async context =>
{ {
int read = context.Request.Body.Read(new byte[100], 0, 100); int read = await context.Request.Body.ReadAsync(new byte[100], 0, 100);
Assert.Equal(11, read); Assert.Equal(11, read);
context.Response.Headers["TestHeader"] = "TestValue:" + requestCount++; context.Response.Headers["TestHeader"] = "TestValue:" + requestCount++;
return Task.FromResult(0);
})); }));
HttpMessageInvoker invoker = new HttpMessageInvoker(handler); HttpMessageInvoker invoker = new HttpMessageInvoker(handler);

View File

@ -109,6 +109,7 @@ namespace Microsoft.AspNetCore.TestHost
{ {
c.Response.Headers["TestHeader"] = "TestValue"; c.Response.Headers["TestHeader"] = "TestValue";
var bytes = Encoding.UTF8.GetBytes("BodyStarted" + Environment.NewLine); var bytes = Encoding.UTF8.GetBytes("BodyStarted" + Environment.NewLine);
c.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
c.Response.Body.Write(bytes, 0, bytes.Length); c.Response.Body.Write(bytes, 0, bytes.Length);
await block.Task; await block.Task;
bytes = Encoding.UTF8.GetBytes("BodyFinished"); bytes = Encoding.UTF8.GetBytes("BodyFinished");

View File

@ -87,8 +87,8 @@ namespace Microsoft.AspNetCore.TestHost
public async Task PutAsyncWorks() public async Task PutAsyncWorks()
{ {
// Arrange // Arrange
RequestDelegate appDelegate = ctx => RequestDelegate appDelegate = async ctx =>
ctx.Response.WriteAsync(new StreamReader(ctx.Request.Body).ReadToEnd() + " PUT Response"); await ctx.Response.WriteAsync(await new StreamReader(ctx.Request.Body).ReadToEndAsync() + " PUT Response");
var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate)); var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate));
var server = new TestServer(builder); var server = new TestServer(builder);
var client = server.CreateClient(); var client = server.CreateClient();
@ -106,7 +106,7 @@ namespace Microsoft.AspNetCore.TestHost
{ {
// Arrange // Arrange
RequestDelegate appDelegate = async ctx => RequestDelegate appDelegate = async ctx =>
await ctx.Response.WriteAsync(new StreamReader(ctx.Request.Body).ReadToEnd() + " POST Response"); await ctx.Response.WriteAsync(await new StreamReader(ctx.Request.Body).ReadToEndAsync() + " POST Response");
var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate)); var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate));
var server = new TestServer(builder); var server = new TestServer(builder);
var client = server.CreateClient(); var client = server.CreateClient();
@ -132,16 +132,15 @@ namespace Microsoft.AspNetCore.TestHost
} }
var builder = new WebHostBuilder(); var builder = new WebHostBuilder();
RequestDelegate app = (ctx) => RequestDelegate app = async ctx =>
{ {
var disposable = new TestDisposable(); var disposable = new TestDisposable();
ctx.Response.RegisterForDispose(disposable); ctx.Response.RegisterForDispose(disposable);
ctx.Response.Body.Write(data, 0, 1024); await ctx.Response.Body.WriteAsync(data, 0, 1024);
Assert.False(disposable.IsDisposed); Assert.False(disposable.IsDisposed);
ctx.Response.Body.Write(data, 1024, 1024); await ctx.Response.Body.WriteAsync(data, 1024, 1024);
return Task.FromResult(0);
}; };
builder.Configure(appBuilder => appBuilder.Run(app)); builder.Configure(appBuilder => appBuilder.Run(app));

View File

@ -352,12 +352,12 @@ namespace Microsoft.AspNetCore.Identity.InMemory
} }
else if (req.Path == new PathString("/me")) else if (req.Path == new PathString("/me"))
{ {
Describe(res, AuthenticateResult.Success(new AuthenticationTicket(context.User, null, "Application"))); await DescribeAsync(res, AuthenticateResult.Success(new AuthenticationTicket(context.User, null, "Application")));
} }
else if (req.Path.StartsWithSegments(new PathString("/me"), out remainder)) else if (req.Path.StartsWithSegments(new PathString("/me"), out remainder))
{ {
var auth = await context.AuthenticateAsync(remainder.Value.Substring(1)); var auth = await context.AuthenticateAsync(remainder.Value.Substring(1));
Describe(res, auth); await DescribeAsync(res, auth);
} }
else if (req.Path == new PathString("/testpath") && testpath != null) else if (req.Path == new PathString("/testpath") && testpath != null)
{ {
@ -393,7 +393,7 @@ namespace Microsoft.AspNetCore.Identity.InMemory
return server; return server;
} }
private static void Describe(HttpResponse res, AuthenticateResult result) private static async Task DescribeAsync(HttpResponse res, AuthenticateResult result)
{ {
res.StatusCode = 200; res.StatusCode = 200;
res.ContentType = "text/xml"; res.ContentType = "text/xml";
@ -412,7 +412,7 @@ namespace Microsoft.AspNetCore.Identity.InMemory
{ {
xml.WriteTo(writer); xml.WriteTo(writer);
} }
res.Body.Write(memory.ToArray(), 0, memory.ToArray().Length); await res.Body.WriteAsync(memory.ToArray(), 0, memory.ToArray().Length);
} }
} }

View File

@ -25,9 +25,7 @@ namespace SampleDestination
context.Response.ContentType = "text/plain; charset=utf-8"; context.Response.ContentType = "text/plain; charset=utf-8";
context.Response.ContentLength = content.Length; context.Response.ContentLength = content.Length;
context.Response.Body.Write(content, 0, content.Length); return context.Response.Body.WriteAsync(content, 0, content.Length);
return Task.CompletedTask;
} }
} }
} }

View File

@ -86,9 +86,7 @@ namespace SampleDestination
context.Response.ContentType = "text/plain; charset=utf-8"; context.Response.ContentType = "text/plain; charset=utf-8";
context.Response.ContentLength = content.Length; context.Response.ContentLength = content.Length;
context.Response.Body.Write(content, 0, content.Length); return context.Response.Body.WriteAsync(content, 0, content.Length);
return Task.CompletedTask;
} }
} }
} }

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;
@ -81,6 +81,11 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
var uniqueId = Guid.NewGuid().ToString(); var uniqueId = Guid.NewGuid().ToString();
if (TestRequestDelegate(context, uniqueId)) if (TestRequestDelegate(context, uniqueId))
{ {
var feature = context.Features.Get<IHttpBodyControlFeature>();
if (feature != null)
{
feature.AllowSynchronousIO = true;
}
context.Response.Write(uniqueId); context.Response.Write(uniqueId);
} }
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -548,6 +548,12 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
app.UseResponseCompression(); app.UseResponseCompression();
app.Run(context => app.Run(context =>
{ {
var feature = context.Features.Get<IHttpBodyControlFeature>();
if (feature != null)
{
feature.AllowSynchronousIO = true;
}
context.Response.Headers[HeaderNames.ContentMD5] = "MD5"; context.Response.Headers[HeaderNames.ContentMD5] = "MD5";
context.Response.ContentType = TextPlain; context.Response.ContentType = TextPlain;
context.Response.Body.Write(new byte[10], 0, 10); context.Response.Body.Write(new byte[10], 0, 10);
@ -652,6 +658,12 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
context.Response.ContentType = TextPlain; context.Response.ContentType = TextPlain;
context.Features.Get<IHttpBufferingFeature>()?.DisableResponseBuffering(); context.Features.Get<IHttpBufferingFeature>()?.DisableResponseBuffering();
var feature = context.Features.Get<IHttpBodyControlFeature>();
if (feature != null)
{
feature.AllowSynchronousIO = true;
}
foreach (var signal in responseReceived) foreach (var signal in responseReceived)
{ {
context.Response.Body.Write(new byte[1], 0, 1); context.Response.Body.Write(new byte[1], 0, 1);

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.Text; using System.Text;
@ -31,6 +31,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
if (!string.IsNullOrEmpty(StatusDescription)) if (!string.IsNullOrEmpty(StatusDescription))
{ {
var feature = context.HttpContext.Features.Get<IHttpBodyControlFeature>();
if (feature != null)
{
feature.AllowSynchronousIO = true;
}
var content = Encoding.UTF8.GetBytes(StatusDescription); var content = Encoding.UTF8.GetBytes(StatusDescription);
response.ContentLength = content.Length; response.ContentLength = content.Length;
response.ContentType = "text/plain; charset=utf-8"; response.ContentType = "text/plain; charset=utf-8";
@ -42,4 +47,4 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
context.Logger?.CustomResponse(context.HttpContext.Request.GetEncodedUrl()); context.Logger?.CustomResponse(context.HttpContext.Request.GetEncodedUrl());
} }
} }
} }

View File

@ -1302,7 +1302,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
app.Use(async (context, next) => app.Use(async (context, next) =>
{ {
var result = await context.AuthenticateAsync("Cookies"); var result = await context.AuthenticateAsync("Cookies");
Describe(context.Response, result); await DescribeAsync(context.Response, result);
}); });
}) })
.ConfigureServices(services => services.AddAuthentication().AddCookie("Cookies", o => .ConfigureServices(services => services.AddAuthentication().AddCookie("Cookies", o =>
@ -1478,12 +1478,12 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
} }
else if (req.Path == new PathString("/me")) else if (req.Path == new PathString("/me"))
{ {
Describe(res, AuthenticateResult.Success(new AuthenticationTicket(context.User, new AuthenticationProperties(), CookieAuthenticationDefaults.AuthenticationScheme))); await DescribeAsync(res, AuthenticateResult.Success(new AuthenticationTicket(context.User, new AuthenticationProperties(), CookieAuthenticationDefaults.AuthenticationScheme)));
} }
else if (req.Path.StartsWithSegments(new PathString("/me"), out remainder)) else if (req.Path.StartsWithSegments(new PathString("/me"), out remainder))
{ {
var ticket = await context.AuthenticateAsync(remainder.Value.Substring(1)); var ticket = await context.AuthenticateAsync(remainder.Value.Substring(1));
Describe(res, ticket); await DescribeAsync(res, ticket);
} }
else if (req.Path == new PathString("/testpath") && testpath != null) else if (req.Path == new PathString("/testpath") && testpath != null)
{ {
@ -1510,7 +1510,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
return server; return server;
} }
private static void Describe(HttpResponse res, AuthenticateResult result) private static Task DescribeAsync(HttpResponse res, AuthenticateResult result)
{ {
res.StatusCode = 200; res.StatusCode = 200;
res.ContentType = "text/xml"; res.ContentType = "text/xml";
@ -1524,7 +1524,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
xml.Add(result.Ticket.Properties.Items.Select(extra => new XElement("extra", new XAttribute("type", extra.Key), new XAttribute("value", extra.Value)))); xml.Add(result.Ticket.Properties.Items.Select(extra => new XElement("extra", new XAttribute("type", extra.Key), new XAttribute("value", extra.Value))));
} }
var xmlBytes = Encoding.UTF8.GetBytes(xml.ToString()); var xmlBytes = Encoding.UTF8.GetBytes(xml.ToString());
res.Body.Write(xmlBytes, 0, xmlBytes.Length); return res.Body.WriteAsync(xmlBytes, 0, xmlBytes.Length);
} }
private static async Task<Transaction> SendAsync(TestServer server, string uri, string cookieHeader = null) private static async Task<Transaction> SendAsync(TestServer server, string uri, string cookieHeader = null)

View File

@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Authentication
{ {
var name = (remainder.Value.Length > 0) ? remainder.Value.Substring(1) : null; var name = (remainder.Value.Length > 0) ? remainder.Value.Substring(1) : null;
var result = await context.AuthenticateAsync(name); var result = await context.AuthenticateAsync(name);
res.Describe(result?.Ticket?.Principal); await res.DescribeAsync(result?.Ticket?.Principal);
} }
else if (req.Path.StartsWithSegments(new PathString("/remove"), out remainder)) else if (req.Path.StartsWithSegments(new PathString("/remove"), out remainder))
{ {

View File

@ -1177,26 +1177,26 @@ namespace Microsoft.AspNetCore.Authentication.Google
{ {
var result = await context.AuthenticateAsync(TestExtensions.CookieAuthenticationScheme); var result = await context.AuthenticateAsync(TestExtensions.CookieAuthenticationScheme);
var tokens = result.Properties.GetTokens(); var tokens = result.Properties.GetTokens();
res.Describe(tokens); await res.DescribeAsync(tokens);
} }
else if (req.Path == new PathString("/me")) else if (req.Path == new PathString("/me"))
{ {
res.Describe(context.User); await res.DescribeAsync(context.User);
} }
else if (req.Path == new PathString("/authenticate")) else if (req.Path == new PathString("/authenticate"))
{ {
var result = await context.AuthenticateAsync(TestExtensions.CookieAuthenticationScheme); var result = await context.AuthenticateAsync(TestExtensions.CookieAuthenticationScheme);
res.Describe(result.Principal); await res.DescribeAsync(result.Principal);
} }
else if (req.Path == new PathString("/authenticateGoogle")) else if (req.Path == new PathString("/authenticateGoogle"))
{ {
var result = await context.AuthenticateAsync("Google"); var result = await context.AuthenticateAsync("Google");
res.Describe(result?.Principal); await res.DescribeAsync(result?.Principal);
} }
else if (req.Path == new PathString("/authenticateFacebook")) else if (req.Path == new PathString("/authenticateFacebook"))
{ {
var result = await context.AuthenticateAsync("Facebook"); var result = await context.AuthenticateAsync("Facebook");
res.Describe(result?.Principal); await res.DescribeAsync(result?.Principal);
} }
else if (req.Path == new PathString("/unauthorized")) else if (req.Path == new PathString("/unauthorized"))
{ {

View File

@ -270,7 +270,7 @@ namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount
} }
else if (req.Path == new PathString("/me")) else if (req.Path == new PathString("/me"))
{ {
res.Describe(context.User); await res.DescribeAsync(context.User);
} }
else if (req.Path == new PathString("/signIn")) else if (req.Path == new PathString("/signIn"))
{ {

View File

@ -469,7 +469,7 @@ namespace Microsoft.AspNetCore.Authentication
{ {
var name = (remainder.Value.Length > 0) ? remainder.Value.Substring(1) : null; var name = (remainder.Value.Length > 0) ? remainder.Value.Substring(1) : null;
var result = await context.AuthenticateAsync(name); var result = await context.AuthenticateAsync(name);
res.Describe(result?.Ticket?.Principal); await res.DescribeAsync(result?.Ticket?.Principal);
} }
else else
{ {

View File

@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Authentication
return transaction; return transaction;
} }
public static void Describe(this HttpResponse res, ClaimsPrincipal principal) public static Task DescribeAsync(this HttpResponse res, ClaimsPrincipal principal)
{ {
res.StatusCode = 200; res.StatusCode = 200;
res.ContentType = "text/xml"; res.ContentType = "text/xml";
@ -62,10 +62,10 @@ namespace Microsoft.AspNetCore.Authentication
} }
} }
var xmlBytes = Encoding.UTF8.GetBytes(xml.ToString()); var xmlBytes = Encoding.UTF8.GetBytes(xml.ToString());
res.Body.Write(xmlBytes, 0, xmlBytes.Length); return res.Body.WriteAsync(xmlBytes, 0, xmlBytes.Length);
} }
public static void Describe(this HttpResponse res, IEnumerable<AuthenticationToken> tokens) public static Task DescribeAsync(this HttpResponse res, IEnumerable<AuthenticationToken> tokens)
{ {
res.StatusCode = 200; res.StatusCode = 200;
res.ContentType = "text/xml"; res.ContentType = "text/xml";
@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Authentication
} }
} }
var xmlBytes = Encoding.UTF8.GetBytes(xml.ToString()); var xmlBytes = Encoding.UTF8.GetBytes(xml.ToString());
res.Body.Write(xmlBytes, 0, xmlBytes.Length); return res.Body.WriteAsync(xmlBytes, 0, xmlBytes.Length);
} }
} }

View File

@ -45,24 +45,5 @@ namespace Microsoft.AspNetCore.CookiePolicy
} }
return transaction; return transaction;
} }
public static void Describe(this HttpResponse res, ClaimsPrincipal principal)
{
res.StatusCode = 200;
res.ContentType = "text/xml";
var xml = new XElement("xml");
if (principal != null)
{
foreach (var identity in principal.Identities)
{
xml.Add(identity.Claims.Select(claim =>
new XElement("claim", new XAttribute("type", claim.Type),
new XAttribute("value", claim.Value),
new XAttribute("issuer", claim.Issuer))));
}
}
var xmlBytes = Encoding.UTF8.GetBytes(xml.ToString());
res.Body.Write(xmlBytes, 0, xmlBytes.Length);
}
} }
} }

View File

@ -353,6 +353,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask); context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
// First write sends headers // First write sends headers
context.AllowSynchronousIO = true;
context.Response.Body.Write(new byte[10], 0, 10); context.Response.Body.Write(new byte[10], 0, 10);
var response = await responseTask; var response = await responseTask;

View File

@ -307,7 +307,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; httpContext.Response.Headers["Cache-Control"] = "public, max-age=10";
httpContext.Response.ContentLength = 10; httpContext.Response.ContentLength = 10;
httpContext.Response.Body.Flush(); httpContext.Response.Body.FlushAsync();
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
})) }))
{ {

View File

@ -82,6 +82,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_options = options; _options = options;
_server = server; _server = server;
_logger = logger; _logger = logger;
((IHttpBodyControlFeature)this).AllowSynchronousIO = _options.AllowSynchronousIO;
} }
public Version HttpVersion { get; set; } public Version HttpVersion { get; set; }

View File

@ -1,10 +1,20 @@
// 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 Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Builder namespace Microsoft.AspNetCore.Builder
{ {
public class IISServerOptions public class IISServerOptions
{ {
/// <summary>
/// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/>
/// </summary>
/// <remarks>
/// Defaults to true.
/// </remarks>
public bool AllowSynchronousIO { get; set; } = true;
/// <summary> /// <summary>
/// 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
/// identity when explicitly requested by the AuthenticationScheme. /// identity when explicitly requested by the AuthenticationScheme.

View File

@ -81,10 +81,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip")); client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("identity", 0)); client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("identity", 0));
client.DefaultRequestHeaders.Add("Response-Content-Type", "text/event-stream"); client.DefaultRequestHeaders.Add("Response-Content-Type", "text/event-stream");
var messages = "Message1\r\nMessage2\r\n"; var messages = "Message1\r\nMessage2\r\n\r\n";
// Send messages with terminator // Send messages with terminator
var response = await client.PostAsync("ReadAndWriteEchoLines", new StringContent(messages + "\r\n")); var response = await client.PostAsync("ReadAndWriteEchoLines", new StringContent(messages));
Assert.Equal(messages, await response.Content.ReadAsStringAsync()); Assert.Equal(messages, await response.Content.ReadAsStringAsync());
Assert.True(response.Content.Headers.TryGetValues("Content-Type", out var contentTypes)); Assert.True(response.Content.Headers.TryGetValues("Content-Type", out var contentTypes));
Assert.Single(contentTypes, "text/event-stream"); Assert.Single(contentTypes, "text/event-stream");

View File

@ -331,10 +331,10 @@ namespace TestSite
await ctx.Response.Body.FlushAsync(); await ctx.Response.Body.FlushAsync();
var reader = new StreamReader(ctx.Request.Body); var reader = new StreamReader(ctx.Request.Body);
while (!reader.EndOfStream) while (true)
{ {
var line = await reader.ReadLineAsync(); var line = await reader.ReadLineAsync();
if (line == "") if (line == null)
{ {
return; return;
} }
@ -357,10 +357,10 @@ namespace TestSite
await ctx.Response.Body.FlushAsync(); await ctx.Response.Body.FlushAsync();
var reader = new StreamReader(ctx.Request.Body); var reader = new StreamReader(ctx.Request.Body);
while (!reader.EndOfStream) while (true)
{ {
var line = await reader.ReadLineAsync(); var line = await reader.ReadLineAsync();
if (line == "") if (line == null)
{ {
return; return;
} }
@ -438,8 +438,8 @@ namespace TestSite
private async Task TestReadOffsetWorks(HttpContext ctx) private async Task TestReadOffsetWorks(HttpContext ctx)
{ {
var buffer = new byte[11]; var buffer = new byte[11];
ctx.Request.Body.Read(buffer, 0, 6); await ctx.Request.Body.ReadAsync(buffer, 0, 6);
ctx.Request.Body.Read(buffer, 6, 5); await ctx.Request.Body.ReadAsync(buffer, 6, 5);
await ctx.Response.WriteAsync(Encoding.UTF8.GetString(buffer)); await ctx.Response.WriteAsync(Encoding.UTF8.GetString(buffer));
} }

View File

@ -1933,6 +1933,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await InitializeConnectionAsync(async context => await InitializeConnectionAsync(async context =>
{ {
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
bodyControlFeature.AllowSynchronousIO = true;
// Fill the flow control window to create async back pressure. // Fill the flow control window to create async back pressure.
await context.Response.Body.WriteAsync(new byte[windowSize + 1], 0, windowSize + 1); await context.Response.Body.WriteAsync(new byte[windowSize + 1], 0, windowSize + 1);
context.Response.Body.Write(new byte[1], 0, 1); context.Response.Body.Write(new byte[1], 0, 1);
@ -2084,4 +2086,4 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
} }
} }
} }

View File

@ -66,9 +66,13 @@ namespace Microsoft.Extensions.RazorViews
Context = context; Context = context;
Request = Context.Request; Request = Context.Request;
Response = Context.Response; Response = Context.Response;
Output = new StreamWriter(Response.Body, UTF8NoBOM, 4096, leaveOpen: true); var buffer = new MemoryStream();
Output = new StreamWriter(buffer, UTF8NoBOM, 4096, leaveOpen: true);
await ExecuteAsync(); await ExecuteAsync();
await Output.FlushAsync();
Output.Dispose(); Output.Dispose();
buffer.Seek(0, SeekOrigin.Begin);
await buffer.CopyToAsync(Response.Body);
} }
/// <summary> /// <summary>
@ -276,4 +280,4 @@ namespace Microsoft.Extensions.RazorViews
.Select(HtmlEncoder.Encode)); .Select(HtmlEncoder.Encode));
} }
} }
} }