-
Notifications
You must be signed in to change notification settings - Fork 10.2k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Kestrel: Support for multiple transports, side-by-side #44537
Comments
What happens if multiple can bind the endpoint type? |
The last registered factory wins. That's what happens today: aspnetcore/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs Lines 77 to 78 in 9de8e33
In the sample below, var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
builder.Services.AddSingleton<IConnectionListenerFactory, NamedPipeTransportFactory>(); // <- registered last
builder.WebHost.ConfigureKestrel(options =>
{
options.Listen(new NamedPipeEndPoint("Pipe-1"), listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
});
var app = builder.Build(); If there are multiple factories that support the same endpoint, then the last one registered wins because |
So the logic is that we get all the transports, then we call canbind on all of the endpoints, and the last one that can bind wins? |
// Reverse so the last registered transport has priority and attempts to bind first
_transportFactories = transportFactories.Reverse().ToList();
public async Task<IConnectionListener> BindAsync(Endpoint endpoint, CancellationToken cancellationToken)
{
foreach (var factory in _transportFactories)
{
if (factory.CanBind(endpoint))
{
return await factory.BindAsync(endpoint, cancellationToken);
}
}
throw new InvalidOperationException("Endpoint '{endpoint}' is not supported by any registered transport factory.");
} |
Or just loop backwards to avoid that allocation 😅 |
Thanks for contacting us. We're moving this issue to the |
I like it. Should we label this as ready for API review? |
There is a loop per-bind. Probably cheaper to reverse once and store it as a list and avoid |
Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:
|
|
@JamesNK We decided to save this for an afternoon API review where hopefully you can make it. Do you want to propose APIs for NamedPipeEndpoint and NamedPipeTransportFactory as well? |
API Review Notes:
namespace Microsoft.AspNetCore.Connections;
+ public interface IConnectionListenerFactorySelector
+ {
+ bool CanBind(EndPoint endpoint);
+ } |
Is it worth looking at things at an even higher level than this? The scenario that CoreWCF needs to support is using Kestrel with a custom IConnectionListenerFactory to support NetTcp and NamedPipes, while being hosted in IIS and using the IIS Integration for HTTP requests. Eventually we'll need to go as far as adding support to the aspnetcoremodule for service activation of non-http protocols (or achieving this via a secondary IIS module that's installed). This approach is still going to result in needing to wire up the equivalent of Kestrel via an IHostedService when using IIS which seems like the wrong approach. |
NetTcp is over a socket, right? What prevents CoreWCF + Kestrel from using the existing sockets transport factory? Likewise, what would prevent CoreWCF + Kestrel from using the new named pipes transport that we're adding in .NET 8? |
Kestrel doesn't accept requests from IIS, so the IServer implementation can't be Kestrel when IIS hosted.
|
@davidfowl, have you tested this in IIS? In CoreWCF we already replace the IServer with a wrapping one (to enable us to throw exceptions on service init as we need to init late due to IServerAddressesFeature) and this broke on IIS. The IIS integration piece throws an exception if the IServer is the wrong type so we have logic to not wrap on IIS (you don't get to see the exceptions on IIS anyway). |
Here's the issue when someone hit this. The full call stack is there. |
@davidfowl, do you have a suggestion on how to overcome the IIS problem? I'm still in the situation where I can't use Kestrel and IIS together and I'm about to add NamedPipe support to CoreWCF very soon. As things currently stand, I'm going to have to compose a lot more of the stack manually than is ideal and will be fragile to future asp.net core design changes under the hood. |
@mconnew that's not a case where you need multiple transports, but rather multiple servers. We've dabbled with this a bit, making an IServer that activates both instances. |
Supporting multiple IServer instances would solve the need to support different types of connection listeners. You would simply have multiple Kestrel IServer instances, one for each connection listener type. It would remove the need for this new proposed API, and would solve more problems at the same time. |
Is there an existing issue for this?
Is your feature request related to a problem? Please describe the problem.
Kestrel supports being extended to support new transports. This is done by registering IConnectionListenerFactory or IMultiplexedConnectionListenerFactory in DI.
When starting up, for each factory type, Kestrel then gets all registered factories. It then uses the last one for any registered listeners.
IConnectionListenerFactory
is used for HTTP/1.1 and HTTP/2 endpoints, andIMultiplexedConnectionListenerFactory
is used for HTTP/3.The problem with this is it isn't easily possible to mix transports together. Someone might want to support TCP endpoints using
options.ListenLocalhost(80)
and a custom transport usingoptions.Listen(new MyCustomEndPoint("address"))
. Both endpoints are sent to the last factory type.Describe the solution you'd like
I think default interface implementation methods should be added to
IConnectionListenerFactory
andIMultiplexedConnectionListenerFactory
which can be used to test if a factory supports binding a given endpoint.public interface IConnectionListenerFactory { Task<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken) + public bool CanBind(EndPoint endpoint) => true; }
public interface IMultiplexedConnectionListenerFactory { Task<IMultiplexedConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken) + public bool CanBind(EndPoint endpoint) => true; }
Kestrel continues to get all registered instances of
IConnectionListenerFactory
andIMultiplexedConnectionListenerFactory
from DI, but instead of using the last one registered, Kestrel now keeps all instances, and then when binding a new listener, Kestrel tests each factory withCanBind
to see whether it supports binding a given endpoint, then falls back to the next listener.The new
CanBind
default implementation returns true, which follows current behavior, and existing factories can override theCanBind
method to only allow currently supported addresses. If no factories support an endpoint then Kestrel throws an error saying the endpoint isn't supported.Additional context
IConnectionListenerFactory
andIMultiplexedConnectionListenerFactory
are in the Microsoft.AspNetCore.Connections.Abstractions project. It targets netstandard2.0 and .NET Framework. The new DIM would only be available in .NET 8+. Is there precedence for doing this?The text was updated successfully, but these errors were encountered: