-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Support side-by-side transports in Kestrel #44657
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Net; | ||
|
||
namespace Microsoft.AspNetCore.Connections; | ||
|
||
/// <summary> | ||
/// Defines an interface that determines whether the listener factory supports binding to the specified <see cref="EndPoint"/>. | ||
/// </summary> | ||
/// <remarks> | ||
/// This interface should be implemented by <see cref="IConnectionListenerFactory"/> and <see cref="IMultiplexedConnectionListenerFactory"/> | ||
/// types that want to control want endpoint instances they can bind to. | ||
/// </remarks> | ||
public interface IConnectionListenerFactorySelector | ||
{ | ||
/// <summary> | ||
/// Returns a value that indicates whether the listener factory supports binding to the specified <see cref="EndPoint"/>. | ||
/// </summary> | ||
/// <param name="endpoint">The <see cref="EndPoint" /> to bind to.</param> | ||
/// <returns>A value that indicates whether the listener factory supports binding to the specified <see cref="EndPoint"/>.</returns> | ||
bool CanBind(EndPoint endpoint); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
#nullable enable | ||
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature | ||
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature.OnClosed(System.Action<object?>! callback, object? state) -> void | ||
Microsoft.AspNetCore.Connections.IConnectionListenerFactorySelector | ||
Microsoft.AspNetCore.Connections.IConnectionListenerFactorySelector.CanBind(System.Net.EndPoint! endpoint) -> bool |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
#nullable enable | ||
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature | ||
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature.OnClosed(System.Action<object?>! callback, object? state) -> void | ||
Microsoft.AspNetCore.Connections.IConnectionListenerFactorySelector | ||
Microsoft.AspNetCore.Connections.IConnectionListenerFactorySelector.CanBind(System.Net.EndPoint! endpoint) -> bool | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dotnet/aspnet-build It looks like we need to Update our PublicAPI baselines now that we're adding new APIs for .NET 8. We'll have to be careful not to include IPNetwork.Parse/TryParse and RequestLocalizationOptions.CultureInfoUseUserOverride in the .NET 7 baseline. I think those are the only APIs we've added since branching for .NET 8. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once 7.0.0 ships, we'll update the baselines. But not until then. That'll involve changes in release/7.0 and copying the Shipped files into main, then patching things up to reflect the new APIs in Unshipped files. |
||
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext | ||
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.ClientHelloInfo.get -> System.Net.Security.SslClientHelloInfo | ||
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.ClientHelloInfo.set -> void | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
#nullable enable | ||
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature | ||
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature.OnClosed(System.Action<object?>! callback, object? state) -> void | ||
Microsoft.AspNetCore.Connections.IConnectionListenerFactorySelector | ||
Microsoft.AspNetCore.Connections.IConnectionListenerFactorySelector.CanBind(System.Net.EndPoint! endpoint) -> bool |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
#nullable enable | ||
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature | ||
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature.OnClosed(System.Action<object?>! callback, object? state) -> void | ||
Microsoft.AspNetCore.Connections.IConnectionListenerFactorySelector | ||
Microsoft.AspNetCore.Connections.IConnectionListenerFactorySelector.CanBind(System.Net.EndPoint! endpoint) -> bool |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,17 +18,17 @@ internal sealed class TransportManager | |
{ | ||
private readonly List<ActiveTransport> _transports = new List<ActiveTransport>(); | ||
|
||
private readonly IConnectionListenerFactory? _transportFactory; | ||
private readonly IMultiplexedConnectionListenerFactory? _multiplexedTransportFactory; | ||
private readonly List<IConnectionListenerFactory> _transportFactories; | ||
private readonly List<IMultiplexedConnectionListenerFactory> _multiplexedTransportFactories; | ||
private readonly ServiceContext _serviceContext; | ||
|
||
public TransportManager( | ||
IConnectionListenerFactory? transportFactory, | ||
IMultiplexedConnectionListenerFactory? multiplexedTransportFactory, | ||
List<IConnectionListenerFactory> transportFactories, | ||
List<IMultiplexedConnectionListenerFactory> multiplexedTransportFactories, | ||
ServiceContext serviceContext) | ||
{ | ||
_transportFactory = transportFactory; | ||
_multiplexedTransportFactory = multiplexedTransportFactory; | ||
_transportFactories = transportFactories; | ||
_multiplexedTransportFactories = multiplexedTransportFactories; | ||
_serviceContext = serviceContext; | ||
} | ||
|
||
|
@@ -37,19 +37,28 @@ public TransportManager( | |
|
||
public async Task<EndPoint> BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig? endpointConfig, CancellationToken cancellationToken) | ||
{ | ||
if (_transportFactory is null) | ||
if (_transportFactories.Count == 0) | ||
{ | ||
throw new InvalidOperationException($"Cannot bind with {nameof(ConnectionDelegate)} no {nameof(IConnectionListenerFactory)} is registered."); | ||
} | ||
|
||
var transport = await _transportFactory.BindAsync(endPoint, cancellationToken).ConfigureAwait(false); | ||
StartAcceptLoop(new GenericConnectionListener(transport), c => connectionDelegate(c), endpointConfig); | ||
return transport.EndPoint; | ||
foreach (var transportFactory in _transportFactories) | ||
{ | ||
var selector = transportFactory as IConnectionListenerFactorySelector; | ||
if (CanBindFactory(endPoint, selector)) | ||
{ | ||
var transport = await transportFactory.BindAsync(endPoint, cancellationToken).ConfigureAwait(false); | ||
StartAcceptLoop(new GenericConnectionListener(transport), c => connectionDelegate(c), endpointConfig); | ||
return transport.EndPoint; | ||
} | ||
} | ||
|
||
throw new InvalidOperationException($"No registered {nameof(IConnectionListenerFactory)} supports endpoint {endPoint.GetType().Name}: {endPoint}"); | ||
} | ||
|
||
public async Task<EndPoint> BindAsync(EndPoint endPoint, MultiplexedConnectionDelegate multiplexedConnectionDelegate, ListenOptions listenOptions, CancellationToken cancellationToken) | ||
{ | ||
if (_multiplexedTransportFactory is null) | ||
if (_multiplexedTransportFactories.Count == 0) | ||
{ | ||
throw new InvalidOperationException($"Cannot bind with {nameof(MultiplexedConnectionDelegate)} no {nameof(IMultiplexedConnectionListenerFactory)} is registered."); | ||
} | ||
|
@@ -87,9 +96,25 @@ public async Task<EndPoint> BindAsync(EndPoint endPoint, MultiplexedConnectionDe | |
}); | ||
} | ||
|
||
var transport = await _multiplexedTransportFactory.BindAsync(endPoint, features, cancellationToken).ConfigureAwait(false); | ||
StartAcceptLoop(new GenericMultiplexedConnectionListener(transport), c => multiplexedConnectionDelegate(c), listenOptions.EndpointConfig); | ||
return transport.EndPoint; | ||
foreach (var multiplexedTransportFactory in _multiplexedTransportFactories) | ||
{ | ||
var selector = multiplexedTransportFactory as IConnectionListenerFactorySelector; | ||
if (CanBindFactory(endPoint, selector)) | ||
{ | ||
var transport = await multiplexedTransportFactory.BindAsync(endPoint, features, cancellationToken).ConfigureAwait(false); | ||
StartAcceptLoop(new GenericMultiplexedConnectionListener(transport), c => multiplexedConnectionDelegate(c), listenOptions.EndpointConfig); | ||
return transport.EndPoint; | ||
} | ||
} | ||
|
||
throw new InvalidOperationException($"No registered {nameof(IMultiplexedConnectionListenerFactory)} supports endpoint {endPoint.GetType().Name}: {endPoint}"); | ||
} | ||
|
||
private static bool CanBindFactory(EndPoint endPoint, IConnectionListenerFactorySelector? selector) | ||
{ | ||
// By default, the last registered factory binds to the endpoint. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you ensure the last wins? Are you relying on the order of items in the DI injected Lists? I thought those lists where given in the order added, so you'd have to reverse it to get the last one first? edit Nevermind, I found the Reverse call in |
||
// A factory can implement IConnectionListenerFactorySelector to decide whether it can bind to the endpoint. | ||
return selector?.CanBind(endPoint) ?? true; | ||
} | ||
|
||
/// <summary> | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we implement this on the socket transport and restrict it to known endpoints?