Remove ListenOptions.Scheme and move IConnectionFilter to .Internal namespace

Add an internal API to ListenOptions to determine if an endpoint is configured to use HTTPS. This is a temporary as the design of connection adapters and configuration will churn before release.
This commit is contained in:
Nate McMaster 2017-04-12 14:40:58 -07:00
parent 8258d300e2
commit ed4a27a827
16 changed files with 83 additions and 49 deletions

View File

@ -3,7 +3,7 @@
using System.IO; using System.IO;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{ {
// Even though this only includes the non-adapted ConnectionStream currently, this is a context in case // Even though this only includes the non-adapted ConnectionStream currently, this is a context in case
// we want to add more connection metadata later. // we want to add more connection metadata later.

View File

@ -4,7 +4,7 @@
using System.IO; using System.IO;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{ {
public interface IAdaptedConnection public interface IAdaptedConnection
{ {

View File

@ -3,10 +3,11 @@
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{ {
public interface IConnectionAdapter public interface IConnectionAdapter
{ {
bool IsHttps { get; }
Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context); Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context);
} }
} }

View File

@ -24,6 +24,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter
_logger = logger; _logger = logger;
} }
public bool IsHttps => false;
public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context) public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{ {
return Task.FromResult<IAdaptedConnection>( return Task.FromResult<IAdaptedConnection>(

View File

@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
}); });
_serviceContext.Log.ConnectionStart(connectionId); _serviceContext.Log.ConnectionStart(connectionId);
KestrelEventSource.Log.ConnectionStart(_listenOptions, connection, connectionInfo); KestrelEventSource.Log.ConnectionStart(connection, connectionInfo);
// Since data cannot be added to the inputPipe by the transport until OnConnection returns, // Since data cannot be added to the inputPipe by the transport until OnConnection returns,
// Frame.RequestProcessingAsync is guaranteed to unblock the transport thread before calling // Frame.RequestProcessingAsync is guaranteed to unblock the transport thread before calling

View File

@ -2,7 +2,7 @@
// 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.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;

View File

@ -12,7 +12,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System; using Microsoft.AspNetCore.Server.Kestrel.Internal.System;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;

View File

@ -26,14 +26,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
// - Avoid renaming methods or parameters marked with EventAttribute. EventSource uses these to form the event object. // - Avoid renaming methods or parameters marked with EventAttribute. EventSource uses these to form the event object.
[NonEvent] [NonEvent]
public void ConnectionStart(ListenOptions listenOptions, IConnectionContext context, IConnectionInformation information) public void ConnectionStart(IConnectionContext context, IConnectionInformation information)
{ {
// avoid allocating strings unless this event source is enabled // avoid allocating strings unless this event source is enabled
if (IsEnabled()) if (IsEnabled())
{ {
ConnectionStart( ConnectionStart(
context.ConnectionId, context.ConnectionId,
listenOptions.Scheme,
information.LocalEndPoint.ToString(), information.LocalEndPoint.ToString(),
information.RemoteEndPoint.ToString()); information.RemoteEndPoint.ToString());
} }
@ -42,14 +41,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
[Event(1, Level = EventLevel.Verbose)] [Event(1, Level = EventLevel.Verbose)]
private void ConnectionStart(string connectionId, private void ConnectionStart(string connectionId,
string scheme,
string localEndPoint, string localEndPoint,
string remoteEndPoint) string remoteEndPoint)
{ {
WriteEvent( WriteEvent(
1, 1,
connectionId, connectionId,
scheme,
localEndPoint, localEndPoint,
remoteEndPoint remoteEndPoint
); );

View File

@ -153,6 +153,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
{ {
throw new InvalidOperationException($"HTTPS endpoints can only be configured using {nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.Listen)}()."); throw new InvalidOperationException($"HTTPS endpoints can only be configured using {nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.Listen)}().");
} }
else if (!parsedAddress.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"Unrecognized scheme in server address '{address}'. Only 'http://' is supported.");
}
if (!string.IsNullOrEmpty(parsedAddress.PathBase)) if (!string.IsNullOrEmpty(parsedAddress.PathBase))
{ {
@ -166,10 +170,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
if (parsedAddress.IsUnixPipe) if (parsedAddress.IsUnixPipe)
{ {
listenOptions.Add(new ListenOptions(parsedAddress.UnixPipePath) listenOptions.Add(new ListenOptions(parsedAddress.UnixPipePath));
{
Scheme = parsedAddress.Scheme,
});
} }
else else
{ {
@ -185,10 +186,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
else else
{ {
// These endPoints will be added later to _serverAddresses.Addresses // These endPoints will be added later to _serverAddresses.Addresses
listenOptions.Add(new ListenOptions(CreateIPEndPoint(parsedAddress)) listenOptions.Add(new ListenOptions(CreateIPEndPoint(parsedAddress)));
{
Scheme = parsedAddress.Scheme,
});
} }
} }
} }
@ -209,8 +207,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw new IOException($"Failed to bind to address {endPoint}: address already in use.", ex); throw new IOException($"Failed to bind to address {endPoint}: address already in use.", ex);
} }
// If requested port was "0", replace with assigned dynamic port. _serverAddresses.Addresses.Add(endPoint.GetDisplayName());
_serverAddresses.Addresses.Add(endPoint.ToString());
} }
} }
catch (Exception ex) catch (Exception ex)
@ -285,10 +282,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
try try
{ {
var ipv4ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, parsedAddress.Port)) var ipv4ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, parsedAddress.Port));
{
Scheme = parsedAddress.Scheme,
};
var connectionHandler = new ConnectionHandler<TContext>(ipv4ListenOptions, serviceContext, application); var connectionHandler = new ConnectionHandler<TContext>(ipv4ListenOptions, serviceContext, application);
var transport = _transportFactory.Create(ipv4ListenOptions, connectionHandler); var transport = _transportFactory.Create(ipv4ListenOptions, connectionHandler);
@ -307,10 +301,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
try try
{ {
var ipv6ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.IPv6Loopback, parsedAddress.Port)) var ipv6ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.IPv6Loopback, parsedAddress.Port));
{
Scheme = parsedAddress.Scheme,
};
var connectionHandler = new ConnectionHandler<TContext>(ipv6ListenOptions, serviceContext, application); var connectionHandler = new ConnectionHandler<TContext>(ipv6ListenOptions, serviceContext, application);
var transport = _transportFactory.Create(ipv6ListenOptions, connectionHandler); var transport = _transportFactory.Create(ipv6ListenOptions, connectionHandler);
@ -349,4 +340,4 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
return new IPEndPoint(ip, address.Port); return new IPEndPoint(ip, address.Port);
} }
} }
} }

View File

@ -3,8 +3,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
namespace Microsoft.AspNetCore.Server.Kestrel.Core namespace Microsoft.AspNetCore.Server.Kestrel.Core
@ -82,26 +83,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// </remarks> /// </remarks>
public List<IConnectionAdapter> ConnectionAdapters { get; } = new List<IConnectionAdapter>(); public List<IConnectionAdapter> ConnectionAdapters { get; } = new List<IConnectionAdapter>();
// Scheme is hopefully only a temporary measure for back compat with IServerAddressesFeature. /// <summary>
// TODO: Allow connection adapters to configure the scheme /// Gets the name of this endpoint to display on command-line when the web server starts.
public string Scheme { get; set; } = "http"; /// </summary>
internal string GetDisplayName()
public override string ToString()
{ {
// Use http scheme for all addresses. If https should be used for this endPoint, var scheme = ConnectionAdapters.Any(f => f.IsHttps)
// it can still be configured for this endPoint specifically. ? "https"
: "http";
switch (Type) switch (Type)
{ {
case ListenType.IPEndPoint: case ListenType.IPEndPoint:
return $"{Scheme}://{IPEndPoint}"; return $"{scheme}://{IPEndPoint}";
case ListenType.SocketPath: case ListenType.SocketPath:
return $"{Scheme}://unix:{SocketPath}"; return $"{scheme}://unix:{SocketPath}";
case ListenType.FileHandle: case ListenType.FileHandle:
// This was never supported via --server.urls, so no need to include Scheme. return $"{scheme}://<file handle>";
return "http://<file handle>";
default: default:
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
} }
public override string ToString() => GetDisplayName();
} }
} }

View File

@ -7,7 +7,7 @@ using System.Net.Security;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -40,6 +40,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
_logger = loggerFactory?.CreateLogger(nameof(HttpsConnectionAdapter)); _logger = loggerFactory?.CreateLogger(nameof(HttpsConnectionAdapter));
} }
public bool IsHttps => true;
public async Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context) public async Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{ {
SslStream sslStream; SslStream sslStream;

View File

@ -260,6 +260,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
} }
} }
[Theory]
[InlineData("https://localhost")]
[InlineData("ftp://localhost")]
public void ThrowsForUnsupportedAddressFromHosting(string addr)
{
var hostBuilder = new WebHostBuilder()
.UseKestrel()
.UseUrls(addr)
.Configure(ConfigureEchoAddress);
using (var host = hostBuilder.Build())
{
Assert.Throws<InvalidOperationException>(() => host.Start());
}
}
private void ThrowsWhenBindingLocalhostToAddressInUse(AddressFamily addressFamily, IPAddress address) private void ThrowsWhenBindingLocalhostToAddressInUse(AddressFamily addressFamily, IPAddress address)
{ {
using (var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp)) using (var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp))

View File

@ -8,7 +8,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Xunit; using Xunit;
@ -112,6 +112,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{ {
private RewritingStream _rewritingStream; private RewritingStream _rewritingStream;
public bool IsHttps => false;
public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context) public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{ {
_rewritingStream = new RewritingStream(context.ConnectionStream); _rewritingStream = new RewritingStream(context.ConnectionStream);
@ -123,6 +125,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
private class AsyncConnectionAdapter : IConnectionAdapter private class AsyncConnectionAdapter : IConnectionAdapter
{ {
public bool IsHttps => false;
public async Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context) public async Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{ {
await Task.Delay(100); await Task.Delay(100);
@ -132,6 +136,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
private class ThrowingConnectionAdapter : IConnectionAdapter private class ThrowingConnectionAdapter : IConnectionAdapter
{ {
public bool IsHttps => false;
public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context) public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{ {
throw new Exception(); throw new Exception();

View File

@ -55,8 +55,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{ {
var start = Assert.Single(events, e => e.EventName == "ConnectionStart"); var start = Assert.Single(events, e => e.EventName == "ConnectionStart");
Assert.All(new[] { "connectionId", "scheme", "remoteEndPoint", "localEndPoint" }, p => Assert.Contains(p, start.PayloadNames)); Assert.All(new[] { "connectionId", "remoteEndPoint", "localEndPoint" }, p => Assert.Contains(p, start.PayloadNames));
Assert.Equal("http", GetProperty(start, "scheme"));
Assert.Equal($"127.0.0.1:{port}", GetProperty(start, "localEndPoint")); Assert.Equal($"127.0.0.1:{port}", GetProperty(start, "localEndPoint"));
} }
{ {

View File

@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers;
@ -46,7 +47,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
await libuvThreadPrimary.StartAsync(); await libuvThreadPrimary.StartAsync();
var listenerPrimary = new ListenerPrimary(transportContextPrimary); var listenerPrimary = new ListenerPrimary(transportContextPrimary);
await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary);
var address = listenOptions.ToString(); var address = GetUri(listenOptions);
// Until a secondary listener is added, TCP connections get dispatched directly // Until a secondary listener is added, TCP connections get dispatched directly
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
@ -108,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
await libuvThreadPrimary.StartAsync(); await libuvThreadPrimary.StartAsync();
var listenerPrimary = new ListenerPrimary(transportContextPrimary); var listenerPrimary = new ListenerPrimary(transportContextPrimary);
await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary);
var address = listenOptions.ToString(); var address = GetUri(listenOptions);
// Add secondary listener // Add secondary listener
var libuvThreadSecondary = new LibuvThread(libuvTransport); var libuvThreadSecondary = new LibuvThread(libuvTransport);
@ -217,7 +218,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
await libuvThreadPrimary.StartAsync(); await libuvThreadPrimary.StartAsync();
var listenerPrimary = new ListenerPrimary(transportContextPrimary); var listenerPrimary = new ListenerPrimary(transportContextPrimary);
await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary);
var address = listenOptions.ToString(); var address = GetUri(listenOptions);
// Add secondary listener with wrong pipe message // Add secondary listener with wrong pipe message
var libuvThreadSecondary = new LibuvThread(libuvTransport); var libuvThreadSecondary = new LibuvThread(libuvTransport);
@ -249,7 +250,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
} }
private static async Task AssertResponseEventually( private static async Task AssertResponseEventually(
string address, Uri address,
string expected, string expected,
string[] allowed = null, string[] allowed = null,
int maxRetries = 100, int maxRetries = 100,
@ -273,5 +274,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.True(false, $"'{address}' failed to respond with '{expected}' in {maxRetries} retries."); Assert.True(false, $"'{address}' failed to respond with '{expected}' in {maxRetries} retries.");
} }
private static Uri GetUri(ListenOptions options)
{
if (options.Type != ListenType.IPEndPoint)
{
throw new InvalidOperationException($"Could not determine a proper URI for options with Type {options.Type}");
}
var scheme = options.ConnectionAdapters.Any(f => f.IsHttps)
? "https"
: "http";
return new Uri($"{scheme}://{options.IPEndPoint}");
}
} }
} }

View File

@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.Testing
{ {
public class PassThroughConnectionAdapter : IConnectionAdapter public class PassThroughConnectionAdapter : IConnectionAdapter
{ {
public bool IsHttps => false;
public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context) public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{ {
var adapted = new AdaptedConnection(new LoggingStream(context.ConnectionStream, new TestApplicationErrorLogger())); var adapted = new AdaptedConnection(new LoggingStream(context.ConnectionStream, new TestApplicationErrorLogger()));