diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/ConnectionAdapterContext.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/ConnectionAdapterContext.cs similarity index 89% rename from src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/ConnectionAdapterContext.cs rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/ConnectionAdapterContext.cs index 380f56ffc8..6d074b3a7c 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/ConnectionAdapterContext.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/ConnectionAdapterContext.cs @@ -3,7 +3,7 @@ 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 // we want to add more connection metadata later. diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/IAdaptedConnection.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/IAdaptedConnection.cs similarity index 85% rename from src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/IAdaptedConnection.cs rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/IAdaptedConnection.cs index 793d4b7e0a..8c39b07c3e 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/IAdaptedConnection.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/IAdaptedConnection.cs @@ -4,7 +4,7 @@ using System.IO; using Microsoft.AspNetCore.Http.Features; -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal { public interface IAdaptedConnection { diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/IConnectionAdapter.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/IConnectionAdapter.cs similarity index 77% rename from src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/IConnectionAdapter.cs rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/IConnectionAdapter.cs index 57c9d904d9..e0249d5545 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/IConnectionAdapter.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/IConnectionAdapter.cs @@ -3,10 +3,11 @@ using System.Threading.Tasks; -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal { public interface IConnectionAdapter { + bool IsHttps { get; } Task OnConnectionAsync(ConnectionAdapterContext context); } } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/LoggingConnectionAdapter.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/LoggingConnectionAdapter.cs index d0c268b1b4..d685e1027d 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/LoggingConnectionAdapter.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/LoggingConnectionAdapter.cs @@ -24,6 +24,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter _logger = logger; } + public bool IsHttps => false; + public Task OnConnectionAsync(ConnectionAdapterContext context) { return Task.FromResult( diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/ConnectionHandler.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/ConnectionHandler.cs index 4cf062d7ca..db9236d9d8 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/ConnectionHandler.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/ConnectionHandler.cs @@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal }); _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, // Frame.RequestProcessingAsync is guaranteed to unblock the transport thread before calling diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnectionContext.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnectionContext.cs index c4edded73e..1394f6c150 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnectionContext.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnectionContext.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 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.Internal.System.IO.Pipelines; diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.cs index 875e406463..4d84a1c6d4 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/Frame.cs @@ -12,7 +12,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; 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.Internal.System; using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines; diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs index 2e1b942de3..eb04b7f486 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs @@ -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. [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 if (IsEnabled()) { ConnectionStart( context.ConnectionId, - listenOptions.Scheme, information.LocalEndPoint.ToString(), information.RemoteEndPoint.ToString()); } @@ -42,14 +41,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure [MethodImpl(MethodImplOptions.NoInlining)] [Event(1, Level = EventLevel.Verbose)] private void ConnectionStart(string connectionId, - string scheme, string localEndPoint, string remoteEndPoint) { WriteEvent( 1, connectionId, - scheme, localEndPoint, remoteEndPoint ); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/KestrelServer.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/KestrelServer.cs index 83058d881a..5c5d204564 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/KestrelServer.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/KestrelServer.cs @@ -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)}()."); } + 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)) { @@ -166,10 +170,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core if (parsedAddress.IsUnixPipe) { - listenOptions.Add(new ListenOptions(parsedAddress.UnixPipePath) - { - Scheme = parsedAddress.Scheme, - }); + listenOptions.Add(new ListenOptions(parsedAddress.UnixPipePath)); } else { @@ -185,10 +186,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core else { // These endPoints will be added later to _serverAddresses.Addresses - listenOptions.Add(new ListenOptions(CreateIPEndPoint(parsedAddress)) - { - Scheme = parsedAddress.Scheme, - }); + listenOptions.Add(new ListenOptions(CreateIPEndPoint(parsedAddress))); } } } @@ -209,8 +207,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core 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.ToString()); + _serverAddresses.Addresses.Add(endPoint.GetDisplayName()); } } catch (Exception ex) @@ -285,10 +282,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core try { - var ipv4ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, parsedAddress.Port)) - { - Scheme = parsedAddress.Scheme, - }; + var ipv4ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, parsedAddress.Port)); var connectionHandler = new ConnectionHandler(ipv4ListenOptions, serviceContext, application); var transport = _transportFactory.Create(ipv4ListenOptions, connectionHandler); @@ -307,10 +301,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core try { - var ipv6ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.IPv6Loopback, parsedAddress.Port)) - { - Scheme = parsedAddress.Scheme, - }; + var ipv6ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.IPv6Loopback, parsedAddress.Port)); var connectionHandler = new ConnectionHandler(ipv6ListenOptions, serviceContext, application); var transport = _transportFactory.Create(ipv6ListenOptions, connectionHandler); @@ -349,4 +340,4 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core return new IPEndPoint(ip, address.Port); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/ListenOptions.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/ListenOptions.cs index 8777ba98bc..2e009f542b 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/ListenOptions.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/ListenOptions.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; 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; namespace Microsoft.AspNetCore.Server.Kestrel.Core @@ -82,26 +83,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core /// public List ConnectionAdapters { get; } = new List(); - // Scheme is hopefully only a temporary measure for back compat with IServerAddressesFeature. - // TODO: Allow connection adapters to configure the scheme - public string Scheme { get; set; } = "http"; - - public override string ToString() + /// + /// Gets the name of this endpoint to display on command-line when the web server starts. + /// + internal string GetDisplayName() { - // Use http scheme for all addresses. If https should be used for this endPoint, - // it can still be configured for this endPoint specifically. + var scheme = ConnectionAdapters.Any(f => f.IsHttps) + ? "https" + : "http"; + switch (Type) { case ListenType.IPEndPoint: - return $"{Scheme}://{IPEndPoint}"; + return $"{scheme}://{IPEndPoint}"; case ListenType.SocketPath: - return $"{Scheme}://unix:{SocketPath}"; + return $"{scheme}://unix:{SocketPath}"; case ListenType.FileHandle: - // This was never supported via --server.urls, so no need to include Scheme. - return "http://"; + return $"{scheme}://"; default: throw new InvalidOperationException(); } } + + public override string ToString() => GetDisplayName(); } } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Https/HttpsConnectionAdapter.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Https/HttpsConnectionAdapter.cs index 7f5aa71eb0..298c501bea 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Https/HttpsConnectionAdapter.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Https/HttpsConnectionAdapter.cs @@ -7,7 +7,7 @@ using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; 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.Extensions.Logging; @@ -40,6 +40,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https _logger = loggerFactory?.CreateLogger(nameof(HttpsConnectionAdapter)); } + public bool IsHttps => true; + public async Task OnConnectionAsync(ConnectionAdapterContext context) { SslStream sslStream; diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs index dcdbaa67a3..eb84fd3331 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs @@ -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(() => host.Start()); + } + } + private void ThrowsWhenBindingLocalhostToAddressInUse(AddressFamily addressFamily, IPAddress address) { using (var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp)) diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ConnectionAdapterTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ConnectionAdapterTests.cs index 0d8150f62c..aecf4a30d7 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ConnectionAdapterTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ConnectionAdapterTests.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features; 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 Xunit; @@ -112,6 +112,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { private RewritingStream _rewritingStream; + public bool IsHttps => false; + public Task OnConnectionAsync(ConnectionAdapterContext context) { _rewritingStream = new RewritingStream(context.ConnectionStream); @@ -123,6 +125,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests private class AsyncConnectionAdapter : IConnectionAdapter { + public bool IsHttps => false; + public async Task OnConnectionAsync(ConnectionAdapterContext context) { await Task.Delay(100); @@ -132,6 +136,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests private class ThrowingConnectionAdapter : IConnectionAdapter { + public bool IsHttps => false; + public Task OnConnectionAsync(ConnectionAdapterContext context) { throw new Exception(); diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/EventSourceTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/EventSourceTests.cs index a0124e51a7..f692537ea7 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/EventSourceTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/EventSourceTests.cs @@ -55,8 +55,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { var start = Assert.Single(events, e => e.EventName == "ConnectionStart"); - Assert.All(new[] { "connectionId", "scheme", "remoteEndPoint", "localEndPoint" }, p => Assert.Contains(p, start.PayloadNames)); - Assert.Equal("http", GetProperty(start, "scheme")); + Assert.All(new[] { "connectionId", "remoteEndPoint", "localEndPoint" }, p => Assert.Contains(p, start.PayloadNames)); Assert.Equal($"127.0.0.1:{port}", GetProperty(start, "localEndPoint")); } { diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests/ListenerPrimaryTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests/ListenerPrimaryTests.cs index 46defb4317..07658dcc23 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests/ListenerPrimaryTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests/ListenerPrimaryTests.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; 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.Networking; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; @@ -46,7 +47,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests await libuvThreadPrimary.StartAsync(); var listenerPrimary = new ListenerPrimary(transportContextPrimary); 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 Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); @@ -108,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests await libuvThreadPrimary.StartAsync(); var listenerPrimary = new ListenerPrimary(transportContextPrimary); await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); - var address = listenOptions.ToString(); + var address = GetUri(listenOptions); // Add secondary listener var libuvThreadSecondary = new LibuvThread(libuvTransport); @@ -217,7 +218,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests await libuvThreadPrimary.StartAsync(); var listenerPrimary = new ListenerPrimary(transportContextPrimary); await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, libuvThreadPrimary); - var address = listenOptions.ToString(); + var address = GetUri(listenOptions); // Add secondary listener with wrong pipe message var libuvThreadSecondary = new LibuvThread(libuvTransport); @@ -249,7 +250,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests } private static async Task AssertResponseEventually( - string address, + Uri address, string expected, string[] allowed = null, 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."); } + + 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}"); + } } } diff --git a/test/shared/PassThroughConnectionAdapter.cs b/test/shared/PassThroughConnectionAdapter.cs index 7384eaa613..6c4c35bf80 100644 --- a/test/shared/PassThroughConnectionAdapter.cs +++ b/test/shared/PassThroughConnectionAdapter.cs @@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.Testing { public class PassThroughConnectionAdapter : IConnectionAdapter { + public bool IsHttps => false; + public Task OnConnectionAsync(ConnectionAdapterContext context) { var adapted = new AdaptedConnection(new LoggingStream(context.ConnectionStream, new TestApplicationErrorLogger()));