Skip to content

Project Bedrock #4772

Closed
Closed
@davidfowl

Description

@davidfowl

Project Bedrock

Project bedrock is about further decoupling the components of Kestrel so that we can use it as the foundation for our non-http networking stack.
We want to build on the primitives, patterns and cross cutting concerns that exist today in ASP.NET Core applications. The goal is to enable higher level frameworks (like SignalR or WCF and even ASP.NET Core itself) to build on top of abstractions that don't tie them to a specific connection implementation (OWIN for Connections). As an example, it allows SignalR to run both on top of TCP or Websockets without having to understand what the underlying transport is. We also want to enable building raw low level protocol servers to handle things like MQTT for IOT scenarios.

There are 3 main actors in this server side programming model:

  • Applications/Middleware/Frameworks - The application code that handles connections and implement protocol parsing logic or other logic that modifies the stream of data (http, TLS as an example)
  • Transports - Transports provide an implementation of an IFeatureCollection that implements the underlying connection semantics. In short, transports provide a concrete implementation of the ConnectionContext that flows through the dispatcher to the application.
  • Dispatchers - The dispatcher is the component that brings the transport layer and application layer together. Its job is to manage the lifetime of the transport connection and application running on top. The dispatcher will expose the IConnectionBuilder for a particular binding relevant to the transport. For example, the http dispatcher will expose the IConnectionBuilder based on a particular route, while the TCP dispatcher will expose an IConnectionBuilder based on an ip address and port.

Applications/Middleware/Frameworks

At the center of this work is a new set of primitives that represent an underlying connection:

public abstract class ConnectionContext
{
    public abstract string ConnectionId { get; set; }

    public abstract IFeatureCollection Features { get; }
    
    public abstract IDuplexPipe Transport { get; set; }

    public abstract IDictionary<object, object> Items { get; set; }
}
public interface IConnectionIdFeature
{
    string ConnectionId { get; set; }
}
public interface IConnectionTransportFeature
{
    IDuplexPipe Transport { get; set; }
}
public interface IConnectionItemsFeature
{
    IDictionary<object, object> Items { get; set; }
}

The ConnectionContext is the "HttpContext" of the connection universe. It's an abstraction that represents a persistent connection of some form. This could
be a TCP connection, a websocket connection or something more hybrid (like a connection implemented over a non duplex protocol like server sent events + http posts). The feature collection
is there for the same reason it's there on the HttpContext, the server or various pieces of "middleware" can add, augment or remove features
from the connection which can enrich the underlying abstraction. The 2 required features are the IConnectionTransportFeature and the IConnectionIdFeature.

Next, we introduce the abstraction for executing a connection.

public delegate Task ConnectionDelegate(ConnectionContext connection);

The ConnectionDelegate represents a function that executes some logic per connection. That Task return represents the
connection lifetime. When it completes, the application is finished with the connection and the server is free to close it.

In order to build up a pipeline, we need a builder abstraction and a pipeline. The IConnectionBuilder (similar to the IApplicationBuilder) represents
a sockets pipeline. The middleware signature is Func<ConnectionDelegate, ConnectionDelegate> so callers can decorate the next ConnectionDelegate
in the chain similar to http middleware in ASP.NET Core.

public interface IConnectionBuilder
{
    IServiceProvider ApplicationServices { get; }

    IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware);

    ConnectionDelegate Build();
}

These are the fundamental building blocks for building connection oriented applications. This will live in the Microsoft.AspNetCore.Connections.Abstractions package.

This refactoring will enable a few things:

  • Kestrel's ASP.NET Core implementation will be re-platted on top of this new infrastructure which means it will be fully decoupled from Kestrel.
  • Kestrel's connection adapter pipeline will be changed to use the IConnectionBuilder instead. This means that things like TLS , windows auth and connection logging can be separate middleware components.
  • SignalR will be built on top of this making it possible to run SignalR on any connection based infrastructure.

Transports

Transports are responsible for providing the initial IFeatureCollection implementation for the connection and providing a stream of bytes to the application.

Libuv and System.Net.Sockets

Today we have 2 transport implementations that reside in Kestrel, a System.Net.Sockets and libuv implementation. We plan to keep these 2 because they both offer different sets of features. Libuv can listen on file handles, named pipes, unix domain sockets, and tcp sockets while System.Net.Sockets just has a tcp socket implementation (and unix domain sockets)

