Add "zero config" HTTPS support using local development certificate. (#2093)
This commit is contained in:
parent
c3ba875d12
commit
8c4bdbcf6b
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
|
|
@ -26,36 +26,36 @@
|
||||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -462,4 +462,7 @@
|
||||||
<data name="Http2ErrorConnectionSpecificHeaderField" xml:space="preserve">
|
<data name="Http2ErrorConnectionSpecificHeaderField" xml:space="preserve">
|
||||||
<value>Request headers contain connection-specific header field.</value>
|
<value>Request headers contain connection-specific header field.</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
<data name="UnableToConfigureHttpsBindings" xml:space="preserve">
|
||||||
|
<value>Unable to configure default https bindings because no IDefaultHttpsProvider service was provided.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -18,10 +18,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
internal class AddressBinder
|
internal class AddressBinder
|
||||||
{
|
{
|
||||||
public static async Task BindAsync(IServerAddressesFeature addresses,
|
public static async Task BindAsync(IServerAddressesFeature addresses,
|
||||||
List<ListenOptions> listenOptions,
|
KestrelServerOptions serverOptions,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
|
IDefaultHttpsProvider defaultHttpsProvider,
|
||||||
Func<ListenOptions, Task> createBinding)
|
Func<ListenOptions, Task> createBinding)
|
||||||
{
|
{
|
||||||
|
var listenOptions = serverOptions.ListenOptions;
|
||||||
var strategy = CreateStrategy(
|
var strategy = CreateStrategy(
|
||||||
listenOptions.ToArray(),
|
listenOptions.ToArray(),
|
||||||
addresses.Addresses.ToArray(),
|
addresses.Addresses.ToArray(),
|
||||||
|
|
@ -31,7 +33,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
{
|
{
|
||||||
Addresses = addresses.Addresses,
|
Addresses = addresses.Addresses,
|
||||||
ListenOptions = listenOptions,
|
ListenOptions = listenOptions,
|
||||||
|
ServerOptions = serverOptions,
|
||||||
Logger = logger,
|
Logger = logger,
|
||||||
|
DefaultHttpsProvider = defaultHttpsProvider ?? UnconfiguredDefaultHttpsProvider.Instance,
|
||||||
CreateBinding = createBinding
|
CreateBinding = createBinding
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -47,7 +51,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
{
|
{
|
||||||
public ICollection<string> Addresses { get; set; }
|
public ICollection<string> Addresses { get; set; }
|
||||||
public List<ListenOptions> ListenOptions { get; set; }
|
public List<ListenOptions> ListenOptions { get; set; }
|
||||||
|
public KestrelServerOptions ServerOptions { get; set; }
|
||||||
public ILogger Logger { get; set; }
|
public ILogger Logger { get; set; }
|
||||||
|
public IDefaultHttpsProvider DefaultHttpsProvider { get; set; }
|
||||||
|
|
||||||
public Func<ListenOptions, Task> CreateBinding { get; set; }
|
public Func<ListenOptions, Task> CreateBinding { get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +126,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)
|
private static async Task BindLocalhostAsync(ServerAddress address, AddressBindContext context, bool https)
|
||||||
{
|
{
|
||||||
if (address.Port == 0)
|
if (address.Port == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -131,7 +137,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
||||||
try
|
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))
|
catch (Exception ex) when (!(ex is IOException))
|
||||||
{
|
{
|
||||||
|
|
@ -141,7 +154,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
||||||
try
|
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))
|
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)
|
private static async Task BindAddressAsync(string address, AddressBindContext context)
|
||||||
{
|
{
|
||||||
var parsedAddress = ServerAddress.FromUrl(address);
|
var parsedAddress = ServerAddress.FromUrl(address);
|
||||||
|
var https = false;
|
||||||
|
|
||||||
if (parsedAddress.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
|
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))
|
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()"));
|
throw new InvalidOperationException(CoreStrings.FormatConfigurePathBaseFromMethodCall($"{nameof(IApplicationBuilder)}.UsePathBase()"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ListenOptions options = null;
|
||||||
if (parsedAddress.IsUnixPipe)
|
if (parsedAddress.IsUnixPipe)
|
||||||
{
|
{
|
||||||
var endPoint = new ListenOptions(parsedAddress.UnixPipePath);
|
options = new ListenOptions(parsedAddress.UnixPipePath);
|
||||||
await BindEndpointAsync(endPoint, context).ConfigureAwait(false);
|
await BindEndpointAsync(options, context).ConfigureAwait(false);
|
||||||
context.Addresses.Add(endPoint.GetDisplayName());
|
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).ConfigureAwait(false);
|
await BindLocalhostAsync(parsedAddress, context, https).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ListenOptions options;
|
|
||||||
if (TryCreateIPEndPoint(parsedAddress, out var endpoint))
|
if (TryCreateIPEndPoint(parsedAddress, out var endpoint))
|
||||||
{
|
{
|
||||||
options = new ListenOptions(endpoint);
|
options = new ListenOptions(endpoint);
|
||||||
|
|
@ -216,6 +237,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
||||||
context.Addresses.Add(options.GetDisplayName());
|
context.Addresses.Add(options.GetDisplayName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (https && options != null)
|
||||||
|
{
|
||||||
|
options.KestrelServerOptions = context.ServerOptions;
|
||||||
|
context.DefaultHttpsProvider.ConfigureHttps(options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface IStrategy
|
private interface IStrategy
|
||||||
|
|
@ -229,7 +256,7 @@ 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).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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
// 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 Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
private readonly List<ITransport> _transports = new List<ITransport>();
|
private readonly List<ITransport> _transports = new List<ITransport>();
|
||||||
private readonly Heartbeat _heartbeat;
|
private readonly Heartbeat _heartbeat;
|
||||||
private readonly IServerAddressesFeature _serverAddresses;
|
private readonly IServerAddressesFeature _serverAddresses;
|
||||||
|
private readonly IDefaultHttpsProvider _defaultHttpsProvider;
|
||||||
private readonly ITransportFactory _transportFactory;
|
private readonly ITransportFactory _transportFactory;
|
||||||
|
|
||||||
private bool _hasStarted;
|
private bool _hasStarted;
|
||||||
|
|
@ -33,6 +34,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KestrelServer(IOptions<KestrelServerOptions> options, ITransportFactory transportFactory, ILoggerFactory loggerFactory, IDefaultHttpsProvider defaultHttpsProvider)
|
||||||
|
: this(transportFactory, CreateServiceContext(options, loggerFactory))
|
||||||
|
{
|
||||||
|
_defaultHttpsProvider = defaultHttpsProvider;
|
||||||
|
}
|
||||||
|
|
||||||
// For testing
|
// For testing
|
||||||
internal KestrelServer(ITransportFactory transportFactory, ServiceContext serviceContext)
|
internal KestrelServer(ITransportFactory transportFactory, ServiceContext serviceContext)
|
||||||
{
|
{
|
||||||
|
|
@ -152,7 +159,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
await transport.BindAsync().ConfigureAwait(false);
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1620,6 +1620,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
internal static string FormatHttp2ErrorConnectionSpecificHeaderField()
|
internal static string FormatHttp2ErrorConnectionSpecificHeaderField()
|
||||||
=> GetString("Http2ErrorConnectionSpecificHeaderField");
|
=> GetString("Http2ErrorConnectionSpecificHeaderField");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unable to configure default https bindings because no IDefaultHttpsProvider service was provided.
|
||||||
|
/// </summary>
|
||||||
|
internal static string UnableToConfigureHttpsBindings
|
||||||
|
{
|
||||||
|
get => GetString("UnableToConfigureHttpsBindings");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unable to configure default https bindings because no IDefaultHttpsProvider service was provided.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatUnableToConfigureHttpsBindings()
|
||||||
|
=> GetString("UnableToConfigureHttpsBindings");
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging;
|
||||||
namespace Microsoft.AspNetCore.Hosting
|
namespace Microsoft.AspNetCore.Hosting
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extension methods fro <see cref="ListenOptions"/> that configure Kestrel to use HTTPS for a given endpoint.
|
/// Extension methods for <see cref="ListenOptions"/> that configure Kestrel to use HTTPS for a given endpoint.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ListenOptionsHttpsExtensions
|
public static class ListenOptionsHttpsExtensions
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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<DefaultHttpsProvider> _logger;
|
||||||
|
|
||||||
|
public DefaultHttpsProvider(ILogger<DefaultHttpsProvider> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<ILogger, string, string, Exception> _locatedDevelopmentCertificate =
|
||||||
|
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(0, nameof(LocatedDevelopmentCertificate)), "Using development certificate: {certificateSubjectName} (Thumbprint: {certificateThumbprint})");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, Exception> _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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,10 +12,14 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Certificates.Generation.Sources" />
|
||||||
|
<PackageReference Include="System.Security.Cryptography.Cng" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Kestrel.Core\Kestrel.Core.csproj" />
|
<ProjectReference Include="..\Kestrel.Core\Kestrel.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\Kestrel.Https\Kestrel.Https.csproj" />
|
||||||
|
|
||||||
<!-- Even though the Libuv transport is no longer used by default, it remains for back-compat -->
|
<!-- Even though the Libuv transport is no longer used by default, it remains for back-compat -->
|
||||||
<ProjectReference Include="..\Kestrel.Transport.Libuv\Kestrel.Transport.Libuv.csproj" />
|
<ProjectReference Include="..\Kestrel.Transport.Libuv\Kestrel.Transport.Libuv.csproj" />
|
||||||
<ProjectReference Include="..\Kestrel.Transport.Sockets\Kestrel.Transport.Sockets.csproj" />
|
<ProjectReference Include="..\Kestrel.Transport.Sockets\Kestrel.Transport.Sockets.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="HttpsUrlProvidedButNoDevelopmentCertificateFound" xml:space="preserve">
|
||||||
|
<value>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</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// <auto-generated />
|
||||||
|
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);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An 'https' URL was provided, but a development certificate could not be found.
|
||||||
|
/// </summary>
|
||||||
|
internal static string HttpsUrlProvidedButNoDevelopmentCertificateFound
|
||||||
|
{
|
||||||
|
get => GetString("HttpsUrlProvidedButNoDevelopmentCertificateFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An 'https' URL was provided, but a development certificate could not be found.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
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.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -33,6 +34,7 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
|
|
||||||
services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
|
services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
|
||||||
services.AddSingleton<IServer, KestrelServer>();
|
services.AddSingleton<IServer, KestrelServer>();
|
||||||
|
services.AddSingleton<IDefaultHttpsProvider, DefaultHttpsProvider>();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ 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 Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
@ -55,12 +55,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
var addresses = new ServerAddressesFeature();
|
var addresses = new ServerAddressesFeature();
|
||||||
addresses.Addresses.Add($"http://{host}");
|
addresses.Addresses.Add($"http://{host}");
|
||||||
var options = new List<ListenOptions>();
|
var options = new KestrelServerOptions();
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<ListenOptions>();
|
var tcs = new TaskCompletionSource<ListenOptions>();
|
||||||
await AddressBinder.BindAsync(addresses,
|
await AddressBinder.BindAsync(addresses,
|
||||||
options,
|
options,
|
||||||
NullLogger.Instance,
|
NullLogger.Instance,
|
||||||
|
Mock.Of<IDefaultHttpsProvider>(),
|
||||||
endpoint =>
|
endpoint =>
|
||||||
{
|
{
|
||||||
tcs.TrySetResult(endpoint);
|
tcs.TrySetResult(endpoint);
|
||||||
|
|
@ -75,13 +76,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
var addresses = new ServerAddressesFeature();
|
var addresses = new ServerAddressesFeature();
|
||||||
addresses.Addresses.Add("http://localhost:5000");
|
addresses.Addresses.Add("http://localhost:5000");
|
||||||
var options = new List<ListenOptions>();
|
var options = new KestrelServerOptions();
|
||||||
|
|
||||||
await Assert.ThrowsAsync<IOException>(() =>
|
await Assert.ThrowsAsync<IOException>(() =>
|
||||||
AddressBinder.BindAsync(addresses,
|
AddressBinder.BindAsync(addresses,
|
||||||
options,
|
options,
|
||||||
NullLogger.Instance,
|
NullLogger.Instance,
|
||||||
endpoint => throw new AddressInUseException("already in use")));
|
Mock.Of<IDefaultHttpsProvider>(),
|
||||||
|
endpoint => throw new AddressInUseException("already in use")));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -93,7 +95,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
var logger = new MockLogger();
|
var logger = new MockLogger();
|
||||||
var addresses = new ServerAddressesFeature();
|
var addresses = new ServerAddressesFeature();
|
||||||
addresses.Addresses.Add(address);
|
addresses.Addresses.Add(address);
|
||||||
var options = new List<ListenOptions>();
|
var options = new KestrelServerOptions();
|
||||||
|
|
||||||
var ipV6Attempt = false;
|
var ipV6Attempt = false;
|
||||||
var ipV4Attempt = false;
|
var ipV4Attempt = false;
|
||||||
|
|
@ -101,6 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await AddressBinder.BindAsync(addresses,
|
await AddressBinder.BindAsync(addresses,
|
||||||
options,
|
options,
|
||||||
logger,
|
logger,
|
||||||
|
Mock.Of<IDefaultHttpsProvider>(),
|
||||||
endpoint =>
|
endpoint =>
|
||||||
{
|
{
|
||||||
if (endpoint.IPEndPoint.Address == IPAddress.IPv6Any)
|
if (endpoint.IPEndPoint.Address == IPAddress.IPv6Any)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -37,20 +38,56 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StartWithHttpsAddressThrows()
|
public void StartWithHttpsAddressConfiguresHttpsEndpoints()
|
||||||
{
|
{
|
||||||
var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false };
|
var mockDefaultHttpsProvider = new Mock<IDefaultHttpsProvider>();
|
||||||
|
|
||||||
using (var server = CreateServer(new KestrelServerOptions(), testLogger))
|
using (var server = CreateServer(new KestrelServerOptions(), mockDefaultHttpsProvider.Object))
|
||||||
{
|
{
|
||||||
server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://127.0.0.1:0");
|
server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://127.0.0.1:0");
|
||||||
|
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() => StartDummyApplication(server));
|
StartDummyApplication(server);
|
||||||
|
|
||||||
Assert.Equal(
|
mockDefaultHttpsProvider.Verify(provider => provider.ConfigureHttps(It.IsAny<ListenOptions>()), Times.Once);
|
||||||
$"HTTPS endpoints can only be configured using {nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.Listen)}().",
|
}
|
||||||
exception.Message);
|
}
|
||||||
Assert.Equal(1, testLogger.CriticalErrorsLogged);
|
|
||||||
|
[Fact]
|
||||||
|
public void KestrelServerThrowsUsefulExceptionIfDefaultHttpsProviderNotAdded()
|
||||||
|
{
|
||||||
|
using (var server = CreateServer(new KestrelServerOptions(), defaultHttpsProvider: null, throwOnCriticalErrors: false))
|
||||||
|
{
|
||||||
|
server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://127.0.0.1:0");
|
||||||
|
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => StartDummyApplication(server));
|
||||||
|
Assert.Equal(CoreStrings.UnableToConfigureHttpsBindings, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void KestrelServerDoesNotThrowIfNoDefaultHttpsProviderButNoHttpUrls()
|
||||||
|
{
|
||||||
|
using (var server = CreateServer(new KestrelServerOptions(), defaultHttpsProvider: null))
|
||||||
|
{
|
||||||
|
server.Features.Get<IServerAddressesFeature>().Addresses.Add("http://127.0.0.1:0");
|
||||||
|
|
||||||
|
StartDummyApplication(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void KestrelServerDoesNotThrowIfNoDefaultHttpsProviderButManualListenOptions()
|
||||||
|
{
|
||||||
|
var mockDefaultHttpsProvider = new Mock<IDefaultHttpsProvider>();
|
||||||
|
|
||||||
|
var serverOptions = new KestrelServerOptions();
|
||||||
|
serverOptions.Listen(new IPEndPoint(IPAddress.Loopback, 0));
|
||||||
|
|
||||||
|
using (var server = CreateServer(serverOptions, defaultHttpsProvider: null))
|
||||||
|
{
|
||||||
|
server.Features.Get<IServerAddressesFeature>().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) }));
|
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)
|
private static void StartDummyApplication(IServer server)
|
||||||
{
|
{
|
||||||
server.StartAsync(new DummyApplication(context => Task.CompletedTask), CancellationToken.None).GetAwaiter().GetResult();
|
server.StartAsync(new DummyApplication(context => Task.CompletedTask), CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
|
|
||||||
|
|
@ -511,8 +511,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("https://localhost")]
|
|
||||||
[InlineData("ftp://localhost")]
|
[InlineData("ftp://localhost")]
|
||||||
|
[InlineData("ssh://localhost")]
|
||||||
public void ThrowsForUnsupportedAddressFromHosting(string addr)
|
public void ThrowsForUnsupportedAddressFromHosting(string addr)
|
||||||
{
|
{
|
||||||
var hostBuilder = TransportSelector.GetWebHostBuilder()
|
var hostBuilder = TransportSelector.GetWebHostBuilder()
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -10,8 +10,8 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
{
|
{
|
||||||
private readonly ILogger _testLogger;
|
private readonly ILogger _testLogger;
|
||||||
|
|
||||||
public KestrelTestLoggerProvider()
|
public KestrelTestLoggerProvider(bool throwOnCriticalErrors = true)
|
||||||
: this(new TestApplicationErrorLogger())
|
: this(new TestApplicationErrorLogger() { ThrowOnCriticalErrors = throwOnCriticalErrors })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,4 +30,4 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue