Add AllowSynchronousIO to TestServer and IIS, fix tests (#6404)
This commit is contained in:
parent
0defbf74f8
commit
1f892d798d
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,8 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
_pathBase = pathBase;
|
||||
}
|
||||
|
||||
internal bool AllowSynchronousIO { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This adapts HttpRequestMessages to ASP.NET Core requests, dispatches them through the pipeline, and returns the
|
||||
/// associated HttpResponseMessage.
|
||||
|
|
@ -59,7 +61,7 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
var contextBuilder = new HttpContextBuilder(_application);
|
||||
var contextBuilder = new HttpContextBuilder(_application, AllowSynchronousIO);
|
||||
|
||||
Stream responseBody = 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.
|
||||
body.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
req.Body = body;
|
||||
req.Body = new AsyncStreamWrapper(body, () => contextBuilder.AllowSynchronousIO);
|
||||
|
||||
responseBody = context.Response.Body;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -11,7 +11,7 @@ using static Microsoft.AspNetCore.Hosting.Internal.HostingApplication;
|
|||
|
||||
namespace Microsoft.AspNetCore.TestHost
|
||||
{
|
||||
internal class HttpContextBuilder
|
||||
internal class HttpContextBuilder : IHttpBodyControlFeature
|
||||
{
|
||||
private readonly IHttpApplication<Context> _application;
|
||||
private readonly HttpContext _httpContext;
|
||||
|
|
@ -23,24 +23,28 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
private bool _pipelineFinished;
|
||||
private Context _testContext;
|
||||
|
||||
internal HttpContextBuilder(IHttpApplication<Context> application)
|
||||
internal HttpContextBuilder(IHttpApplication<Context> application, bool allowSynchronousIO)
|
||||
{
|
||||
_application = application ?? throw new ArgumentNullException(nameof(application));
|
||||
AllowSynchronousIO = allowSynchronousIO;
|
||||
_httpContext = new DefaultHttpContext();
|
||||
|
||||
var request = _httpContext.Request;
|
||||
request.Protocol = "HTTP/1.1";
|
||||
request.Method = HttpMethods.Get;
|
||||
|
||||
_httpContext.Features.Set<IHttpBodyControlFeature>(this);
|
||||
_httpContext.Features.Set<IHttpResponseFeature>(_responseFeature);
|
||||
var requestLifetimeFeature = new HttpRequestLifetimeFeature();
|
||||
requestLifetimeFeature.RequestAborted = _requestAbortedSource.Token;
|
||||
_httpContext.Features.Set<IHttpRequestLifetimeFeature>(requestLifetimeFeature);
|
||||
|
||||
_responseStream = new ResponseStream(ReturnResponseMessageAsync, AbortRequest);
|
||||
_responseStream = new ResponseStream(ReturnResponseMessageAsync, AbortRequest, () => AllowSynchronousIO);
|
||||
_responseFeature.Body = _responseStream;
|
||||
}
|
||||
|
||||
public bool AllowSynchronousIO { get; set; }
|
||||
|
||||
internal void Configure(Action<HttpContext> configureContext)
|
||||
{
|
||||
if (configureContext == null)
|
||||
|
|
@ -136,4 +140,4 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
_responseTcs.TrySetException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,13 +24,15 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
private Func<Task> _onFirstWriteAsync;
|
||||
private bool _firstWrite;
|
||||
private Action _abortRequest;
|
||||
private Func<bool> _allowSynchronousIO;
|
||||
|
||||
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));
|
||||
_abortRequest = abortRequest ?? throw new ArgumentNullException(nameof(abortRequest));
|
||||
_allowSynchronousIO = allowSynchronousIO ?? throw new ArgumentNullException(nameof(allowSynchronousIO));
|
||||
_firstWrite = true;
|
||||
_writeLock = new SemaphoreSlim(1, 1);
|
||||
}
|
||||
|
|
@ -144,6 +146,11 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
// Write with count 0 will still trigger OnFirstWrite
|
||||
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.
|
||||
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,14 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
|
||||
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
|
||||
{
|
||||
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()
|
||||
{
|
||||
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
|
||||
return new ClientHandler(pathBase, Application);
|
||||
return new ClientHandler(pathBase, Application) { AllowSynchronousIO = AllowSynchronousIO };
|
||||
}
|
||||
|
||||
public HttpClient CreateClient()
|
||||
|
|
@ -96,7 +104,7 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
public WebSocketClient CreateWebSocketClient()
|
||||
{
|
||||
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
|
||||
return new WebSocketClient(pathBase, Application);
|
||||
return new WebSocketClient(pathBase, Application) { AllowSynchronousIO = AllowSynchronousIO };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -120,7 +128,7 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
throw new ArgumentNullException(nameof(configureContext));
|
||||
}
|
||||
|
||||
var builder = new HttpContextBuilder(Application);
|
||||
var builder = new HttpContextBuilder(Application, AllowSynchronousIO);
|
||||
builder.Configure(context =>
|
||||
{
|
||||
var request = context.Request;
|
||||
|
|
@ -138,6 +146,7 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
request.PathBase = pathBase;
|
||||
});
|
||||
builder.Configure(configureContext);
|
||||
// TODO: Wrap the request body if any?
|
||||
return await builder.SendAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,10 +46,12 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
set;
|
||||
}
|
||||
|
||||
internal bool AllowSynchronousIO { get; set; }
|
||||
|
||||
public async Task<WebSocket> ConnectAsync(Uri uri, CancellationToken cancellationToken)
|
||||
{
|
||||
WebSocketFeature webSocketFeature = null;
|
||||
var contextBuilder = new HttpContextBuilder(_application);
|
||||
var contextBuilder = new HttpContextBuilder(_application, AllowSynchronousIO);
|
||||
contextBuilder.Configure(context =>
|
||||
{
|
||||
var request = context.Request;
|
||||
|
|
@ -131,4 +133,4 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,13 +92,12 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
public async Task ResubmitRequestWorks()
|
||||
{
|
||||
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);
|
||||
|
||||
context.Response.Headers["TestHeader"] = "TestValue:" + requestCount++;
|
||||
return Task.FromResult(0);
|
||||
}));
|
||||
|
||||
HttpMessageInvoker invoker = new HttpMessageInvoker(handler);
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
{
|
||||
c.Response.Headers["TestHeader"] = "TestValue";
|
||||
var bytes = Encoding.UTF8.GetBytes("BodyStarted" + Environment.NewLine);
|
||||
c.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
|
||||
c.Response.Body.Write(bytes, 0, bytes.Length);
|
||||
await block.Task;
|
||||
bytes = Encoding.UTF8.GetBytes("BodyFinished");
|
||||
|
|
|
|||
|
|
@ -87,8 +87,8 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
public async Task PutAsyncWorks()
|
||||
{
|
||||
// Arrange
|
||||
RequestDelegate appDelegate = ctx =>
|
||||
ctx.Response.WriteAsync(new StreamReader(ctx.Request.Body).ReadToEnd() + " PUT Response");
|
||||
RequestDelegate appDelegate = async ctx =>
|
||||
await ctx.Response.WriteAsync(await new StreamReader(ctx.Request.Body).ReadToEndAsync() + " PUT Response");
|
||||
var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate));
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
|
|
@ -106,7 +106,7 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
{
|
||||
// Arrange
|
||||
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 server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
|
|
@ -132,16 +132,15 @@ namespace Microsoft.AspNetCore.TestHost
|
|||
}
|
||||
|
||||
var builder = new WebHostBuilder();
|
||||
RequestDelegate app = (ctx) =>
|
||||
RequestDelegate app = async ctx =>
|
||||
{
|
||||
var disposable = new TestDisposable();
|
||||
ctx.Response.RegisterForDispose(disposable);
|
||||
ctx.Response.Body.Write(data, 0, 1024);
|
||||
await ctx.Response.Body.WriteAsync(data, 0, 1024);
|
||||
|
||||
Assert.False(disposable.IsDisposed);
|
||||
|
||||
ctx.Response.Body.Write(data, 1024, 1024);
|
||||
return Task.FromResult(0);
|
||||
await ctx.Response.Body.WriteAsync(data, 1024, 1024);
|
||||
};
|
||||
|
||||
builder.Configure(appBuilder => appBuilder.Run(app));
|
||||
|
|
|
|||
|
|
@ -352,12 +352,12 @@ namespace Microsoft.AspNetCore.Identity.InMemory
|
|||
}
|
||||
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))
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
@ -393,7 +393,7 @@ namespace Microsoft.AspNetCore.Identity.InMemory
|
|||
return server;
|
||||
}
|
||||
|
||||
private static void Describe(HttpResponse res, AuthenticateResult result)
|
||||
private static async Task DescribeAsync(HttpResponse res, AuthenticateResult result)
|
||||
{
|
||||
res.StatusCode = 200;
|
||||
res.ContentType = "text/xml";
|
||||
|
|
@ -412,7 +412,7 @@ namespace Microsoft.AspNetCore.Identity.InMemory
|
|||
{
|
||||
xml.WriteTo(writer);
|
||||
}
|
||||
res.Body.Write(memory.ToArray(), 0, memory.ToArray().Length);
|
||||
await res.Body.WriteAsync(memory.ToArray(), 0, memory.ToArray().Length);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,7 @@ namespace SampleDestination
|
|||
|
||||
context.Response.ContentType = "text/plain; charset=utf-8";
|
||||
context.Response.ContentLength = content.Length;
|
||||
context.Response.Body.Write(content, 0, content.Length);
|
||||
|
||||
return Task.CompletedTask;
|
||||
return context.Response.Body.WriteAsync(content, 0, content.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,9 +86,7 @@ namespace SampleDestination
|
|||
|
||||
context.Response.ContentType = "text/plain; charset=utf-8";
|
||||
context.Response.ContentLength = content.Length;
|
||||
context.Response.Body.Write(content, 0, content.Length);
|
||||
|
||||
return Task.CompletedTask;
|
||||
return context.Response.Body.WriteAsync(content, 0, content.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -81,6 +81,11 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
var uniqueId = Guid.NewGuid().ToString();
|
||||
if (TestRequestDelegate(context, uniqueId))
|
||||
{
|
||||
var feature = context.Features.Get<IHttpBodyControlFeature>();
|
||||
if (feature != null)
|
||||
{
|
||||
feature.AllowSynchronousIO = true;
|
||||
}
|
||||
context.Response.Write(uniqueId);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
|
|
|
|||
|
|
@ -548,6 +548,12 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
|
|||
app.UseResponseCompression();
|
||||
app.Run(context =>
|
||||
{
|
||||
var feature = context.Features.Get<IHttpBodyControlFeature>();
|
||||
if (feature != null)
|
||||
{
|
||||
feature.AllowSynchronousIO = true;
|
||||
}
|
||||
|
||||
context.Response.Headers[HeaderNames.ContentMD5] = "MD5";
|
||||
context.Response.ContentType = TextPlain;
|
||||
context.Response.Body.Write(new byte[10], 0, 10);
|
||||
|
|
@ -652,6 +658,12 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
|
|||
context.Response.ContentType = TextPlain;
|
||||
context.Features.Get<IHttpBufferingFeature>()?.DisableResponseBuffering();
|
||||
|
||||
var feature = context.Features.Get<IHttpBodyControlFeature>();
|
||||
if (feature != null)
|
||||
{
|
||||
feature.AllowSynchronousIO = true;
|
||||
}
|
||||
|
||||
foreach (var signal in responseReceived)
|
||||
{
|
||||
context.Response.Body.Write(new byte[1], 0, 1);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Text;
|
||||
|
|
@ -31,6 +31,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
|
|||
|
||||
if (!string.IsNullOrEmpty(StatusDescription))
|
||||
{
|
||||
var feature = context.HttpContext.Features.Get<IHttpBodyControlFeature>();
|
||||
if (feature != null)
|
||||
{
|
||||
feature.AllowSynchronousIO = true;
|
||||
}
|
||||
var content = Encoding.UTF8.GetBytes(StatusDescription);
|
||||
response.ContentLength = content.Length;
|
||||
response.ContentType = "text/plain; charset=utf-8";
|
||||
|
|
@ -42,4 +47,4 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
|
|||
context.Logger?.CustomResponse(context.HttpContext.Request.GetEncodedUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1302,7 +1302,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
|
|||
app.Use(async (context, next) =>
|
||||
{
|
||||
var result = await context.AuthenticateAsync("Cookies");
|
||||
Describe(context.Response, result);
|
||||
await DescribeAsync(context.Response, result);
|
||||
});
|
||||
})
|
||||
.ConfigureServices(services => services.AddAuthentication().AddCookie("Cookies", o =>
|
||||
|
|
@ -1478,12 +1478,12 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
|
|||
}
|
||||
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))
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
@ -1510,7 +1510,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
|
|||
return server;
|
||||
}
|
||||
|
||||
private static void Describe(HttpResponse res, AuthenticateResult result)
|
||||
private static Task DescribeAsync(HttpResponse res, AuthenticateResult result)
|
||||
{
|
||||
res.StatusCode = 200;
|
||||
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))));
|
||||
}
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
{
|
||||
var name = (remainder.Value.Length > 0) ? remainder.Value.Substring(1) : null;
|
||||
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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1177,26 +1177,26 @@ namespace Microsoft.AspNetCore.Authentication.Google
|
|||
{
|
||||
var result = await context.AuthenticateAsync(TestExtensions.CookieAuthenticationScheme);
|
||||
var tokens = result.Properties.GetTokens();
|
||||
res.Describe(tokens);
|
||||
await res.DescribeAsync(tokens);
|
||||
}
|
||||
else if (req.Path == new PathString("/me"))
|
||||
{
|
||||
res.Describe(context.User);
|
||||
await res.DescribeAsync(context.User);
|
||||
}
|
||||
else if (req.Path == new PathString("/authenticate"))
|
||||
{
|
||||
var result = await context.AuthenticateAsync(TestExtensions.CookieAuthenticationScheme);
|
||||
res.Describe(result.Principal);
|
||||
await res.DescribeAsync(result.Principal);
|
||||
}
|
||||
else if (req.Path == new PathString("/authenticateGoogle"))
|
||||
{
|
||||
var result = await context.AuthenticateAsync("Google");
|
||||
res.Describe(result?.Principal);
|
||||
await res.DescribeAsync(result?.Principal);
|
||||
}
|
||||
else if (req.Path == new PathString("/authenticateFacebook"))
|
||||
{
|
||||
var result = await context.AuthenticateAsync("Facebook");
|
||||
res.Describe(result?.Principal);
|
||||
await res.DescribeAsync(result?.Principal);
|
||||
}
|
||||
else if (req.Path == new PathString("/unauthorized"))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount
|
|||
}
|
||||
else if (req.Path == new PathString("/me"))
|
||||
{
|
||||
res.Describe(context.User);
|
||||
await res.DescribeAsync(context.User);
|
||||
}
|
||||
else if (req.Path == new PathString("/signIn"))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -469,7 +469,7 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
{
|
||||
var name = (remainder.Value.Length > 0) ? remainder.Value.Substring(1) : null;
|
||||
var result = await context.AuthenticateAsync(name);
|
||||
res.Describe(result?.Ticket?.Principal);
|
||||
await res.DescribeAsync(result?.Ticket?.Principal);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
return transaction;
|
||||
}
|
||||
|
||||
public static void Describe(this HttpResponse res, ClaimsPrincipal principal)
|
||||
public static Task DescribeAsync(this HttpResponse res, ClaimsPrincipal principal)
|
||||
{
|
||||
res.StatusCode = 200;
|
||||
res.ContentType = "text/xml";
|
||||
|
|
@ -62,10 +62,10 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
}
|
||||
}
|
||||
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.ContentType = "text/xml";
|
||||
|
|
@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Authentication
|
|||
}
|
||||
}
|
||||
var xmlBytes = Encoding.UTF8.GetBytes(xml.ToString());
|
||||
res.Body.Write(xmlBytes, 0, xmlBytes.Length);
|
||||
return res.Body.WriteAsync(xmlBytes, 0, xmlBytes.Length);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,24 +45,5 @@ namespace Microsoft.AspNetCore.CookiePolicy
|
|||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -353,6 +353,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
|
|||
|
||||
context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
|
||||
// First write sends headers
|
||||
context.AllowSynchronousIO = true;
|
||||
context.Response.Body.Write(new byte[10], 0, 10);
|
||||
|
||||
var response = await responseTask;
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
|
|||
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
|
||||
httpContext.Response.Headers["Cache-Control"] = "public, max-age=10";
|
||||
httpContext.Response.ContentLength = 10;
|
||||
httpContext.Response.Body.Flush();
|
||||
httpContext.Response.Body.FlushAsync();
|
||||
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
|
||||
}))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
|||
_options = options;
|
||||
_server = server;
|
||||
_logger = logger;
|
||||
|
||||
((IHttpBodyControlFeature)this).AllowSynchronousIO = _options.AllowSynchronousIO;
|
||||
}
|
||||
|
||||
public Version HttpVersion { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
// 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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
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>
|
||||
/// If true the server should set HttpContext.User. If false the server will only provide an
|
||||
/// identity when explicitly requested by the AuthenticationScheme.
|
||||
|
|
|
|||
|
|
@ -81,10 +81,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
|
||||
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("identity", 0));
|
||||
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
|
||||
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.True(response.Content.Headers.TryGetValues("Content-Type", out var contentTypes));
|
||||
Assert.Single(contentTypes, "text/event-stream");
|
||||
|
|
|
|||
|
|
@ -331,10 +331,10 @@ namespace TestSite
|
|||
await ctx.Response.Body.FlushAsync();
|
||||
|
||||
var reader = new StreamReader(ctx.Request.Body);
|
||||
while (!reader.EndOfStream)
|
||||
while (true)
|
||||
{
|
||||
var line = await reader.ReadLineAsync();
|
||||
if (line == "")
|
||||
if (line == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -357,10 +357,10 @@ namespace TestSite
|
|||
await ctx.Response.Body.FlushAsync();
|
||||
|
||||
var reader = new StreamReader(ctx.Request.Body);
|
||||
while (!reader.EndOfStream)
|
||||
while (true)
|
||||
{
|
||||
var line = await reader.ReadLineAsync();
|
||||
if (line == "")
|
||||
if (line == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -438,8 +438,8 @@ namespace TestSite
|
|||
private async Task TestReadOffsetWorks(HttpContext ctx)
|
||||
{
|
||||
var buffer = new byte[11];
|
||||
ctx.Request.Body.Read(buffer, 0, 6);
|
||||
ctx.Request.Body.Read(buffer, 6, 5);
|
||||
await ctx.Request.Body.ReadAsync(buffer, 0, 6);
|
||||
await ctx.Request.Body.ReadAsync(buffer, 6, 5);
|
||||
|
||||
await ctx.Response.WriteAsync(Encoding.UTF8.GetString(buffer));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1933,6 +1933,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await InitializeConnectionAsync(async context =>
|
||||
{
|
||||
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
|
||||
bodyControlFeature.AllowSynchronousIO = true;
|
||||
// Fill the flow control window to create async back pressure.
|
||||
await context.Response.Body.WriteAsync(new byte[windowSize + 1], 0, windowSize + 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,9 +66,13 @@ namespace Microsoft.Extensions.RazorViews
|
|||
Context = context;
|
||||
Request = Context.Request;
|
||||
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 Output.FlushAsync();
|
||||
Output.Dispose();
|
||||
buffer.Seek(0, SeekOrigin.Begin);
|
||||
await buffer.CopyToAsync(Response.Body);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -276,4 +280,4 @@ namespace Microsoft.Extensions.RazorViews
|
|||
.Select(HtmlEncoder.Encode));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue