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;
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.

View File

@ -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
{

View File

@ -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<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context);
}
}

View File

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

View File

@ -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

View File

@ -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;

View File

@ -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;

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.
[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
);

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)}().");
}
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<TContext>(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<TContext>(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);
}
}
}
}

View File

@ -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
/// </remarks>
public List<IConnectionAdapter> ConnectionAdapters { get; } = new List<IConnectionAdapter>();
// 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()
/// <summary>
/// Gets the name of this endpoint to display on command-line when the web server starts.
/// </summary>
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://<file handle>";
return $"{scheme}://<file handle>";
default:
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.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<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{
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)
{
using (var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp))

View File

@ -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<IAdaptedConnection> 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<IAdaptedConnection> 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<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{
throw new Exception();

View File

@ -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"));
}
{

View File

@ -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}");
}
}
}

View File

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