Allow more than one IConnectionListenerFactory (#17383)

This commit is contained in:
Stephen Halter 2019-11-25 14:19:25 -08:00 committed by GitHub
parent cf116a7815
commit b81c1a7954
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 40 deletions

View File

@ -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.
--> -->
@ -559,7 +559,8 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
</data> </data>
<data name="HttpParserTlsOverHttpError" xml:space="preserve"> <data name="HttpParserTlsOverHttpError" xml:space="preserve">
<value>Detected a TLS handshake to an endpoint that does not have TLS enabled.</value> <value>Detected a TLS handshake to an endpoint that does not have TLS enabled.</value>
</data> <data name="BadDeveloperCertificateState" xml:space="preserve"> </data>
<data name="BadDeveloperCertificateState" xml:space="preserve">
<value>The ASP.NET Core developer certificate is in an invalid state. To fix this issue, run the following commands 'dotnet dev-certs https --clean' and 'dotnet dev-certs https' to remove all existing ASP.NET Core development certificates and create a new untrusted developer certificate. On macOS or Windows, use 'dotnet dev-certs https --trust' to trust the new certificate.</value> <value>The ASP.NET Core developer certificate is in an invalid state. To fix this issue, run the following commands 'dotnet dev-certs https --clean' and 'dotnet dev-certs https' to remove all existing ASP.NET Core development certificates and create a new untrusted developer certificate. On macOS or Windows, use 'dotnet dev-certs https --trust' to trust the new certificate.</value>
</data> </data>
<data name="QPackErrorIndexOutOfRange" xml:space="preserve"> <data name="QPackErrorIndexOutOfRange" xml:space="preserve">
@ -574,4 +575,10 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
<data name="QPackStringLengthTooLarge" xml:space="preserve"> <data name="QPackStringLengthTooLarge" xml:space="preserve">
<value>Decoded string length of {length} octets is greater than the configured maximum length of {maxStringLength} octets.</value> <value>Decoded string length of {length} octets is greater than the configured maximum length of {maxStringLength} octets.</value>
</data> </data>
</root> <data name="QuicTransportNotFound" xml:space="preserve">
<value>Quic transport not found when using HTTP/3.</value>
</data>
<data name="TransportNotFound" xml:space="preserve">
<value>Unable to resolve service for type 'Microsoft.AspNetCore.Connections.IConnectionListenerFactory' while attempting to activate 'Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer'.</value>
</data>
</root>

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
{ {
private readonly List<(IConnectionListener, Task)> _transports = new List<(IConnectionListener, Task)>(); private readonly List<(IConnectionListener, Task)> _transports = new List<(IConnectionListener, Task)>();
private readonly IServerAddressesFeature _serverAddresses; private readonly IServerAddressesFeature _serverAddresses;
private readonly IEnumerable<IConnectionListenerFactory> _transportFactories; private readonly List<IConnectionListenerFactory> _transportFactories;
private bool _hasStarted; private bool _hasStarted;
private int _stopping; private int _stopping;
@ -42,7 +42,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw new ArgumentNullException(nameof(transportFactories)); throw new ArgumentNullException(nameof(transportFactories));
} }
_transportFactories = transportFactories; _transportFactories = transportFactories.ToList();
if (_transportFactories.Count == 0)
{
throw new InvalidOperationException(CoreStrings.TransportNotFound);
}
ServiceContext = serviceContext; ServiceContext = serviceContext;
Features = new FeatureCollection(); Features = new FeatureCollection();
@ -143,19 +149,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
{ {
if (transportFactory is IMultiplexedConnectionListenerFactory) if (transportFactory is IMultiplexedConnectionListenerFactory)
{ {
// Don't break early. Always use the last registered factory.
factory = transportFactory; factory = transportFactory;
break;
} }
} }
if (factory == null) if (factory == null)
{ {
throw new Exception("Quic transport not found when using HTTP/3"); throw new InvalidOperationException(CoreStrings.QuicTransportNotFound);
} }
} }
else else
{ {
factory = _transportFactories.Single(); factory = _transportFactories.Last();
} }
var transport = await factory.BindAsync(options.EndPoint).ConfigureAwait(false); var transport = await factory.BindAsync(options.EndPoint).ConfigureAwait(false);

View File

@ -204,22 +204,45 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockLoggerFactory = new Mock<ILoggerFactory>(); var mockLoggerFactory = new Mock<ILoggerFactory>();
var mockLogger = new Mock<ILogger>(); var mockLogger = new Mock<ILogger>();
mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny<string>())).Returns(mockLogger.Object); mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny<string>())).Returns(mockLogger.Object);
new KestrelServer(Options.Create<KestrelServerOptions>(null), Mock.Of<IEnumerable<IConnectionListenerFactory>>(), mockLoggerFactory.Object); new KestrelServer(Options.Create<KestrelServerOptions>(null), new List<IConnectionListenerFactory>() { new MockTransportFactory() }, mockLoggerFactory.Object);
mockLoggerFactory.Verify(factory => factory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel")); mockLoggerFactory.Verify(factory => factory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel"));
} }
[Fact] [Fact]
public void StartWithNoTransportFactoryThrows() public void ConstructorWithNullTransportFactoriesThrows()
{ {
var mockLoggerFactory = new Mock<ILoggerFactory>();
var mockLogger = new Mock<ILogger>();
mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny<string>())).Returns(mockLogger.Object);
var exception = Assert.Throws<ArgumentNullException>(() => var exception = Assert.Throws<ArgumentNullException>(() =>
new KestrelServer(Options.Create<KestrelServerOptions>(null), null, mockLoggerFactory.Object)); new KestrelServer(
Options.Create<KestrelServerOptions>(null),
null,
new LoggerFactory(new[] { new KestrelTestLoggerProvider() })));
Assert.Equal("transportFactories", exception.ParamName); Assert.Equal("transportFactories", exception.ParamName);
} }
[Fact]
public void ConstructorWithNoTransportFactoriesThrows()
{
var exception = Assert.Throws<InvalidOperationException>(() =>
new KestrelServer(
Options.Create<KestrelServerOptions>(null),
new List<IConnectionListenerFactory>(),
new LoggerFactory(new[] { new KestrelTestLoggerProvider() })));
Assert.Equal(CoreStrings.TransportNotFound, exception.Message);
}
[Fact]
public void StartWithMultipleTransportFactoriesDoesNotThrow()
{
using var server = new KestrelServer(
Options.Create(CreateServerOptions()),
new List<IConnectionListenerFactory>() { new ThrowingTransportFactory(), new MockTransportFactory() },
new LoggerFactory(new[] { new KestrelTestLoggerProvider() }));
StartDummyApplication(server);
}
[Fact] [Fact]
public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() public async Task StopAsyncCallsCompleteWhenFirstCallCompletes()
{ {
@ -456,5 +479,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
return new ValueTask<IConnectionListener>(mock.Object); return new ValueTask<IConnectionListener>(mock.Object);
} }
} }
private class ThrowingTransportFactory : IConnectionListenerFactory
{
public ValueTask<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
{
throw new InvalidOperationException();
}
}
} }
} }