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"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
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
|
||||
|
||||
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>
|
||||
|
|
@ -26,36 +26,36 @@
|
|||
<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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -462,4 +462,7 @@
|
|||
<data name="Http2ErrorConnectionSpecificHeaderField" xml:space="preserve">
|
||||
<value>Request headers contain connection-specific header field.</value>
|
||||
</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.
|
||||
|
||||
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> listenOptions,
|
||||
KestrelServerOptions serverOptions,
|
||||
ILogger logger,
|
||||
IDefaultHttpsProvider defaultHttpsProvider,
|
||||
Func<ListenOptions, Task> 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<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; }
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
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 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<KestrelServerOptions> 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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1620,6 +1620,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
internal static string FormatHttp2ErrorConnectionSpecificHeaderField()
|
||||
=> 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)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging;
|
|||
namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
/// <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>
|
||||
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>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Certificates.Generation.Sources" />
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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 -->
|
||||
<ProjectReference Include="..\Kestrel.Transport.Libuv\Kestrel.Transport.Libuv.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.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<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
|
||||
services.AddSingleton<IServer, KestrelServer>();
|
||||
services.AddSingleton<IDefaultHttpsProvider, DefaultHttpsProvider>();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ListenOptions>();
|
||||
var options = new KestrelServerOptions();
|
||||
|
||||
var tcs = new TaskCompletionSource<ListenOptions>();
|
||||
await AddressBinder.BindAsync(addresses,
|
||||
options,
|
||||
NullLogger.Instance,
|
||||
Mock.Of<IDefaultHttpsProvider>(),
|
||||
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<ListenOptions>();
|
||||
var options = new KestrelServerOptions();
|
||||
|
||||
await Assert.ThrowsAsync<IOException>(() =>
|
||||
AddressBinder.BindAsync(addresses,
|
||||
options,
|
||||
NullLogger.Instance,
|
||||
endpoint => throw new AddressInUseException("already in use")));
|
||||
options,
|
||||
NullLogger.Instance,
|
||||
Mock.Of<IDefaultHttpsProvider>(),
|
||||
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<ListenOptions>();
|
||||
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<IDefaultHttpsProvider>(),
|
||||
endpoint =>
|
||||
{
|
||||
if (endpoint.IPEndPoint.Address == IPAddress.IPv6Any)
|
||||
|
|
|
|||
|
|
@ -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<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");
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => 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<ListenOptions>()), Times.Once);
|
||||
}
|
||||
}
|
||||
|
||||
[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) }));
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue