Skip to content

Kestrel: Support for multiple transports, side-by-side #44537

@JamesNK

Description

@JamesNK

Is there an existing issue for this?

  • I have searched the existing issues

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, and IMultiplexedConnectionListenerFactory 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 using options.Listen(new MyCustomEndPoint("address")). Both endpoints are sent to the last factory type.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
builder.Services.AddSingleton<IConnectionListenerFactory, NamedPipeTransportFactory>();
builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenLocalhost(80); // TCP!
    options.Listen(new NamedPipeEndPoint("Pipe-1")); // Named pipes!
});

var app = builder.Build();

Describe the solution you'd like

I think default interface implementation methods should be added to IConnectionListenerFactory and IMultiplexedConnectionListenerFactory 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 and IMultiplexedConnectionListenerFactory 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 with CanBind 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 the CanBind 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 and IMultiplexedConnectionListenerFactory 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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-networkingIncludes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions