API Review for Http.Connections (#2010)

This commit is contained in:
Andrew Stanton-Nurse 2018-04-13 16:35:07 -07:00 committed by GitHub
parent d0137a996c
commit c7f7f36210
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 193 additions and 111 deletions

View File

@ -3,6 +3,7 @@
using System;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Http.Connections.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Http.Connections.Internal;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.Extensions.DependencyInjection

View File

@ -6,6 +6,7 @@ using System.Reflection;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Internal;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Http.Connections
@ -21,13 +22,10 @@ namespace Microsoft.AspNetCore.Http.Connections
_dispatcher = dispatcher;
}
public void MapConnections(string path, Action<IConnectionBuilder> configure) =>
MapConnections(new PathString(path), new HttpConnectionOptions(), configure);
public void MapConnections(PathString path, Action<IConnectionBuilder> configure) =>
MapConnections(path, new HttpConnectionOptions(), configure);
MapConnections(path, new HttpConnectionDispatcherOptions(), configure);
public void MapConnections(PathString path, HttpConnectionOptions options, Action<IConnectionBuilder> configure)
public void MapConnections(PathString path, HttpConnectionDispatcherOptions options, Action<IConnectionBuilder> configure)
{
var connectionBuilder = new ConnectionBuilder(_routes.ServiceProvider);
configure(connectionBuilder);
@ -36,20 +34,15 @@ namespace Microsoft.AspNetCore.Http.Connections
_routes.MapRoute(path + "/negotiate", c => _dispatcher.ExecuteNegotiateAsync(c, options));
}
public void MapConnectionHandler<TConnectionHandler>(string path) where TConnectionHandler : ConnectionHandler
{
MapConnectionHandler<TConnectionHandler>(new PathString(path), configureOptions: null);
}
public void MapConnectionHandler<TConnectionHandler>(PathString path) where TConnectionHandler : ConnectionHandler
{
MapConnectionHandler<TConnectionHandler>(path, configureOptions: null);
}
public void MapConnectionHandler<TConnectionHandler>(PathString path, Action<HttpConnectionOptions> configureOptions) where TConnectionHandler : ConnectionHandler
public void MapConnectionHandler<TConnectionHandler>(PathString path, Action<HttpConnectionDispatcherOptions> configureOptions) where TConnectionHandler : ConnectionHandler
{
var authorizeAttributes = typeof(TConnectionHandler).GetCustomAttributes<AuthorizeAttribute>(inherit: true);
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
foreach (var attribute in authorizeAttributes)
{
options.AuthorizationData.Add(attribute);

View File

@ -7,13 +7,13 @@ using Microsoft.AspNetCore.Http.Connections.Internal;
namespace Microsoft.AspNetCore.Http.Connections
{
public class HttpConnectionOptions
public class HttpConnectionDispatcherOptions
{
// Selected because this is the default value of PipeWriter.PauseWriterThreshold.
// There maybe the opportunity for performance gains by tuning this default.
private const int DefaultPipeBufferSize = 32768;
public HttpConnectionOptions()
public HttpConnectionDispatcherOptions()
{
AuthorizationData = new List<IAuthorizeData>();
Transports = HttpTransports.All;

View File

@ -8,14 +8,13 @@ using System.IO.Pipelines;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Http.Connections
namespace Microsoft.AspNetCore.Http.Connections.Internal
{
public class HttpConnectionContext : ConnectionContext,
IConnectionIdFeature,
@ -88,7 +87,7 @@ namespace Microsoft.AspNetCore.Http.Connections
public DateTime LastSeenUtc { get; set; }
public ConnectionStatus Status { get; set; } = ConnectionStatus.Inactive;
public HttpConnectionStatus Status { get; set; } = HttpConnectionStatus.Inactive;
public override string ConnectionId { get; set; }
@ -161,13 +160,13 @@ namespace Microsoft.AspNetCore.Http.Connections
{
await Lock.WaitAsync();
if (Status == ConnectionStatus.Disposed)
if (Status == HttpConnectionStatus.Disposed)
{
disposeTask = _disposeTcs.Task;
}
else
{
Status = ConnectionStatus.Disposed;
Status = HttpConnectionStatus.Disposed;
Log.DisposingConnection(_logger, ConnectionId);
@ -282,13 +281,6 @@ namespace Microsoft.AspNetCore.Http.Connections
}
}
public enum ConnectionStatus
{
Inactive,
Active,
Disposed
}
private static class Log
{
private static readonly Action<ILogger, string, Exception> _disposingConnection =

View File

@ -4,7 +4,7 @@
using System;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Http.Connections
namespace Microsoft.AspNetCore.Http.Connections.Internal
{
public partial class HttpConnectionDispatcher
{

View File

@ -12,14 +12,13 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.Http.Connections.Internal;
using Microsoft.AspNetCore.Http.Connections.Internal.Transports;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Http.Connections
namespace Microsoft.AspNetCore.Http.Connections.Internal
{
public partial class HttpConnectionDispatcher
{
@ -55,7 +54,7 @@ namespace Microsoft.AspNetCore.Http.Connections
_logger = _loggerFactory.CreateLogger<HttpConnectionDispatcher>();
}
public async Task ExecuteAsync(HttpContext context, HttpConnectionOptions options, ConnectionDelegate connectionDelegate)
public async Task ExecuteAsync(HttpContext context, HttpConnectionDispatcherOptions options, ConnectionDelegate connectionDelegate)
{
// Create the log scope and attempt to pass the Connection ID to it so as many logs as possible contain
// the Connection ID metadata. If this is the negotiate request then the Connection ID for the scope will
@ -91,7 +90,7 @@ namespace Microsoft.AspNetCore.Http.Connections
}
}
public async Task ExecuteNegotiateAsync(HttpContext context, HttpConnectionOptions options)
public async Task ExecuteNegotiateAsync(HttpContext context, HttpConnectionDispatcherOptions options)
{
// Create the log scope and the scope connectionId param will be set when the connection is created.
var logScope = new ConnectionLogScope(connectionId: string.Empty);
@ -115,7 +114,7 @@ namespace Microsoft.AspNetCore.Http.Connections
}
}
private async Task ExecuteAsync(HttpContext context, ConnectionDelegate connectionDelegate, HttpConnectionOptions options, ConnectionLogScope logScope)
private async Task ExecuteAsync(HttpContext context, ConnectionDelegate connectionDelegate, HttpConnectionDispatcherOptions options, ConnectionLogScope logScope)
{
var supportedTransports = options.Transports;
@ -193,7 +192,7 @@ namespace Microsoft.AspNetCore.Http.Connections
{
await connection.Lock.WaitAsync();
if (connection.Status == HttpConnectionContext.ConnectionStatus.Disposed)
if (connection.Status == HttpConnectionStatus.Disposed)
{
Log.ConnectionDisposed(_logger, connection.ConnectionId);
@ -203,7 +202,7 @@ namespace Microsoft.AspNetCore.Http.Connections
return;
}
if (connection.Status == HttpConnectionContext.ConnectionStatus.Active)
if (connection.Status == HttpConnectionStatus.Active)
{
var existing = connection.GetHttpContext();
Log.ConnectionAlreadyActive(_logger, connection.ConnectionId, existing.TraceIdentifier);
@ -221,7 +220,7 @@ namespace Microsoft.AspNetCore.Http.Connections
}
// Mark the connection as active
connection.Status = HttpConnectionContext.ConnectionStatus.Active;
connection.Status = HttpConnectionStatus.Active;
// Raise OnConnected for new connections only since polls happen all the time
if (connection.ApplicationTask == null)
@ -295,12 +294,12 @@ namespace Microsoft.AspNetCore.Http.Connections
{
await connection.Lock.WaitAsync();
if (connection.Status == HttpConnectionContext.ConnectionStatus.Active)
if (connection.Status == HttpConnectionStatus.Active)
{
// Mark the connection as inactive
connection.LastSeenUtc = DateTime.UtcNow;
connection.Status = HttpConnectionContext.ConnectionStatus.Inactive;
connection.Status = HttpConnectionStatus.Inactive;
// Dispose the cancellation token
connection.Cancellation.Dispose();
@ -325,7 +324,7 @@ namespace Microsoft.AspNetCore.Http.Connections
{
await connection.Lock.WaitAsync();
if (connection.Status == HttpConnectionContext.ConnectionStatus.Disposed)
if (connection.Status == HttpConnectionStatus.Disposed)
{
Log.ConnectionDisposed(_logger, connection.ConnectionId);
@ -335,7 +334,7 @@ namespace Microsoft.AspNetCore.Http.Connections
}
// There's already an active request
if (connection.Status == HttpConnectionContext.ConnectionStatus.Active)
if (connection.Status == HttpConnectionStatus.Active)
{
Log.ConnectionAlreadyActive(_logger, connection.ConnectionId, connection.GetHttpContext().TraceIdentifier);
@ -345,7 +344,7 @@ namespace Microsoft.AspNetCore.Http.Connections
}
// Mark the connection as active
connection.Status = HttpConnectionContext.ConnectionStatus.Active;
connection.Status = HttpConnectionStatus.Active;
// Call into the end point passing the connection
connection.ApplicationTask = ExecuteApplication(connectionDelegate, connection);
@ -380,7 +379,7 @@ namespace Microsoft.AspNetCore.Http.Connections
await connectionDelegate(connection);
}
private async Task ProcessNegotiate(HttpContext context, HttpConnectionOptions options, ConnectionLogScope logScope)
private async Task ProcessNegotiate(HttpContext context, HttpConnectionDispatcherOptions options, ConnectionLogScope logScope)
{
context.Response.ContentType = "application/json";
@ -411,7 +410,7 @@ namespace Microsoft.AspNetCore.Http.Connections
}
}
private static void WriteNegotiatePayload(IBufferWriter<byte> writer, string connectionId, HttpContext context, HttpConnectionOptions options)
private static void WriteNegotiatePayload(IBufferWriter<byte> writer, string connectionId, HttpContext context, HttpConnectionDispatcherOptions options)
{
var response = new NegotiationResponse();
response.ConnectionId = connectionId;
@ -442,7 +441,7 @@ namespace Microsoft.AspNetCore.Http.Connections
private static string GetConnectionId(HttpContext context) => context.Request.Query["id"];
private async Task ProcessSend(HttpContext context, HttpConnectionOptions options)
private async Task ProcessSend(HttpContext context, HttpConnectionDispatcherOptions options)
{
var connection = await GetConnectionAsync(context);
if (connection == null)
@ -469,7 +468,7 @@ namespace Microsoft.AspNetCore.Http.Connections
try
{
if (connection.Status == HttpConnectionContext.ConnectionStatus.Disposed)
if (connection.Status == HttpConnectionStatus.Disposed)
{
Log.ConnectionDisposed(_logger, connection.ConnectionId);
@ -523,7 +522,7 @@ namespace Microsoft.AspNetCore.Http.Connections
context.Response.ContentType = "text/plain";
}
private async Task<bool> EnsureConnectionStateAsync(HttpConnectionContext connection, HttpContext context, HttpTransportType transportType, HttpTransportType supportedTransports, ConnectionLogScope logScope, HttpConnectionOptions options)
private async Task<bool> EnsureConnectionStateAsync(HttpConnectionContext connection, HttpContext context, HttpTransportType transportType, HttpTransportType supportedTransports, ConnectionLogScope logScope, HttpConnectionDispatcherOptions options)
{
if ((supportedTransports & transportType) == 0)
{
@ -672,7 +671,7 @@ namespace Microsoft.AspNetCore.Http.Connections
}
// This is only used for WebSockets connections, which can connect directly without negotiating
private async Task<HttpConnectionContext> GetOrCreateConnectionAsync(HttpContext context, HttpConnectionOptions options)
private async Task<HttpConnectionContext> GetOrCreateConnectionAsync(HttpContext context, HttpConnectionDispatcherOptions options)
{
var connectionId = GetConnectionId(context);
HttpConnectionContext connection;
@ -693,7 +692,7 @@ namespace Microsoft.AspNetCore.Http.Connections
return connection;
}
private HttpConnectionContext CreateConnection(HttpConnectionOptions options)
private HttpConnectionContext CreateConnection(HttpConnectionDispatcherOptions options)
{
var transportPipeOptions = new PipeOptions(pauseWriterThreshold: options.TransportMaxBufferSize, resumeWriterThreshold: options.TransportMaxBufferSize / 2, readerScheduler: PipeScheduler.ThreadPool, useSynchronizationContext: false);
var appPipeOptions = new PipeOptions(pauseWriterThreshold: options.ApplicationMaxBufferSize, resumeWriterThreshold: options.ApplicationMaxBufferSize / 2, readerScheduler: PipeScheduler.ThreadPool, useSynchronizationContext: false);

View File

@ -4,7 +4,7 @@
using System;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Http.Connections
namespace Microsoft.AspNetCore.Http.Connections.Internal
{
public partial class HttpConnectionManager
{

View File

@ -13,11 +13,10 @@ using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Connections.Internal;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Http.Connections
namespace Microsoft.AspNetCore.Http.Connections.Internal
{
public partial class HttpConnectionManager
{
@ -139,7 +138,7 @@ namespace Microsoft.AspNetCore.Http.Connections
// Scan the registered connections looking for ones that have timed out
foreach (var c in _connections)
{
HttpConnectionContext.ConnectionStatus status;
HttpConnectionStatus status;
DateTimeOffset lastSeenUtc;
var connection = c.Value.Connection;
@ -159,7 +158,7 @@ namespace Microsoft.AspNetCore.Http.Connections
// Once the decision has been made to dispose we don't check the status again
// But don't clean up connections while the debugger is attached.
if (!Debugger.IsAttached && status == HttpConnectionContext.ConnectionStatus.Inactive && (DateTimeOffset.UtcNow - lastSeenUtc).TotalSeconds > 5)
if (!Debugger.IsAttached && status == HttpConnectionStatus.Inactive && (DateTimeOffset.UtcNow - lastSeenUtc).TotalSeconds > 5)
{
Log.ConnectionTimedOut(_logger, connection.ConnectionId);
HttpConnectionsEventSource.Log.ConnectionTimedOut(connection.ConnectionId);

View File

@ -0,0 +1,12 @@
// 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.
namespace Microsoft.AspNetCore.Http.Connections.Internal
{
public enum HttpConnectionStatus
{
Inactive,
Active,
Disposed
}
}

View File

@ -11,8 +11,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal.Transports
{
private static class Log
{
private static readonly Action<ILogger, Exception> _socketOpened =
LoggerMessage.Define(LogLevel.Debug, new EventId(1, "SocketOpened"), "Socket opened.");
private static readonly Action<ILogger, string, Exception> _socketOpened =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(1, "SocketOpened"), "Socket opened using Sub-Protocol: '{SubProtocol}'.");
private static readonly Action<ILogger, Exception> _socketClosed =
LoggerMessage.Define(LogLevel.Debug, new EventId(2, "SocketClosed"), "Socket closed.");
@ -50,9 +50,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal.Transports
private static readonly Action<ILogger, Exception> _sendFailed =
LoggerMessage.Define(LogLevel.Error, new EventId(13, "SendFailed"), "Socket failed to send.");
public static void SocketOpened(ILogger logger)
public static void SocketOpened(ILogger logger, string subProtocol)
{
_socketOpened(logger, null);
_socketOpened(logger, subProtocol, null);
}
public static void SocketClosed(ILogger logger)

View File

@ -49,9 +49,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal.Transports
{
Debug.Assert(context.WebSockets.IsWebSocketRequest, "Not a websocket request");
using (var ws = await context.WebSockets.AcceptWebSocketAsync(_options.SubProtocol))
var subProtocol = _options.SubProtocolSelector?.Invoke(context.WebSockets.WebSocketRequestedProtocols);
using (var ws = await context.WebSockets.AcceptWebSocketAsync(subProtocol))
{
Log.SocketOpened(_logger);
Log.SocketOpened(_logger, subProtocol);
try
{

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Http.Connections
{
@ -9,6 +10,17 @@ namespace Microsoft.AspNetCore.Http.Connections
{
public TimeSpan CloseTimeout { get; set; } = TimeSpan.FromSeconds(5);
public string SubProtocol { get; set; }
/// <summary>
/// Gets or sets a delegate that will be called when a new WebSocket is established to select the value
/// for the 'Sec-WebSocket-Protocol' response header. The delegate will be called with a list of the protocols provided
/// by the client in the 'Sec-WebSocket-Protocol' request header.
/// </summary>
/// <remarks>
/// See RFC 6455 section 1.3 for more details on the WebSocket handshake: https://tools.ietf.org/html/rfc6455#section-1.3
/// </remarks>
// WebSocketManager's list of sub protocols is an IList:
// https://github.com/aspnet/HttpAbstractions/blob/a6bdb9b1ec6ed99978a508e71a7f131be7e4d9fb/src/Microsoft.AspNetCore.Http.Abstractions/WebSocketManager.cs#L23
// Unfortunately, IList<T> does not implement IReadOnlyList<T> :(
public Func<IList<string>, string> SubProtocolSelector { get; set; }
}
}

View File

@ -28,11 +28,11 @@ namespace Microsoft.AspNetCore.SignalR
MapHub<THub>(path, configureOptions: null);
}
public void MapHub<THub>(PathString path, Action<HttpConnectionOptions> configureOptions) where THub : Hub
public void MapHub<THub>(PathString path, Action<HttpConnectionDispatcherOptions> configureOptions) where THub : Hub
{
// find auth attributes
var authorizeAttributes = typeof(THub).GetCustomAttributes<AuthorizeAttribute>(inherit: true);
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
foreach (var attribute in authorizeAttributes)
{
options.AuthorizationData.Add(attribute);

View File

@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Connections.Internal;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.Extensions.DependencyInjection;
@ -51,7 +52,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
context.Request.Path = "/foo";
context.Request.Method = "POST";
context.Response.Body = ms;
await dispatcher.ExecuteNegotiateAsync(context, new HttpConnectionOptions());
await dispatcher.ExecuteNegotiateAsync(context, new HttpConnectionDispatcherOptions());
var negotiateResponse = JsonConvert.DeserializeObject<JObject>(Encoding.UTF8.GetString(ms.ToArray()));
var connectionId = negotiateResponse.Value<string>("connectionId");
Assert.True(manager.TryGetConnection(connectionId, out var connectionContext));
@ -74,7 +75,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
context.Request.Path = "/foo";
context.Request.Method = "POST";
context.Response.Body = ms;
var options = new HttpConnectionOptions { TransportMaxBufferSize = 4, ApplicationMaxBufferSize = 4 };
var options = new HttpConnectionDispatcherOptions { TransportMaxBufferSize = 4, ApplicationMaxBufferSize = 4 };
await dispatcher.ExecuteNegotiateAsync(context, options);
var negotiateResponse = JsonConvert.DeserializeObject<JObject>(Encoding.UTF8.GetString(ms.ToArray()));
var connectionId = negotiateResponse.Value<string>("connectionId");
@ -134,7 +135,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var app = builder.Build();
// This task should complete immediately but it exceeds the writer threshold
var executeTask = dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
var executeTask = dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
Assert.False(executeTask.IsCompleted);
await connection.Transport.Input.ConsumeAsync(10);
await executeTask.OrTimeout();
@ -166,7 +167,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
context.Request.Path = "/foo";
context.Request.Method = "POST";
context.Response.Body = ms;
await dispatcher.ExecuteNegotiateAsync(context, new HttpConnectionOptions { Transports = transports });
await dispatcher.ExecuteNegotiateAsync(context, new HttpConnectionDispatcherOptions { Transports = transports });
var negotiateResponse = JsonConvert.DeserializeObject<JObject>(Encoding.UTF8.GetString(ms.ToArray()));
var availableTransports = HttpTransportType.None;
@ -211,7 +212,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
await dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
Assert.Equal(StatusCodes.Status404NotFound, context.Response.StatusCode);
await strm.FlushAsync();
@ -246,7 +247,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
await dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
Assert.Equal(StatusCodes.Status404NotFound, context.Response.StatusCode);
await strm.FlushAsync();
@ -283,7 +284,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
await dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
Assert.Equal(StatusCodes.Status405MethodNotAllowed, context.Response.StatusCode);
await strm.FlushAsync();
@ -321,7 +322,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
await dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
Assert.Equal(StatusCodes.Status404NotFound, context.Response.StatusCode);
}
@ -371,7 +372,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
});
var app = builder.Build();
var task = dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
var task = dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
// Pretend the transport closed because the client disconnected
if (context.WebSockets.IsWebSocketRequest)
@ -432,7 +433,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
});
var app = builder.Build();
var task = dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
var task = dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
// Pretend the transport closed because the client disconnected
cts.Cancel();
@ -491,7 +492,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
Assert.Equal(0, connection.ApplicationStream.Length);
await dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
Assert.True(connection.Transport.Input.TryRead(out var result));
Assert.Equal("Hello World", Encoding.UTF8.GetString(result.Buffer.ToArray()));
@ -551,7 +552,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
await dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
}
}
}
@ -633,7 +634,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var app = builder.Build();
// Start a poll
var task = dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
var task = dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
// Send to the application
var buffer = Encoding.UTF8.GetBytes("Hello World");
@ -704,7 +705,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
await dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
await strm.FlushAsync();
@ -733,7 +734,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
await dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
await strm.FlushAsync();
@ -807,7 +808,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<ImmediatelyCompleteConnectionHandler>();
var app = builder.Build();
await dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
@ -834,7 +835,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<SynchronusExceptionConnectionHandler>();
var app = builder.Build();
await dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
@ -861,7 +862,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<ImmediatelyCompleteConnectionHandler>();
var app = builder.Build();
await dispatcher.ExecuteAsync(context, new HttpConnectionOptions(), app);
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
@ -888,7 +889,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
options.LongPolling.PollTimeout = TimeSpan.FromSeconds(2);
await dispatcher.ExecuteAsync(context, options, app).OrTimeout();
@ -915,7 +916,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<ImmediatelyCompleteConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
options.WebSockets.CloseTimeout = TimeSpan.FromSeconds(1);
var task = dispatcher.ExecuteAsync(context, options, app);
@ -948,7 +949,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
var request1 = dispatcher.ExecuteAsync(context1, options, app);
await dispatcher.ExecuteAsync(context2, options, app);
@ -988,14 +989,14 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
var request1 = dispatcher.ExecuteAsync(context1, options, app);
var request2 = dispatcher.ExecuteAsync(context2, options, app);
await request1;
Assert.Equal(StatusCodes.Status204NoContent, context1.Response.StatusCode);
Assert.Equal(HttpConnectionContext.ConnectionStatus.Active, connection.Status);
Assert.Equal(HttpConnectionStatus.Active, connection.Status);
Assert.False(request2.IsCompleted);
@ -1015,7 +1016,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var manager = CreateConnectionManager(loggerFactory);
var connection = manager.CreateConnection();
connection.TransportType = transportType;
connection.Status = HttpConnectionContext.ConnectionStatus.Disposed;
connection.Status = HttpConnectionStatus.Disposed;
var dispatcher = new HttpConnectionDispatcher(manager, loggerFactory);
@ -1027,7 +1028,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
await dispatcher.ExecuteAsync(context, options, app);
@ -1053,7 +1054,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
var task = dispatcher.ExecuteAsync(context, options, app);
var buffer = Encoding.UTF8.GetBytes("Hello World");
@ -1063,7 +1064,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
await task;
Assert.Equal(HttpConnectionContext.ConnectionStatus.Inactive, connection.Status);
Assert.Equal(HttpConnectionStatus.Inactive, connection.Status);
Assert.NotNull(connection.GetHttpContext());
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
@ -1089,7 +1090,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<BlockingConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
var task = dispatcher.ExecuteAsync(context, options, app);
var buffer = Encoding.UTF8.GetBytes("Hello World");
@ -1123,7 +1124,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<BlockingConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
var task = dispatcher.ExecuteAsync(context, options, app);
var buffer = Encoding.UTF8.GetBytes("Hello World");
@ -1155,7 +1156,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
var context1 = MakeRequest("/foo", connection);
var task1 = dispatcher.ExecuteAsync(context1, options, app);
@ -1201,7 +1202,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
builder.UseConnectionHandler<ImmediatelyCompleteConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
options.WebSockets.CloseTimeout = TimeSpan.FromSeconds(0);
await dispatcher.ExecuteAsync(context, options, app);
@ -1249,7 +1250,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(sp);
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
options.AuthorizationData.Add(new AuthorizeAttribute("test"));
// would get stuck if EndPoint was running
@ -1295,7 +1296,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(sp);
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
options.AuthorizationData.Add(new AuthorizeAttribute("test"));
context.User = new ClaimsPrincipal(new ClaimsIdentity("authenticated"));
@ -1348,7 +1349,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(sp);
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
options.AuthorizationData.Add(new AuthorizeAttribute("test"));
// "authorize" user
@ -1409,7 +1410,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(sp);
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
options.AuthorizationData.Add(new AuthorizeAttribute("test"));
options.AuthorizationData.Add(new AuthorizeAttribute("secondPolicy"));
@ -1488,7 +1489,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(sp);
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
options.AuthorizationData.Add(new AuthorizeAttribute("test"));
// "authorize" user
@ -1545,7 +1546,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(sp);
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
options.AuthorizationData.Add(new AuthorizeAttribute("test"));
// "authorize" user
@ -1576,7 +1577,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
options.LongPolling.PollTimeout = TimeSpan.FromMilliseconds(1); // We don't care about the poll itself
Assert.Null(connection.Features.Get<IConnectionInherentKeepAliveFeature>());
@ -1610,7 +1611,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services);
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
_ = dispatcher.ExecuteAsync(context, options, app).OrTimeout();
@ -1649,7 +1650,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
var pollTask = dispatcher.ExecuteAsync(context, options, app);
@ -1696,7 +1697,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
options.LongPolling.PollTimeout = TimeSpan.FromMilliseconds(1);
await dispatcher.ExecuteAsync(context, options, app).OrTimeout();
@ -1742,7 +1743,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
context.Request.Path = "/foo";
context.Request.Method = "POST";
context.Response.Body = ms;
await dispatcher.ExecuteNegotiateAsync(context, new HttpConnectionOptions { Transports = HttpTransportType.WebSockets });
await dispatcher.ExecuteNegotiateAsync(context, new HttpConnectionDispatcherOptions { Transports = HttpTransportType.WebSockets });
var negotiateResponse = JsonConvert.DeserializeObject<JObject>(Encoding.UTF8.GetString(ms.ToArray()));
var availableTransports = (JArray)negotiateResponse["availableTransports"];
@ -1821,7 +1822,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var builder = new ConnectionBuilder(services.BuildServiceProvider());
builder.UseConnectionHandler<ImmediatelyCompleteConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
options.Transports = supportedTransports;
await dispatcher.ExecuteAsync(context, options, app);

View File

@ -5,6 +5,7 @@ using System;
using System.IO.Pipelines;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Connections.Internal;
using Microsoft.Extensions.Logging;
using Xunit;
@ -19,7 +20,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var connection = connectionManager.CreateConnection();
Assert.NotNull(connection.ConnectionId);
Assert.Equal(HttpConnectionContext.ConnectionStatus.Inactive, connection.Status);
Assert.Equal(HttpConnectionStatus.Inactive, connection.Status);
Assert.Null(connection.ApplicationTask);
Assert.Null(connection.TransportTask);
Assert.Null(connection.Cancellation);
@ -265,7 +266,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
Assert.NotNull(connection.Transport);
await connection.DisposeAsync();
Assert.Equal(HttpConnectionContext.ConnectionStatus.Disposed, connection.Status);
Assert.Equal(HttpConnectionStatus.Disposed, connection.Status);
}
[Fact]
@ -279,7 +280,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
Assert.NotNull(connection.Application);
await connection.DisposeAsync();
Assert.Equal(HttpConnectionContext.ConnectionStatus.Disposed, connection.Status);
Assert.Equal(HttpConnectionStatus.Disposed, connection.Status);
}
[Fact]

View File

@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
[Fact]
public void CheckLongPollingTimeoutValue()
{
var options = new HttpConnectionOptions();
var options = new HttpConnectionDispatcherOptions();
Assert.Equal(options.LongPolling.PollTimeout, TimeSpan.FromSeconds(90));
}
}

View File

@ -73,7 +73,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
public async Task MapConnectionHandlerWithWebSocketSubProtocolSetsProtocol()
{
var host = BuildWebHost<MyConnectionHandler>("/socket",
options => options.WebSockets.SubProtocol = "protocol1");
options => options.WebSockets.SubProtocolSelector = subprotocols =>
{
Assert.Equal(new [] { "protocol1", "protocol2" }, subprotocols.ToArray());
return "protocol1";
});
await host.StartAsync();
@ -131,7 +135,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
}
}
private IWebHost BuildWebHost<TConnectionHandler>(string path, Action<HttpConnectionOptions> configureOptions) where TConnectionHandler : ConnectionHandler
private IWebHost BuildWebHost<TConnectionHandler>(string path, Action<HttpConnectionDispatcherOptions> configureOptions) where TConnectionHandler : ConnectionHandler
{
return new WebHostBuilder()
.UseUrls("http://127.0.0.1:0")

View File

@ -10,10 +10,16 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
{
internal class TestWebSocketConnectionFeature : IHttpWebSocketFeature, IDisposable
{
private readonly TaskCompletionSource<object> _accepted = new TaskCompletionSource<object>();
public bool IsWebSocketRequest => true;
public WebSocketChannel Client { get; private set; }
public string SubProtocol { get; private set; }
public Task Accepted => _accepted.Task;
public Task<WebSocket> AcceptAsync() => AcceptAsync(new WebSocketAcceptContext());
public Task<WebSocket> AcceptAsync(WebSocketAcceptContext context)
@ -25,6 +31,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var serverSocket = new WebSocketChannel(clientToServer.Reader, serverToClient.Writer);
Client = clientSocket;
SubProtocol = context.SubProtocol;
_accepted.TrySetResult(null);
return Task.FromResult<WebSocket>(serverSocket);
}

View File

@ -4,14 +4,18 @@
using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Internal;
using Microsoft.AspNetCore.Http.Connections.Internal.Transports;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Microsoft.Net.Http.Headers;
using Xunit;
using Xunit.Abstractions;
@ -346,5 +350,57 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
}
}
}
[Fact]
public async Task SubProtocolSelectorIsUsedToSelectSubProtocol()
{
const string ExpectedSubProtocol = "expected";
var providedSubProtocols = new[] {"provided1", "provided2"};
using (StartLog(out var loggerFactory, LogLevel.Debug))
{
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
var connection = new HttpConnectionContext("foo", pair.Transport, pair.Application);
using (var feature = new TestWebSocketConnectionFeature())
{
var options = new WebSocketOptions
{
// We want to verify behavior without timeout affecting it
CloseTimeout = TimeSpan.FromSeconds(20),
SubProtocolSelector = protocols => {
Assert.Equal(providedSubProtocols, protocols.ToArray());
return ExpectedSubProtocol;
},
};
var connectionContext = new HttpConnectionContext(string.Empty, null, null);
var ws = new WebSocketsTransport(options, connection.Application, connectionContext, loggerFactory);
// Create an HttpContext
var context = new DefaultHttpContext();
context.Request.Headers.Add(HeaderNames.WebSocketSubProtocols, providedSubProtocols.ToArray());
context.Features.Set<IHttpWebSocketFeature>(feature);
var transport = ws.ProcessRequestAsync(context, CancellationToken.None);
await feature.Accepted.OrThrowIfOtherFails(transport);
// Assert the feature got the right subprotocol
Assert.Equal(ExpectedSubProtocol, feature.SubProtocol);
// Run the client socket
var client = feature.Client.ExecuteAndCaptureFramesAsync();
await feature.Client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None).OrTimeout();
// close the client to server channel
connection.Transport.Output.Complete();
_ = await client.OrTimeout();
await transport.OrTimeout();
}
}
}
}
}