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"?>
<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.
-->
@ -559,7 +559,8 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
</data>
<data name="HttpParserTlsOverHttpError" xml:space="preserve">
<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>
</data>
<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">
<value>Decoded string length of {length} octets is greater than the configured maximum length of {maxStringLength} octets.</value>
</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 IServerAddressesFeature _serverAddresses;
private readonly IEnumerable<IConnectionListenerFactory> _transportFactories;
private readonly List<IConnectionListenerFactory> _transportFactories;
private bool _hasStarted;
private int _stopping;
@ -42,7 +42,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw new ArgumentNullException(nameof(transportFactories));
}
_transportFactories = transportFactories;
_transportFactories = transportFactories.ToList();
if (_transportFactories.Count == 0)
{
throw new InvalidOperationException(CoreStrings.TransportNotFound);
}
ServiceContext = serviceContext;
Features = new FeatureCollection();
@ -143,19 +149,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
if (transportFactory is IMultiplexedConnectionListenerFactory)
{
// Don't break early. Always use the last registered factory.
factory = transportFactory;
break;
}
}
if (factory == null)
{
throw new Exception("Quic transport not found when using HTTP/3");
throw new InvalidOperationException(CoreStrings.QuicTransportNotFound);
}
}
else
{
factory = _transportFactories.Single();
factory = _transportFactories.Last();
}
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 mockLogger = new Mock<ILogger>();
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"));
}
[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>(() =>
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);
}
[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]
public async Task StopAsyncCallsCompleteWhenFirstCallCompletes()
{
@ -456,5 +479,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
return new ValueTask<IConnectionListener>(mock.Object);
}
}
private class ThrowingTransportFactory : IConnectionListenerFactory
{
public ValueTask<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
{
throw new InvalidOperationException();
}
}
}
}