dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.16k stars 9.92k forks source link

Project Bedrock #4772

Closed davidfowl closed 5 years ago

davidfowl commented 7 years ago

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

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:

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:

We should introduce the following packages:

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();
aL3891 commented 7 years ago

It would be really cool to have something equivalent to Transports on the client side as well, so that you could basically have a more abstract HttpClient with a switchable underlying transport mechanism

d0pare commented 7 years ago

@davidfowl what do you think about performance hit after introducing such an abstraction?

galvesribeiro commented 7 years ago

@davidfowl Great work! I only have one concern...

Since the abstractions will not be aspnet-only, don't you think instead of using Microsoft.AspNetCore.Sockets.Abstractions we could use just Microsoft.Sockets.Abstractions for the core abstractions?

I agree that Kestrel and AspNet abstractions should have the respective names, but I think those abstractions are very... Abstracted and like you mentioned, are there to plug and manage very low level primitives.

Great work! Looking forward for it! :)

dls314 commented 7 years ago

Similarly, Microsoft.AspNetCore.Sockets.Tls => Microsoft.Sockets.Tls would make sense, but I want the feature more than a name.

shaggygi commented 7 years ago

@davidfowl What if we needed to read data from USB or serial ports instead of sockets, would that be a scenario where we would have to create a specific Transport?

galvesribeiro commented 7 years ago

@dls314

but I want the feature more than a name.

Me too. But I would like to have the package semantics more clear. Better to suggest it now then after the release :)

@shaggygi

What if we needed to read data from USB or serial ports instead of sockets, would that be a scenario where we would have to create a specific Transport?

I guess that is the purpose of the transports.

At least that is what I understood from this part:

Transports provide an implementation of an IFeatureCollection that implements the underlying connection semantics.

no1melman commented 7 years ago

Does that mean you could push message transport (msmq, rabbitMq, kafka) further down the stack? I suppose those transports would sit at the same abstraction level as SignalR....

davidfowl commented 7 years ago

@aL3891

It would be really cool to have something equivalent to Transports on the client side as well, so that you could basically have a more abstract HttpClient with a switchable underlying transport mechanism

I've been thinking about a client story as well that gels with this. SignalR has the beginnings of it, but I left it out of this spec.

@galvesribeiro

Since the abstractions will not be aspnet-only, don't you think instead of using Microsoft.AspNetCore.Sockets.Abstractions we could use just Microsoft.Sockets.Abstractions for the core abstractions?

This is something we've struggled with in the past, but AspNetCore will mean more than just our existing HTTP stack, it's the server stack in general. We won't be putting anything in the root namespace (i.e. Microsoft.Sockets). Naming needs some work though, sockets isn't great.

@shaggygi

@davidfowl What if we needed to read data from USB or serial ports instead of sockets, would that be a scenario where we would have to create a specific Transport?

Yes that would be a transport.

@no1melman

Does that mean you could push message transport (msmq, rabbitMq, kafka) further down the stack? I suppose those transports would sit at the same abstraction level as SignalR....

I don't fully understand the question. A transport can be anything but I wouldn't start implementing HTTP over a message bus 😄 .

no1melman commented 7 years ago

I was just thinking that you could make the message queue as the transport, much like you would with signalR, then you're abstracted away from mechanism.

galvesribeiro commented 7 years ago

@davidfowl well, if now AspNetCore will become a reference to all the server technologies in .Net and not just web stack anymore, them I'm all for it! :)

wholroyd commented 7 years ago

I am thoroughly upset by the complete lack of references to The Flintstones in this issue.

benaadams commented 7 years ago

if now AspNetCore will become a reference to all the server technologies in .Net and not just web stack anymore, them I'm all for it! :)

Async Serving Power

davidfowl commented 7 years ago

@no1melman

I was just thinking that you could make the message queue as the transport, much like you would with signalR, then you're abstracted away from mechanism.

SignalR didn't make a message queue the transport, those were fundamentally different abstractions.

markrendle commented 7 years ago

@davidfowl

Naming needs some work though, sockets isn't great.

Microsoft.AspNetCore.Bungholes

