#2139 Add ListenLocalhost and ListenAnyIP
This commit is contained in:
parent
186e9806cd
commit
89d1862f21
|
|
@ -76,6 +76,13 @@ namespace SampleApp
|
||||||
listenOptions.UseConnectionLogging();
|
listenOptions.UseConnectionLogging();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
options.ListenLocalhost(basePort + 2, listenOptions =>
|
||||||
|
{
|
||||||
|
listenOptions.UseHttps("testCert.pfx", "testPassword");
|
||||||
|
});
|
||||||
|
|
||||||
|
options.ListenAnyIP(basePort + 3);
|
||||||
|
|
||||||
options.UseSystemd();
|
options.UseSystemd();
|
||||||
|
|
||||||
// The following section should be used to demo sockets
|
// The following section should be used to demo sockets
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
|
{
|
||||||
|
internal class AnyIPListenOptions : ListenOptions
|
||||||
|
{
|
||||||
|
internal AnyIPListenOptions(int port)
|
||||||
|
: base(new IPEndPoint(IPAddress.IPv6Any, port))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override async Task BindAsync(AddressBindContext context)
|
||||||
|
{
|
||||||
|
// when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await base.BindAsync(context).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is IOException))
|
||||||
|
{
|
||||||
|
context.Logger.LogDebug(CoreStrings.FormatFallbackToIPv4Any(IPEndPoint.Port));
|
||||||
|
|
||||||
|
// for machines that do not support IPv6
|
||||||
|
IPEndPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.Port);
|
||||||
|
await base.BindAsync(context).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
{
|
||||||
|
internal class AddressBindContext
|
||||||
|
{
|
||||||
|
public ICollection<string> Addresses { get; set; }
|
||||||
|
public List<ListenOptions> ListenOptions { get; set; }
|
||||||
|
public KestrelServerOptions ServerOptions { get; set; }
|
||||||
|
public ILogger Logger { get; set; }
|
||||||
|
public IDefaultHttpsProvider DefaultHttpsProvider { get; set; }
|
||||||
|
|
||||||
|
public Func<ListenOptions, Task> CreateBinding { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -47,17 +47,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
await strategy.BindAsync(context).ConfigureAwait(false);
|
await strategy.BindAsync(context).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AddressBindContext
|
|
||||||
{
|
|
||||||
public ICollection<string> Addresses { get; set; }
|
|
||||||
public List<ListenOptions> ListenOptions { get; set; }
|
|
||||||
public KestrelServerOptions ServerOptions { get; set; }
|
|
||||||
public ILogger Logger { get; set; }
|
|
||||||
public IDefaultHttpsProvider DefaultHttpsProvider { get; set; }
|
|
||||||
|
|
||||||
public Func<ListenOptions, Task> CreateBinding { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IStrategy CreateStrategy(ListenOptions[] listenOptions, string[] addresses, bool preferAddresses)
|
private static IStrategy CreateStrategy(ListenOptions[] listenOptions, string[] addresses, bool preferAddresses)
|
||||||
{
|
{
|
||||||
var hasListenOptions = listenOptions.Length > 0;
|
var hasListenOptions = listenOptions.Length > 0;
|
||||||
|
|
@ -109,10 +98,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task BindEndpointAsync(IPEndPoint endpoint, AddressBindContext context)
|
internal static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context)
|
||||||
=> BindEndpointAsync(new ListenOptions(endpoint), context);
|
|
||||||
|
|
||||||
private static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -126,60 +112,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
context.ListenOptions.Add(endpoint);
|
context.ListenOptions.Add(endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task BindLocalhostAsync(ServerAddress address, AddressBindContext context, bool https)
|
internal static ListenOptions ParseAddress(string address, KestrelServerOptions serverOptions, IDefaultHttpsProvider defaultHttpsProvider)
|
||||||
{
|
|
||||||
if (address.Port == 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(CoreStrings.DynamicPortOnLocalhostNotSupported);
|
|
||||||
}
|
|
||||||
|
|
||||||
var exceptions = new List<Exception>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var options = new ListenOptions(new IPEndPoint(IPAddress.Loopback, address.Port));
|
|
||||||
await BindEndpointAsync(options, context).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (https)
|
|
||||||
{
|
|
||||||
options.KestrelServerOptions = context.ServerOptions;
|
|
||||||
context.DefaultHttpsProvider.ConfigureHttps(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (!(ex is IOException))
|
|
||||||
{
|
|
||||||
context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, address, "IPv4 loopback", ex.Message);
|
|
||||||
exceptions.Add(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var options = new ListenOptions(new IPEndPoint(IPAddress.IPv6Loopback, address.Port));
|
|
||||||
await BindEndpointAsync(options, context).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (https)
|
|
||||||
{
|
|
||||||
options.KestrelServerOptions = context.ServerOptions;
|
|
||||||
context.DefaultHttpsProvider.ConfigureHttps(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (!(ex is IOException))
|
|
||||||
{
|
|
||||||
context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, address, "IPv6 loopback", ex.Message);
|
|
||||||
exceptions.Add(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exceptions.Count == 2)
|
|
||||||
{
|
|
||||||
throw new IOException(CoreStrings.FormatAddressBindingFailed(address), new AggregateException(exceptions));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If StartLocalhost doesn't throw, there is at least one listener.
|
|
||||||
// The port cannot change for "localhost".
|
|
||||||
context.Addresses.Add(address.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task BindAddressAsync(string address, AddressBindContext context)
|
|
||||||
{
|
{
|
||||||
var parsedAddress = ServerAddress.FromUrl(address);
|
var parsedAddress = ServerAddress.FromUrl(address);
|
||||||
var https = false;
|
var https = false;
|
||||||
|
|
@ -202,47 +135,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
if (parsedAddress.IsUnixPipe)
|
if (parsedAddress.IsUnixPipe)
|
||||||
{
|
{
|
||||||
options = new ListenOptions(parsedAddress.UnixPipePath);
|
options = new ListenOptions(parsedAddress.UnixPipePath);
|
||||||
await BindEndpointAsync(options, context).ConfigureAwait(false);
|
|
||||||
context.Addresses.Add(options.GetDisplayName());
|
|
||||||
}
|
}
|
||||||
else if (string.Equals(parsedAddress.Host, "localhost", StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(parsedAddress.Host, "localhost", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
|
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
|
||||||
await BindLocalhostAsync(parsedAddress, context, https).ConfigureAwait(false);
|
options = new LocalhostListenOptions(parsedAddress.Port);
|
||||||
|
}
|
||||||
|
else if (TryCreateIPEndPoint(parsedAddress, out var endpoint))
|
||||||
|
{
|
||||||
|
options = new ListenOptions(endpoint);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (TryCreateIPEndPoint(parsedAddress, out var endpoint))
|
// when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
|
||||||
{
|
options = new AnyIPListenOptions(parsedAddress.Port);
|
||||||
options = new ListenOptions(endpoint);
|
|
||||||
await BindEndpointAsync(options, context).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
|
|
||||||
try
|
|
||||||
{
|
|
||||||
options = new ListenOptions(new IPEndPoint(IPAddress.IPv6Any, parsedAddress.Port));
|
|
||||||
await BindEndpointAsync(options, context).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (!(ex is IOException))
|
|
||||||
{
|
|
||||||
context.Logger.LogDebug(CoreStrings.FormatFallbackToIPv4Any(parsedAddress.Port));
|
|
||||||
|
|
||||||
// for machines that do not support IPv6
|
|
||||||
options = new ListenOptions(new IPEndPoint(IPAddress.Any, parsedAddress.Port));
|
|
||||||
await BindEndpointAsync(options, context).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Addresses.Add(options.GetDisplayName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (https && options != null)
|
if (https)
|
||||||
{
|
{
|
||||||
options.KestrelServerOptions = context.ServerOptions;
|
options.KestrelServerOptions = serverOptions;
|
||||||
context.DefaultHttpsProvider.ConfigureHttps(options);
|
defaultHttpsProvider.ConfigureHttps(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface IStrategy
|
private interface IStrategy
|
||||||
|
|
@ -256,7 +171,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
{
|
{
|
||||||
context.Logger.LogDebug(CoreStrings.BindingToDefaultAddress, Constants.DefaultServerAddress);
|
context.Logger.LogDebug(CoreStrings.BindingToDefaultAddress, Constants.DefaultServerAddress);
|
||||||
|
|
||||||
await BindLocalhostAsync(ServerAddress.FromUrl(Constants.DefaultServerAddress), context, https: false).ConfigureAwait(false);
|
await ParseAddress(Constants.DefaultServerAddress, context.ServerOptions, context.DefaultHttpsProvider)
|
||||||
|
.BindAsync(context).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -308,9 +224,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
{
|
{
|
||||||
foreach (var endpoint in _endpoints)
|
foreach (var endpoint in _endpoints)
|
||||||
{
|
{
|
||||||
await BindEndpointAsync(endpoint, context).ConfigureAwait(false);
|
await endpoint.BindAsync(context).ConfigureAwait(false);
|
||||||
|
|
||||||
context.Addresses.Add(endpoint.GetDisplayName());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -328,7 +242,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
{
|
{
|
||||||
foreach (var address in _addresses)
|
foreach (var address in _addresses)
|
||||||
{
|
{
|
||||||
await BindAddressAsync(address, context).ConfigureAwait(false);
|
await ParseAddress(address, context.ServerOptions, context.DefaultHttpsProvider)
|
||||||
|
.BindAsync(context).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
ListenOptions.Add(listenOptions);
|
ListenOptions.Add(listenOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ListenLocalhost(int port) => ListenLocalhost(port, options => { });
|
||||||
|
|
||||||
|
public void ListenLocalhost(int port, Action<ListenOptions> configure)
|
||||||
|
{
|
||||||
|
if (configure == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configure));
|
||||||
|
}
|
||||||
|
|
||||||
|
var listenOptions = new LocalhostListenOptions(port)
|
||||||
|
{
|
||||||
|
KestrelServerOptions = this,
|
||||||
|
};
|
||||||
|
configure(listenOptions);
|
||||||
|
ListenOptions.Add(listenOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ListenAnyIP(int port) => ListenAnyIP(port, options => { });
|
||||||
|
|
||||||
|
public void ListenAnyIP(int port, Action<ListenOptions> configure)
|
||||||
|
{
|
||||||
|
if (configure == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configure));
|
||||||
|
}
|
||||||
|
|
||||||
|
var listenOptions = new AnyIPListenOptions(port)
|
||||||
|
{
|
||||||
|
KestrelServerOptions = this,
|
||||||
|
};
|
||||||
|
configure(listenOptions);
|
||||||
|
ListenOptions.Add(listenOptions);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bind to given Unix domain socket path.
|
/// Bind to given Unix domain socket path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Protocols;
|
using Microsoft.AspNetCore.Protocols;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
|
|
@ -140,7 +141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of this endpoint to display on command-line when the web server starts.
|
/// Gets the name of this endpoint to display on command-line when the web server starts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal string GetDisplayName()
|
internal virtual string GetDisplayName()
|
||||||
{
|
{
|
||||||
var scheme = ConnectionAdapters.Any(f => f.IsHttps)
|
var scheme = ConnectionAdapters.Any(f => f.IsHttps)
|
||||||
? "https"
|
? "https"
|
||||||
|
|
@ -182,5 +183,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal virtual async Task BindAsync(AddressBindContext context)
|
||||||
|
{
|
||||||
|
await AddressBinder.BindEndpointAsync(this, context).ConfigureAwait(false);
|
||||||
|
context.Addresses.Add(GetDisplayName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
|
{
|
||||||
|
internal class LocalhostListenOptions : ListenOptions
|
||||||
|
{
|
||||||
|
internal LocalhostListenOptions(int port)
|
||||||
|
: base(new IPEndPoint(IPAddress.Loopback, port))
|
||||||
|
{
|
||||||
|
if (port == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(CoreStrings.DynamicPortOnLocalhostNotSupported);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of this endpoint to display on command-line when the web server starts.
|
||||||
|
/// </summary>
|
||||||
|
internal override string GetDisplayName()
|
||||||
|
{
|
||||||
|
var scheme = ConnectionAdapters.Any(f => f.IsHttps)
|
||||||
|
? "https"
|
||||||
|
: "http";
|
||||||
|
|
||||||
|
return $"{scheme}://localhost:{IPEndPoint.Port}";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override async Task BindAsync(AddressBindContext context)
|
||||||
|
{
|
||||||
|
var exceptions = new List<Exception>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var v4Options = Clone(IPAddress.Loopback);
|
||||||
|
await AddressBinder.BindEndpointAsync(v4Options, context).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is IOException))
|
||||||
|
{
|
||||||
|
context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, GetDisplayName(), "IPv4 loopback", ex.Message);
|
||||||
|
exceptions.Add(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var v6Options = Clone(IPAddress.IPv6Loopback);
|
||||||
|
await AddressBinder.BindEndpointAsync(v6Options, context).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is IOException))
|
||||||
|
{
|
||||||
|
context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, GetDisplayName(), "IPv6 loopback", ex.Message);
|
||||||
|
exceptions.Add(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exceptions.Count == 2)
|
||||||
|
{
|
||||||
|
throw new IOException(CoreStrings.FormatAddressBindingFailed(GetDisplayName()), new AggregateException(exceptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If StartLocalhost doesn't throw, there is at least one listener.
|
||||||
|
// The port cannot change for "localhost".
|
||||||
|
context.Addresses.Add(GetDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// used for cloning to two IPEndpoints
|
||||||
|
private ListenOptions Clone(IPAddress address)
|
||||||
|
{
|
||||||
|
var options = new ListenOptions(new IPEndPoint(address, IPEndPoint.Port))
|
||||||
|
{
|
||||||
|
HandleType = HandleType,
|
||||||
|
KestrelServerOptions = KestrelServerOptions,
|
||||||
|
NoDelay = NoDelay,
|
||||||
|
Protocols = Protocols,
|
||||||
|
};
|
||||||
|
options.ConnectionAdapters.AddRange(ConnectionAdapters);
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
// 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;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Protocols;
|
using Microsoft.AspNetCore.Protocols;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -51,24 +51,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[InlineData("randomhost")]
|
[InlineData("randomhost")]
|
||||||
[InlineData("+")]
|
[InlineData("+")]
|
||||||
[InlineData("contoso.com")]
|
[InlineData("contoso.com")]
|
||||||
public async Task DefaultsToIPv6AnyOnInvalidIPAddress(string host)
|
public void ParseAddressDefaultsToAnyIPOnInvalidIPAddress(string host)
|
||||||
{
|
{
|
||||||
var addresses = new ServerAddressesFeature();
|
|
||||||
addresses.Addresses.Add($"http://{host}");
|
|
||||||
var options = new KestrelServerOptions();
|
var options = new KestrelServerOptions();
|
||||||
|
var listenOptions = AddressBinder.ParseAddress($"http://{host}", options, Mock.Of<IDefaultHttpsProvider>());
|
||||||
|
Assert.IsType<AnyIPListenOptions>(listenOptions);
|
||||||
|
Assert.Equal(ListenType.IPEndPoint, listenOptions.Type);
|
||||||
|
Assert.Equal(IPAddress.IPv6Any, listenOptions.IPEndPoint.Address);
|
||||||
|
Assert.Equal(80, listenOptions.IPEndPoint.Port);
|
||||||
|
}
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<ListenOptions>();
|
[Fact]
|
||||||
await AddressBinder.BindAsync(addresses,
|
public void ParseAddressLocalhost()
|
||||||
options,
|
{
|
||||||
NullLogger.Instance,
|
var options = new KestrelServerOptions();
|
||||||
Mock.Of<IDefaultHttpsProvider>(),
|
var listenOptions = AddressBinder.ParseAddress("http://localhost", options, Mock.Of<IDefaultHttpsProvider>());
|
||||||
endpoint =>
|
Assert.IsType<LocalhostListenOptions>(listenOptions);
|
||||||
{
|
Assert.Equal(ListenType.IPEndPoint, listenOptions.Type);
|
||||||
tcs.TrySetResult(endpoint);
|
Assert.Equal(IPAddress.Loopback, listenOptions.IPEndPoint.Address);
|
||||||
return Task.CompletedTask;
|
Assert.Equal(80, listenOptions.IPEndPoint.Port);
|
||||||
});
|
}
|
||||||
var result = await tcs.Task;
|
|
||||||
Assert.Equal(IPAddress.IPv6Any, result.IPEndPoint.Address);
|
[Fact]
|
||||||
|
public void ParseAddressUnixPipe()
|
||||||
|
{
|
||||||
|
var options = new KestrelServerOptions();
|
||||||
|
var listenOptions = AddressBinder.ParseAddress("http://unix:/tmp/kestrel-test.sock", options, Mock.Of<IDefaultHttpsProvider>());
|
||||||
|
Assert.Equal(ListenType.SocketPath, listenOptions.Type);
|
||||||
|
Assert.Equal("/tmp/kestrel-test.sock", listenOptions.SocketPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("http://10.10.10.10:5000/", "10.10.10.10", 5000)]
|
||||||
|
[InlineData("http://[::1]:5000", "::1", 5000)]
|
||||||
|
[InlineData("http://[::1]", "::1", 80)]
|
||||||
|
[InlineData("http://127.0.0.1", "127.0.0.1", 80)]
|
||||||
|
[InlineData("https://127.0.0.1", "127.0.0.1", 443)]
|
||||||
|
public void ParseAddressIP(string address, string ip, int port)
|
||||||
|
{
|
||||||
|
var options = new KestrelServerOptions();
|
||||||
|
var listenOptions = AddressBinder.ParseAddress(address, options, Mock.Of<IDefaultHttpsProvider>());
|
||||||
|
Assert.Equal(ListenType.IPEndPoint, listenOptions.Type);
|
||||||
|
Assert.Equal(IPAddress.Parse(ip), listenOptions.IPEndPoint.Address);
|
||||||
|
Assert.Equal(port, listenOptions.IPEndPoint.Port);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
private Task RegisterAddresses_Success(string addressInput, string testUrl, int testPort = 0)
|
private Task RegisterAddresses_Success(string addressInput, string testUrl, int testPort = 0)
|
||||||
=> RegisterAddresses_Success(addressInput, new[] { testUrl }, testPort);
|
=> RegisterAddresses_Success(addressInput, new[] { testUrl }, testPort);
|
||||||
|
|
||||||
private async Task RegisterAddresses_StaticPort_Success(string addressInput, string[] testUrls)
|
private Task RegisterAddresses_StaticPort_Success(string addressInput, string[] testUrls) =>
|
||||||
|
RunTestWithStaticPort(port => RegisterAddresses_Success($"{addressInput}:{port}", testUrls, port));
|
||||||
|
|
||||||
|
private async Task RunTestWithStaticPort(Func<int, Task> test)
|
||||||
{
|
{
|
||||||
var retryCount = 0;
|
var retryCount = 0;
|
||||||
var errors = new List<Exception>();
|
var errors = new List<Exception>();
|
||||||
|
|
@ -197,7 +200,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var port = GetNextPort();
|
var port = GetNextPort();
|
||||||
await RegisterAddresses_Success($"{addressInput}:{port}", testUrls, port);
|
await test(port);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch (XunitException)
|
catch (XunitException)
|
||||||
|
|
@ -254,34 +257,93 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RegisterIPEndPoint_StaticPort_Success(IPAddress address, string testUrl)
|
private Task RegisterIPEndPoint_StaticPort_Success(IPAddress address, string testUrl)
|
||||||
|
=> RunTestWithStaticPort(port => RegisterIPEndPoint_Success(new IPEndPoint(address, port), testUrl, port));
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task ListenAnyIP_IPv4_Success()
|
||||||
{
|
{
|
||||||
var retryCount = 0;
|
await ListenAnyIP_Success(new[] { "http://localhost", "http://127.0.0.1" });
|
||||||
var errors = new List<Exception>();
|
}
|
||||||
|
|
||||||
while (retryCount < MaxRetries)
|
[ConditionalFact]
|
||||||
|
[IPv6SupportedCondition]
|
||||||
|
public async Task ListenAnyIP_IPv6_Success()
|
||||||
|
{
|
||||||
|
await ListenAnyIP_Success(new[] { "http://[::1]", "http://localhost", "http://127.0.0.1" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
[NetworkIsReachable]
|
||||||
|
public async Task ListenAnyIP_HostName_Success()
|
||||||
|
{
|
||||||
|
var hostName = Dns.GetHostName();
|
||||||
|
await ListenAnyIP_Success(new[] { $"http://{hostName}" });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ListenAnyIP_Success(string[] testUrls, int testPort = 0)
|
||||||
|
{
|
||||||
|
var hostBuilder = TransportSelector.GetWebHostBuilder()
|
||||||
|
.UseKestrel(options =>
|
||||||
|
{
|
||||||
|
options.ListenAnyIP(testPort);
|
||||||
|
})
|
||||||
|
.ConfigureLogging(_configureLoggingDelegate)
|
||||||
|
.Configure(ConfigureEchoAddress);
|
||||||
|
|
||||||
|
using (var host = hostBuilder.Build())
|
||||||
{
|
{
|
||||||
try
|
host.Start();
|
||||||
{
|
|
||||||
var port = GetNextPort();
|
|
||||||
await RegisterIPEndPoint_Success(new IPEndPoint(address, port), testUrl, port);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (XunitException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
errors.Add(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
retryCount++;
|
foreach (var testUrl in testUrls.Select(testUrl => $"{testUrl}:{(testPort == 0 ? host.GetPort() : testPort)}"))
|
||||||
|
{
|
||||||
|
var response = await HttpClientSlim.GetStringAsync(testUrl, validateCertificate: false);
|
||||||
|
|
||||||
|
// Compare the response with Uri.ToString(), rather than testUrl directly.
|
||||||
|
// Required to handle IPv6 addresses with zone index, like "fe80::3%1"
|
||||||
|
Assert.Equal(new Uri(testUrl).ToString(), response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (errors.Any())
|
[ConditionalFact]
|
||||||
|
public async Task ListenLocalhost_IPv4LocalhostStaticPort_Success()
|
||||||
|
{
|
||||||
|
await ListenLocalhost_StaticPort_Success(new[] { "http://localhost", "http://127.0.0.1" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
[IPv6SupportedCondition]
|
||||||
|
public async Task ListenLocalhost_IPv6LocalhostStaticPort_Success()
|
||||||
|
{
|
||||||
|
await ListenLocalhost_StaticPort_Success(new[] { "http://localhost", "http://127.0.0.1", "http://[::1]" });
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ListenLocalhost_StaticPort_Success(string[] testUrls) =>
|
||||||
|
RunTestWithStaticPort(port => ListenLocalhost_Success(testUrls, port));
|
||||||
|
|
||||||
|
private async Task ListenLocalhost_Success(string[] testUrls, int testPort = 0)
|
||||||
|
{
|
||||||
|
var hostBuilder = TransportSelector.GetWebHostBuilder()
|
||||||
|
.UseKestrel(options =>
|
||||||
|
{
|
||||||
|
options.ListenLocalhost(testPort);
|
||||||
|
})
|
||||||
|
.ConfigureLogging(_configureLoggingDelegate)
|
||||||
|
.Configure(ConfigureEchoAddress);
|
||||||
|
|
||||||
|
using (var host = hostBuilder.Build())
|
||||||
{
|
{
|
||||||
throw new AggregateException(errors);
|
host.Start();
|
||||||
|
|
||||||
|
foreach (var testUrl in testUrls.Select(testUrl => $"{testUrl}:{(testPort == 0 ? host.GetPort() : testPort)}"))
|
||||||
|
{
|
||||||
|
var response = await HttpClientSlim.GetStringAsync(testUrl, validateCertificate: false);
|
||||||
|
|
||||||
|
// Compare the response with Uri.ToString(), rather than testUrl directly.
|
||||||
|
// Required to handle IPv6 addresses with zone index, like "fe80::3%1"
|
||||||
|
Assert.Equal(new Uri(testUrl).ToString(), response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
|
||||||
_bufferPool = new MemoryPool();
|
_bufferPool = new MemoryPool();
|
||||||
_mockLibuv = new MockLibuv();
|
_mockLibuv = new MockLibuv();
|
||||||
|
|
||||||
var libuvTransport = new LibuvTransport(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions(0));
|
var libuvTransport = new LibuvTransport(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions((ulong)0));
|
||||||
_libuvThread = new LibuvThread(libuvTransport, maxLoops: 1);
|
_libuvThread = new LibuvThread(libuvTransport, maxLoops: 1);
|
||||||
_libuvThread.StartAsync().Wait();
|
_libuvThread.StartAsync().Wait();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue