diff --git a/src/Kestrel.Core/CoreStrings.resx b/src/Kestrel.Core/CoreStrings.resx
index fdbebb8aaf..158bc6cc53 100644
--- a/src/Kestrel.Core/CoreStrings.resx
+++ b/src/Kestrel.Core/CoreStrings.resx
@@ -1,17 +1,17 @@
-
@@ -462,4 +462,7 @@
Request headers contain connection-specific header field.
-
+
+ Unable to configure default https bindings because no IDefaultHttpsProvider service was provided.
+
+
\ No newline at end of file
diff --git a/src/Kestrel.Core/Internal/AddressBinder.cs b/src/Kestrel.Core/Internal/AddressBinder.cs
index e4d1d18b93..808675c0fb 100644
--- a/src/Kestrel.Core/Internal/AddressBinder.cs
+++ b/src/Kestrel.Core/Internal/AddressBinder.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// 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;
@@ -18,10 +18,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
internal class AddressBinder
{
public static async Task BindAsync(IServerAddressesFeature addresses,
- List listenOptions,
+ KestrelServerOptions serverOptions,
ILogger logger,
+ IDefaultHttpsProvider defaultHttpsProvider,
Func createBinding)
{
+ var listenOptions = serverOptions.ListenOptions;
var strategy = CreateStrategy(
listenOptions.ToArray(),
addresses.Addresses.ToArray(),
@@ -31,7 +33,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
Addresses = addresses.Addresses,
ListenOptions = listenOptions,
+ ServerOptions = serverOptions,
Logger = logger,
+ DefaultHttpsProvider = defaultHttpsProvider ?? UnconfiguredDefaultHttpsProvider.Instance,
CreateBinding = createBinding
};
@@ -47,7 +51,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
public ICollection Addresses { get; set; }
public List ListenOptions { get; set; }
+ public KestrelServerOptions ServerOptions { get; set; }
public ILogger Logger { get; set; }
+ public IDefaultHttpsProvider DefaultHttpsProvider { get; set; }
public Func CreateBinding { get; set; }
}
@@ -120,7 +126,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
context.ListenOptions.Add(endpoint);
}
- private static async Task BindLocalhostAsync(ServerAddress address, AddressBindContext context)
+ private static async Task BindLocalhostAsync(ServerAddress address, AddressBindContext context, bool https)
{
if (address.Port == 0)
{
@@ -131,7 +137,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
try
{
- await BindEndpointAsync(new IPEndPoint(IPAddress.Loopback, address.Port), context).ConfigureAwait(false);
+ 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))
{
@@ -141,7 +154,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
try
{
- await BindEndpointAsync(new IPEndPoint(IPAddress.IPv6Loopback, address.Port), context).ConfigureAwait(false);
+ 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))
{
@@ -162,10 +182,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private static async Task BindAddressAsync(string address, AddressBindContext context)
{
var parsedAddress = ServerAddress.FromUrl(address);
+ var https = false;
if (parsedAddress.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
{
- throw new InvalidOperationException(CoreStrings.FormatConfigureHttpsFromMethodCall($"{nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.Listen)}()"));
+ https = true;
}
else if (!parsedAddress.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase))
{
@@ -177,20 +198,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
throw new InvalidOperationException(CoreStrings.FormatConfigurePathBaseFromMethodCall($"{nameof(IApplicationBuilder)}.UsePathBase()"));
}
+ ListenOptions options = null;
if (parsedAddress.IsUnixPipe)
{
- var endPoint = new ListenOptions(parsedAddress.UnixPipePath);
- await BindEndpointAsync(endPoint, context).ConfigureAwait(false);
- context.Addresses.Add(endPoint.GetDisplayName());
+ 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))
{
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
- await BindLocalhostAsync(parsedAddress, context).ConfigureAwait(false);
+ await BindLocalhostAsync(parsedAddress, context, https).ConfigureAwait(false);
}
else
{
- ListenOptions options;
if (TryCreateIPEndPoint(parsedAddress, out var endpoint))
{
options = new ListenOptions(endpoint);
@@ -216,6 +237,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
context.Addresses.Add(options.GetDisplayName());
}
+
+ if (https && options != null)
+ {
+ options.KestrelServerOptions = context.ServerOptions;
+ context.DefaultHttpsProvider.ConfigureHttps(options);
+ }
}
private interface IStrategy
@@ -229,7 +256,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
context.Logger.LogDebug(CoreStrings.BindingToDefaultAddress, Constants.DefaultServerAddress);
- await BindLocalhostAsync(ServerAddress.FromUrl(Constants.DefaultServerAddress), context).ConfigureAwait(false);
+ await BindLocalhostAsync(ServerAddress.FromUrl(Constants.DefaultServerAddress), context, https: false).ConfigureAwait(false);
}
}
@@ -305,5 +332,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
}
}
}
+
+ private class UnconfiguredDefaultHttpsProvider : IDefaultHttpsProvider
+ {
+ public static readonly UnconfiguredDefaultHttpsProvider Instance = new UnconfiguredDefaultHttpsProvider();
+
+ private UnconfiguredDefaultHttpsProvider()
+ {
+ }
+
+ public void ConfigureHttps(ListenOptions listenOptions)
+ {
+ // We have to throw here. If this is called, it's because the user asked for "https" binding but for some
+ // reason didn't provide a certificate and didn't use the "DefaultHttpsProvider". This means if we no-op,
+ // we'll silently downgrade to HTTP, which is bad.
+ throw new InvalidOperationException(CoreStrings.UnableToConfigureHttpsBindings);
+ }
+ }
}
}
diff --git a/src/Kestrel.Core/Internal/IDefaultHttpsProvider.cs b/src/Kestrel.Core/Internal/IDefaultHttpsProvider.cs
new file mode 100644
index 0000000000..3ed67b0cda
--- /dev/null
+++ b/src/Kestrel.Core/Internal/IDefaultHttpsProvider.cs
@@ -0,0 +1,10 @@
+// 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.Server.Kestrel.Core.Internal
+{
+ public interface IDefaultHttpsProvider
+ {
+ void ConfigureHttps(ListenOptions listenOptions);
+ }
+}
diff --git a/src/Kestrel.Core/Internal/KestrelServerOptionsSetup.cs b/src/Kestrel.Core/Internal/KestrelServerOptionsSetup.cs
index 18c96e2039..4d7a95ca5b 100644
--- a/src/Kestrel.Core/Internal/KestrelServerOptionsSetup.cs
+++ b/src/Kestrel.Core/Internal/KestrelServerOptionsSetup.cs
@@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
diff --git a/src/Kestrel.Core/KestrelServer.cs b/src/Kestrel.Core/KestrelServer.cs
index fac999d904..81536a44ab 100644
--- a/src/Kestrel.Core/KestrelServer.cs
+++ b/src/Kestrel.Core/KestrelServer.cs
@@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
private readonly List _transports = new List();
private readonly Heartbeat _heartbeat;
private readonly IServerAddressesFeature _serverAddresses;
+ private readonly IDefaultHttpsProvider _defaultHttpsProvider;
private readonly ITransportFactory _transportFactory;
private bool _hasStarted;
@@ -33,6 +34,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
}
+ public KestrelServer(IOptions options, ITransportFactory transportFactory, ILoggerFactory loggerFactory, IDefaultHttpsProvider defaultHttpsProvider)
+ : this(transportFactory, CreateServiceContext(options, loggerFactory))
+ {
+ _defaultHttpsProvider = defaultHttpsProvider;
+ }
+
// For testing
internal KestrelServer(ITransportFactory transportFactory, ServiceContext serviceContext)
{
@@ -152,7 +159,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
await transport.BindAsync().ConfigureAwait(false);
}
- await AddressBinder.BindAsync(_serverAddresses, Options.ListenOptions, Trace, OnBind).ConfigureAwait(false);
+ await AddressBinder.BindAsync(_serverAddresses, Options, Trace, _defaultHttpsProvider, OnBind).ConfigureAwait(false);
}
catch (Exception ex)
{
diff --git a/src/Kestrel.Core/Properties/CoreStrings.Designer.cs b/src/Kestrel.Core/Properties/CoreStrings.Designer.cs
index 346b18a969..9321f1a2c9 100644
--- a/src/Kestrel.Core/Properties/CoreStrings.Designer.cs
+++ b/src/Kestrel.Core/Properties/CoreStrings.Designer.cs
@@ -1620,6 +1620,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal static string FormatHttp2ErrorConnectionSpecificHeaderField()
=> GetString("Http2ErrorConnectionSpecificHeaderField");
+ ///
+ /// Unable to configure default https bindings because no IDefaultHttpsProvider service was provided.
+ ///
+ internal static string UnableToConfigureHttpsBindings
+ {
+ get => GetString("UnableToConfigureHttpsBindings");
+ }
+
+ ///
+ /// Unable to configure default https bindings because no IDefaultHttpsProvider service was provided.
+ ///
+ internal static string FormatUnableToConfigureHttpsBindings()
+ => GetString("UnableToConfigureHttpsBindings");
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Kestrel.Https/ListenOptionsHttpsExtensions.cs b/src/Kestrel.Https/ListenOptionsHttpsExtensions.cs
index 7cf8ab1b9e..44463ea10e 100644
--- a/src/Kestrel.Https/ListenOptionsHttpsExtensions.cs
+++ b/src/Kestrel.Https/ListenOptionsHttpsExtensions.cs
@@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Hosting
{
///
- /// Extension methods fro that configure Kestrel to use HTTPS for a given endpoint.
+ /// Extension methods for that configure Kestrel to use HTTPS for a given endpoint.
///
public static class ListenOptionsHttpsExtensions
{
diff --git a/src/Kestrel/Internal/DefaultHttpsProvider.cs b/src/Kestrel/Internal/DefaultHttpsProvider.cs
new file mode 100644
index 0000000000..efc7d9fb10
--- /dev/null
+++ b/src/Kestrel/Internal/DefaultHttpsProvider.cs
@@ -0,0 +1,42 @@
+// 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.Linq;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.AspNetCore.Certificates.Generation;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Internal
+{
+ public class DefaultHttpsProvider : IDefaultHttpsProvider
+ {
+ private static readonly CertificateManager _certificateManager = new CertificateManager();
+
+ private readonly ILogger _logger;
+
+ public DefaultHttpsProvider(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public void ConfigureHttps(ListenOptions listenOptions)
+ {
+ var certificate = _certificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true)
+ .FirstOrDefault();
+ if (certificate != null)
+ {
+ _logger.LocatedDevelopmentCertificate(certificate);
+ listenOptions.UseHttps(certificate);
+ }
+ else
+ {
+ _logger.UnableToLocateDevelopmentCertificate();
+ throw new InvalidOperationException(KestrelStrings.HttpsUrlProvidedButNoDevelopmentCertificateFound);
+ }
+ }
+ }
+}
diff --git a/src/Kestrel/Internal/LoggerExtensions.cs b/src/Kestrel/Internal/LoggerExtensions.cs
new file mode 100644
index 0000000000..218f50ca10
--- /dev/null
+++ b/src/Kestrel/Internal/LoggerExtensions.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Internal
+{
+ internal static class LoggerExtensions
+ {
+ // Category: DefaultHttpsProvider
+ private static readonly Action _locatedDevelopmentCertificate =
+ LoggerMessage.Define(LogLevel.Debug, new EventId(0, nameof(LocatedDevelopmentCertificate)), "Using development certificate: {certificateSubjectName} (Thumbprint: {certificateThumbprint})");
+
+ private static readonly Action _unableToLocateDevelopmentCertificate =
+ LoggerMessage.Define(LogLevel.Error, new EventId(1, nameof(UnableToLocateDevelopmentCertificate)), "Unable to locate an appropriate development https certificate.");
+
+ public static void LocatedDevelopmentCertificate(this ILogger logger, X509Certificate2 certificate) => _locatedDevelopmentCertificate(logger, certificate.Subject, certificate.Thumbprint, null);
+
+ public static void UnableToLocateDevelopmentCertificate(this ILogger logger) => _unableToLocateDevelopmentCertificate(logger, null);
+ }
+}
diff --git a/src/Kestrel/Kestrel.csproj b/src/Kestrel/Kestrel.csproj
index 1376f1e891..7f7f8de70b 100644
--- a/src/Kestrel/Kestrel.csproj
+++ b/src/Kestrel/Kestrel.csproj
@@ -12,10 +12,14 @@
+
+
+
+
diff --git a/src/Kestrel/KestrelStrings.resx b/src/Kestrel/KestrelStrings.resx
new file mode 100644
index 0000000000..d39f8b5166
--- /dev/null
+++ b/src/Kestrel/KestrelStrings.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Unable to configure HTTPS endpoint. Try running 'dotnet developercertificates https -t' to setup a developer certificate for use with localhost. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054
+
+
\ No newline at end of file
diff --git a/src/Kestrel/Properties/KestrelStrings.Designer.cs b/src/Kestrel/Properties/KestrelStrings.Designer.cs
new file mode 100644
index 0000000000..ede41f12cf
--- /dev/null
+++ b/src/Kestrel/Properties/KestrelStrings.Designer.cs
@@ -0,0 +1,44 @@
+//
+namespace Microsoft.AspNetCore.Server.Kestrel
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class KestrelStrings
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.Server.Kestrel.KestrelStrings", typeof(KestrelStrings).GetTypeInfo().Assembly);
+
+ ///
+ /// An 'https' URL was provided, but a development certificate could not be found.
+ ///
+ internal static string HttpsUrlProvidedButNoDevelopmentCertificateFound
+ {
+ get => GetString("HttpsUrlProvidedButNoDevelopmentCertificateFound");
+ }
+
+ ///
+ /// An 'https' URL was provided, but a development certificate could not be found.
+ ///
+ internal static string FormatHttpsUrlProvidedButNoDevelopmentCertificateFound()
+ => GetString("HttpsUrlProvidedButNoDevelopmentCertificateFound");
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/Kestrel/WebHostBuilderKestrelExtensions.cs b/src/Kestrel/WebHostBuilderKestrelExtensions.cs
index 5ed947e96a..f65a66ac4e 100644
--- a/src/Kestrel/WebHostBuilderKestrelExtensions.cs
+++ b/src/Kestrel/WebHostBuilderKestrelExtensions.cs
@@ -5,6 +5,7 @@ using System;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
+using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
using Microsoft.Extensions.DependencyInjection;
@@ -33,6 +34,7 @@ namespace Microsoft.AspNetCore.Hosting
services.AddTransient, KestrelServerOptionsSetup>();
services.AddSingleton();
+ services.AddSingleton();
});
}
diff --git a/test/Kestrel.Core.Tests/AddressBinderTests.cs b/test/Kestrel.Core.Tests/AddressBinderTests.cs
index 2c957e6030..d42a598e9c 100644
--- a/test/Kestrel.Core.Tests/AddressBinderTests.cs
+++ b/test/Kestrel.Core.Tests/AddressBinderTests.cs
@@ -8,9 +8,9 @@ using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Protocols;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
@@ -55,12 +55,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
var addresses = new ServerAddressesFeature();
addresses.Addresses.Add($"http://{host}");
- var options = new List();
+ var options = new KestrelServerOptions();
var tcs = new TaskCompletionSource();
await AddressBinder.BindAsync(addresses,
options,
NullLogger.Instance,
+ Mock.Of(),
endpoint =>
{
tcs.TrySetResult(endpoint);
@@ -75,13 +76,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
var addresses = new ServerAddressesFeature();
addresses.Addresses.Add("http://localhost:5000");
- var options = new List();
+ var options = new KestrelServerOptions();
await Assert.ThrowsAsync(() =>
AddressBinder.BindAsync(addresses,
- options,
- NullLogger.Instance,
- endpoint => throw new AddressInUseException("already in use")));
+ options,
+ NullLogger.Instance,
+ Mock.Of(),
+ endpoint => throw new AddressInUseException("already in use")));
}
[Theory]
@@ -93,7 +95,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var logger = new MockLogger();
var addresses = new ServerAddressesFeature();
addresses.Addresses.Add(address);
- var options = new List();
+ var options = new KestrelServerOptions();
var ipV6Attempt = false;
var ipV4Attempt = false;
@@ -101,6 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await AddressBinder.BindAsync(addresses,
options,
logger,
+ Mock.Of(),
endpoint =>
{
if (endpoint.IPEndPoint.Address == IPAddress.IPv6Any)
diff --git a/test/Kestrel.Core.Tests/KestrelServerTests.cs b/test/Kestrel.Core.Tests/KestrelServerTests.cs
index c6ae3b5a7a..51e9de0221 100644
--- a/test/Kestrel.Core.Tests/KestrelServerTests.cs
+++ b/test/Kestrel.Core.Tests/KestrelServerTests.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
@@ -37,20 +38,56 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}
[Fact]
- public void StartWithHttpsAddressThrows()
+ public void StartWithHttpsAddressConfiguresHttpsEndpoints()
{
- var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false };
+ var mockDefaultHttpsProvider = new Mock();
- using (var server = CreateServer(new KestrelServerOptions(), testLogger))
+ using (var server = CreateServer(new KestrelServerOptions(), mockDefaultHttpsProvider.Object))
{
server.Features.Get().Addresses.Add("https://127.0.0.1:0");
- var exception = Assert.Throws(() => StartDummyApplication(server));
+ StartDummyApplication(server);
- Assert.Equal(
- $"HTTPS endpoints can only be configured using {nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.Listen)}().",
- exception.Message);
- Assert.Equal(1, testLogger.CriticalErrorsLogged);
+ mockDefaultHttpsProvider.Verify(provider => provider.ConfigureHttps(It.IsAny()), Times.Once);
+ }
+ }
+
+ [Fact]
+ public void KestrelServerThrowsUsefulExceptionIfDefaultHttpsProviderNotAdded()
+ {
+ using (var server = CreateServer(new KestrelServerOptions(), defaultHttpsProvider: null, throwOnCriticalErrors: false))
+ {
+ server.Features.Get().Addresses.Add("https://127.0.0.1:0");
+
+ var ex = Assert.Throws(() => StartDummyApplication(server));
+ Assert.Equal(CoreStrings.UnableToConfigureHttpsBindings, ex.Message);
+ }
+ }
+
+ [Fact]
+ public void KestrelServerDoesNotThrowIfNoDefaultHttpsProviderButNoHttpUrls()
+ {
+ using (var server = CreateServer(new KestrelServerOptions(), defaultHttpsProvider: null))
+ {
+ server.Features.Get().Addresses.Add("http://127.0.0.1:0");
+
+ StartDummyApplication(server);
+ }
+ }
+
+ [Fact]
+ public void KestrelServerDoesNotThrowIfNoDefaultHttpsProviderButManualListenOptions()
+ {
+ var mockDefaultHttpsProvider = new Mock();
+
+ var serverOptions = new KestrelServerOptions();
+ serverOptions.Listen(new IPEndPoint(IPAddress.Loopback, 0));
+
+ using (var server = CreateServer(serverOptions, defaultHttpsProvider: null))
+ {
+ server.Features.Get().Addresses.Add("https://127.0.0.1:0");
+
+ StartDummyApplication(server);
}
}
@@ -274,6 +311,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
return new KestrelServer(Options.Create(options), new MockTransportFactory(), new LoggerFactory(new[] { new KestrelTestLoggerProvider(testLogger) }));
}
+ private static KestrelServer CreateServer(KestrelServerOptions options, IDefaultHttpsProvider defaultHttpsProvider, bool throwOnCriticalErrors = true)
+ {
+ return new KestrelServer(Options.Create(options), new MockTransportFactory(), new LoggerFactory(new[] { new KestrelTestLoggerProvider(throwOnCriticalErrors) }), defaultHttpsProvider);
+ }
+
private static void StartDummyApplication(IServer server)
{
server.StartAsync(new DummyApplication(context => Task.CompletedTask), CancellationToken.None).GetAwaiter().GetResult();
diff --git a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs
index 7b9c9bdcda..446a201f71 100644
--- a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs
+++ b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs
@@ -511,8 +511,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
[Theory]
- [InlineData("https://localhost")]
[InlineData("ftp://localhost")]
+ [InlineData("ssh://localhost")]
public void ThrowsForUnsupportedAddressFromHosting(string addr)
{
var hostBuilder = TransportSelector.GetWebHostBuilder()
diff --git a/test/shared/KestrelTestLoggerProvider.cs b/test/shared/KestrelTestLoggerProvider.cs
index 45462791d1..48455557fb 100644
--- a/test/shared/KestrelTestLoggerProvider.cs
+++ b/test/shared/KestrelTestLoggerProvider.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// 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;
@@ -10,8 +10,8 @@ namespace Microsoft.AspNetCore.Testing
{
private readonly ILogger _testLogger;
- public KestrelTestLoggerProvider()
- : this(new TestApplicationErrorLogger())
+ public KestrelTestLoggerProvider(bool throwOnCriticalErrors = true)
+ : this(new TestApplicationErrorLogger() { ThrowOnCriticalErrors = throwOnCriticalErrors })
{
}
@@ -30,4 +30,4 @@ namespace Microsoft.AspNetCore.Testing
throw new NotImplementedException();
}
}
-}
\ No newline at end of file
+}