NinoFloris commented 7 years ago

I really wouldn't mind a better name, shorter and without confusion to old full framework tech for AspNetCore. Especially if it's going to be the reference name for the server stack in general.

KPixel commented 7 years ago

Regarding the name, I agree that, considering how low-level and ubiquitous this API would be, removing "AspNetCore" is a good idea.

I think the most fitting keyword to describe it is "Network". So, maybe Microsoft.Network? Or just Microsoft.Net (like System.Net) but it sounds like "Microsoft .NET" :)

benaadams commented 7 years ago

Microsoft.Network

Well... its wouldn't be strictly true the Transport abstraction is quite flexible; so you could write a stdin/out or filestream Transport and pipe to the program or read and write http from filestream. Or examples earlier it could be from usb or serial port...

Transport is like a driver

aL3891 commented 7 years ago

Microsoft.Bedrock? :) Its a cool name imo

It could also be Microsoft.Transport, also fits pretty well conceptually

KPixel commented 7 years ago

Network is also a fairly generic term outside of computer science. One of its definition is: "A group or system of interconnected people or things." So, when you connect something with something else, you create a network.

dls314 commented 7 years ago

It may be nice to identify connections by T instead of string. Perhaps IConnectionIdFeature w/ properly comparable T?

KPixel commented 7 years ago

My guess is that the ConnectionId is a string to simplify passing it around. If you make it a T, you will need to provide a Comparer (like you mentioned) but also a Serializer. That's a lot of complexity. Can you give a compelling scenario where it would be much better to use something else than a string?

galvesribeiro commented 7 years ago

It may be nice to identify connections by T instead of string. Perhaps IConnectionIdFeature w/ properly comparable T?

Make sense... Would avoid allocations with unnecessary .ToString() calls.

benaadams commented 7 years ago

Socket generally has a very focused use; could it be more general like Connection? (Also matching the ConnectionContext) of which Socket can be of of the many Connection types.

e.g.

public delegate Task ConnectionDelegate(ConnectionContext connection);
public interface IConnectionBuilder
{
    IServiceProvider ApplicationServices { get; }
    IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware);
    ConnectionDelegate Build();
}
shaggygi commented 7 years ago

I like @benaadams Connection suggestion. What about namespace System.IO.Connection?

dls314 commented 7 years ago

Can you give a compelling scenario where it would be much better to use something else than a string?

I'm not sure I can. My thought is that the connection id might often be used as a hash key and with some T you could get away without a string.GetHashCode call.

If not T how about going to int?

dls314 commented 7 years ago

I like @benaadams Connection suggestion. What about namespace System.IO.Connection?

Avoid the eponymous namespace's class ambiguity with System.IO.Connections?

galvesribeiro commented 7 years ago

Can you give a compelling scenario where it would be much better to use something else than a string?

and

My thought is that the connection id might often be used as a hash key and with some T you could get away without a string.GetHashCode call

If you are going to use a Hash function with this value and you are using your own T type as the Id, its is your responsibility to override GetHashCode() like in everywhere else you would and want to avoid collisions. I don't see why we need enforce an string, int, or whatever type.

Why don't let the user use whatever type they want?

galvesribeiro commented 7 years ago

Also yeah, @benaadams suggestion looks great. By using Socket the user expect a very specific semantics while Connection is more abstract and fits better with the context of those abstractions.

aL3891 commented 7 years ago

Can you give a compelling scenario where it would be much better to use something else than a string?

One could argue that byte[] would be better in some cases like when you're dealing with ip addresses, if not T, maybe that's an option

Another reason why something other than a string would be nice is if you have multiple components to the connection (like an ip and port), I mean you can encode that as a string of course but then that has to be parsed, if it where possible to have T as the "adress" It would open up to a lot of flexibility

benaadams commented 7 years ago

T ConnectionId makes it a bit generically nasty

public abstract class ConnectionContext<T>
{
    public abstract T ConnectionId { get; set; }
    public abstract IFeatureCollection Features { get; }
    public abstract IPipe Transport { get; set; }
}

public interface IConnectionIdFeature<T>
{
    T ConnectionId { get; set; }
}

public interface IConnectionTransportFeature
{
    public abstract PipeFactory PipeFactory { get; set; }
    public abstract IPipe Transport { get; set; }
}

public delegate Task ConnectionDelegate<T>(ConnectionContext<T> connection);

public interface IConnectionBuilder<T>
{
    IServiceProvider ApplicationServices { get; }
    IConnectionBuilder Use(Func<ConnectionDelegate<T>, ConnectionDelegate<T>> middleware);
    ConnectionDelegate<T> Build();
}

Also there is no compile time enforcement making the T in IConnectionIdFeature agree with anything else; even though you now need it everywhere?

Drawaes commented 7 years ago

Also "other things of t" can be added via features like IIPAddressFeature

davidfowl commented 7 years ago

It's going to be a string. It's simpler and we already use strings for things like the request id.

KPixel commented 7 years ago

If IConnectionTransportFeature and IConnectionIdFeature are required features of ConnectionContext, why duplicate the properties ConnectionId and Transport?

davidfowl commented 7 years ago

See the design of HttpContext. The properties that are hoisted to the top level are the most commonly used one that will apply to most connection implementations. It's a convenience, nothing more. Under the covers, the implementation of those properties directly expose the feature properties.

davidfowl commented 7 years ago

@NinoFloris

I really wouldn't mind a better name, shorter and without confusion to old full framework tech for AspNetCore. Especially if it's going to be the reference name for the server stack in general.

You meant for ASP.NET not, AspNetCore right? There's a point where you end up going from pure connection abstraction to ASP.NET Core's http stack (see the "ASP.NET Core HTTP running inside of ASP.NET Core Sockets" sample). What would you call the bridge package that is the HTTP "connection" middleware.

Maybe a brand name would solve the naming problem 😄.

@benaadams

Socket generally has a very focused use; could it be more general like Connection? (Also matching the ConnectionContext) of which Socket can be of of the many Connection types.

I like it. I'm not sure I like the assembly name yet though. Microsoft.AspNetCore.Connections? It'll grow on me.

benaadams commented 7 years ago

It's going to be a string. It's simpler and we already use strings for things like the request id.

Also makes it simpler to work with Activity of System.Diagnostics e.g. HttpCorrelationProtocol; byte[] you'd have realloc back to string

benaadams commented 7 years ago

I like it. I'm not sure I like the assembly name yet though. Microsoft.AspNetCore.Connections? It'll grow on me.

Its the interpreting layer, maybe:

Microsoft.AspNetCore.Protocols?

Microsoft.AspNetCore.Protocols.Http1 - Specific Http1, Http1.1 protocol Microsoft.AspNetCore.Protocols.Http2 - Specific Http2 protocol Microsoft.AspNetCore.Protocols.Http - Merged Http1+2 (negotiated) Microsoft.AspNetCore.Protocols.Tls - TLS SocketDelegate middleware

davidfowl commented 7 years ago

Microsoft.AspNetCore.Protocol.Abstractions Microsoft.AspNetCore.Protocol.Http Microsoft.AspNetCore.Protocol.Tls

NickCraver commented 7 years ago

@davidfowl I like that layout the best of all, but for consistency I think it should be "Protocols", like most other namespaces, e.g.

System.Collections System.DirectoryServices.Protocols System.Web.Services.Protocols

Some of the new libs have Protocol at the end, for a Protocol space for a singular purpose, but if we're going to have many here: plural feels far more correct to me. List for comparison: https://apisof.net/catalog

NinoFloris commented 7 years ago

@davidfowl yes I meant ASP.NET is getting pretty overloaded. What's the actual web framework part of it called these days? The ASP.NET Core 2.0 Web Framework?

Doing a search for ASP.NET 2.0 is great archaeological fun though: https://www.google.com/search?q=asp.net+2.0

It bothers me the name ASP.NET Core is 2 parts platform identifier (.NET and Core) and just one part 'product name' which instead of actually being a good name for a server stack is just an acronym reference to ancient tech from the nineties, it's long and hard to type, age is showing.

Now we can all guess the marketing department loves the brand recognition ASP.NET has with the old guard (meaning no offence), and I get that by using the same name might help those people find the transition to .NET Core. However overloading it more and more is doing nobody a service.

I'm exactly missing the few brand/codenames I can reference to people that directly correspond to a specific piece of the bigger stack. Not always having to refer to the long overloaded umbrella brand name attached to a precise description of said piece of the stack.

Kestrel is a very successful example of exactly that.

Sorry for the hijack, is there any designated place where I can put this on the table?

davidfowl commented 7 years ago

Sorry for the hijack, is there any designated place where I can put this on the table?

Sure, file an issue on aspnet/Home, but I gotta be honest, I'm not sure it'll change anything. The fact that ASP.NET Core is even called ASP.NET Core should be a clear sign of that. It's possible we could move some things under the Microsoft.Extensions umbrella since there's other prior art for that.

NinoFloris commented 7 years ago

Sure, file an issue on aspnet/Home, but I gotta be honest, I'm not sure it'll change anything.

I won't have any illusions ;) I know a name change is never going to happen, all I'm asking for is a bit more specificity underneath that bulging umbrella.

KPixel commented 7 years ago

Maybe ASP.NET (Core) can stay as the umbrella name, but assemblies can be called differently. Kestrel is a good example of that.

It is just difficult to find names like that, so you may end up defaulting to Microsoft.AspNetCore.XXX as a safe choice.

But I'm all for these unique names. They are distinctive and easy to research/reference.

davidfowl commented 7 years ago

Maybe ASP.NET (Core) can stay as the umbrella name, but assemblies can be called differently. Kestrel is a good example of that.

No disagreement there.

It is just difficult to find names like that, so you may end up defaulting to Microsoft.AspNetCore.XXX as a safe choice.

Likely, the latest suggestion of Protocols is what I like the most so far.

But I'm all for these unique names. They are distinctive and easy to research/reference.

You mean a name like kestrel but the represents these lower layers. Bedrock 😄 might be it, but that'll lead to too many Flintstones references.

wholroyd commented 7 years ago

I don't see any issue with the kestrel stuff continuing to remain in the AspNetCore nomenclature as it's very much owned by the ASP.NET team and part of their platform. This change might decouple the components of Kestrel a bit more to add another layer of abstraction, but it's still very much rooted in the same team/platform.

I see the Microsoft.Extensions nomenclature being something that spans usage by frameworks from multiple teams, whether its web, console, Windows app, or library.

My suggestion would be to move anything that is platform/framework agnostic into the Microsoft.Extensions.* nomenclature where possible and where it makes sense.

On a side note, I actually wish the host providers used to bootstrap web apps wasn't in the AspNetCore arena as there is a service host option, which to me seems more like a platform specific implementation as it solely requires a windows system to operate. Would be nice if it was a true abstraction were there could be systemd and upstartd implementations under them. Seems like those would be more relatable to the .NET ecosystem and be part of System.* instead. Feels weird using the service host for a Windows service when it's not web based.

davidfowl commented 7 years ago

On a side note, I actually wish the host providers used to bootstrap web apps wasn't in the AspNetCore arena as there is a service host option, which to me seems more like a platform specific implementation as it solely requires a windows system to operate. Would be nice if it was a true abstraction were there could be systemd and upstartd implementations under them. Seems like those would be more relatable to the .NET ecosystem and be part of System.* instead. Feels weird using the service host for a Windows service when it's not web based.

Microsoft.Extensions.Hosting is coming. But that's another spec and another code name (if I can think one up). See the tease as part of the 2.0 release https://github.com/aspnet/Hosting/tree/dev/src/Microsoft.Extensions.Hosting.Abstractions

davidfowl commented 7 years ago

I've updated the spec with the new names.

shaggygi commented 7 years ago

@davidfowl for hosting project codename/spec, I like Project Hostess. As in Hostess CupCake 😄

wholroyd commented 7 years ago

Where can I find the spec?

KPixel commented 7 years ago

Based on the description: "OWIN for Connections", what about calling this OCIN? "Open Connection/Communication Interface for .NET"? Namespace: Microsoft.Ocin.