WebSockets

We want to enable people to build websocket based frameworks without dealing with low level details like connection management and buffering. As such, we will provide a web socket transport that exposes these connection primitives. This currently lives in the Microsoft.AspNetCore.Http.Connectons package.

Other HTTP transports

SignalR in the past has provided multiple transport implementations historically for browsers that didn't support websockets. These are not full duplex transports but are implemented as such by round tripping a connection id over http requests. We will also provide implementations transports for long polling and server sent events. These implementations will require a special client library that understands the underlying non-duplex protocol. These currently lives in the Microsoft.AspNetCore.Http.Connectons and Microsoft.AspNetCore.Http.Connectons.Client packages.

QUIC

QUIC is a quickly emerging standard that is looking to improve perceived performance of connection-oriented web applications that are currently using TCP. When QUIC comes around we'll want to be able to support it with the same abstraction.

Dispatchers

ASP.NET Core

ASP.NET Core will serve as the basis for our HTTP dispatcher. There will be a RequestDelegate implementation that serves as the dispatcher built on top of routing.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using SocketsSample.EndPoints;
using SocketsSample.Hubs;

namespace SocketsSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddConnections();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseConnections(routes =>
            {
                // Handle mqtt connections over websockets on the /mqtt path
                routes.MapWebSocket("mqtt", connectionBuilder =>
                {
                    connectionBuilder.UseMQTT<MQTTHandler>();
                });

                // Handle SignalR chat connections on the /chat path (multiple transports)
                routes.MapConnections("chat", connectionBuilder =>
                {
                    connectionBuilder.UseSignalR<Chat>();
                });
            });
        }
    }
}

Kestrel

Kestrel was originally built as an http server for ASP.NET Core. Since then it's evolved to into a bunch of separate components but has still been hyper focused on http scenarios. As part of this work, there are further refactorings that will happen and kestrel will serve as the generic sockets server that will support multiple protocols. We want to end up with layers that look something like this:

  • Microsoft.AspNetCore.Server.Kestrel.Core - Dispatcher implementation
  • Microsoft.AspNetCore.Server.Kestrel.Https - Deprecate this package in favor of (Microsoft.AspNetCore.Protocols.Tls)
  • Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions - Abstractions for plugging different transports into kestrel
  • Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv - Libuv transport (tcp, pipes, unix sockets)
  • Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets - System.Net.Sockets transport
  • Microsoft.AspNetCore.Server.Kestrel - Meta package for ASP.NET Core to avoid breaking changes

We should introduce the following packages:

  • Microsoft.AspNetCore.Protocols.Http - Http ConnectionDelegate middleware
  • Microsoft.AspNetCore.Protocols.Http2 - Http2 ConnectionDelegate middleware (do we merge Http and Http2?)
  • Microsoft.AspNetCore.Protocols.Tls - TLS ConnectionDelegate middleware

Here's what the Kestrel for TCP could look like wired up to the generic host:

var host = new HostBuilder()
            .ConfigureServer(options =>
            {
                // Listen on (*, 5001), then get access to the ISocketBuilder for this binding
                options.Listen(IPAddress.Any, 5001, connectionBuilder =>
                {
                   // This is the SignalR middleware running directly on top of TCP
                   connectionBuilder.UseHub<Chat>();
                });
                
                // Listen on (localhost, 8001), then get access to the ISocketBuilder for this binding
                options.Listen("localhost", 8001, connectionBuilder =>
                {
                   // Accept connections from an IOT device using the MQTT protocol
                   connectionBuilder.UseMQTT<MQTTHandler>();
                });
                
                options.Listen("localhost", 5000, connectionBuilder => 
                {
                    // TLS required for this end point (this piece of middleware isn't terminal)
                    connectionBuilder.UseTls("testCert.pfx", "testPassword");
                    
                    // ASP.NET Core HTTP running inside of a Connections based server
                    connectionBuilder.UseHttpServer(async context =>
                    {
                        await context.Response.WriteAsync("Hello World");
                    });
                });
            })
            .Build();

host.Run();

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-networkingIncludes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractionsfeature-kestrel🥌 Bedrock

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions