. NET cloud native architect training camp (KestrelServer source code analysis) -- learning notes

catalogue

  • target
  • Source code

target

Understand how KestrelServer receives network requests and how network requests are converted into http request context (C# recognizable)

Source code

https://github.com/dotnet/aspnetcore/

There is a KestrelServerImpl in the directory aspnetcore\src\Servers\Kestrel\Core\src\Internal

internal class KestrelServerImpl : IServer

When the host starts, the startup method of the server is called. You can start from this entry

public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull

The StartAsync method is mainly divided into the following three steps

async Task OnBind(ListenOptions options, CancellationToken onBindCancellationToken)
{
    ...
}

AddressBindContext = new AddressBindContext(_serverAddresses, Options, Trace, OnBind);

await BindAsync(cancellationToken).ConfigureAwait(false);

The BindAsync method uses AddressBindContext for binding

await AddressBinder.BindAsync(Options.ListenOptions, AddressBindContext!, cancellationToken).ConfigureAwait(false);

A variety of policies are created in the BindAsync method of AddressBinder for binding

var strategy = CreateStrategy(
    listenOptions.ToArray(),
    context.Addresses.ToArray(),
    context.ServerAddressesFeature.PreferHostingUrls);

...

await strategy.BindAsync(context, cancellationToken).ConfigureAwait(false);

For example, AddressesStrategy, which has its own binding method

private class AddressesStrategy : IStrategy
{
    protected readonly IReadOnlyCollection<string> _addresses;

    public AddressesStrategy(IReadOnlyCollection<string> addresses)
    {
        _addresses = addresses;
    }

    public virtual async Task BindAsync(AddressBindContext context, CancellationToken cancellationToken)
    {
        foreach (var address in _addresses)
        {
            var options = ParseAddress(address, out var https);
            context.ServerOptions.ApplyEndpointDefaults(options);

            if (https && !options.IsTls)
            {
                options.UseHttps();
            }

            await options.BindAsync(context, cancellationToken).ConfigureAwait(false);
        }
    }
}

options binding of ListenOptions from IConnectionBuilder

public class ListenOptions : IConnectionBuilder, IMultiplexedConnectionBuilder

I found that I couldn't find the key point along the way, so I need to change the direction and start with the OnBind method, which is a delegate and needs to find the place to call

async Task OnBind(ListenOptions options, CancellationToken onBindCancellationToken)

You can see that the OnBind method is passed into the AddressBindContext

AddressBindContext = new AddressBindContext(_serverAddresses, Options, Trace, OnBind);

In AddressBindContext, it is a CreateBinding

public Func<ListenOptions, CancellationToken, Task> CreateBinding { get; }

Global search CreateBinding

You can find what is called in the BindEndpointAsync method of AddressBinder

internal static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken)
{
    try
    {
        await context.CreateBinding(endpoint, cancellationToken).ConfigureAwait(false);
    }
    catch (AddressInUseException ex)
    {
        throw new IOException(CoreStrings.FormatEndpointAlreadyInUse(endpoint), ex);
    }

    context.ServerOptions.OptionsInUse.Add(endpoint);
}

The BindEndpointAsync method is called by the BindAsync method of ListenOptions, that is, the ListenOptions that BindAsync goes to in the third step of StartAsync mentioned above

internal virtual async Task BindAsync(AddressBindContext context, CancellationToken cancellationToken)
{
    await AddressBinder.BindEndpointAsync(this, context, cancellationToken).ConfigureAwait(false);
    context.Addresses.Add(GetDisplayName());
}

In the third step, load the configuration in the BindAsync method, and then call the real binding method

Options.ConfigurationLoader?.Load();

await AddressBinder.BindAsync(Options.ListenOptions, AddressBindContext!, cancellationToken).ConfigureAwait(false);

Therefore, the whole process focuses on OnBind in the first step, and OnBind focuses on the BindAsync method of TransportManager

options.EndPoint = await _transportManager.BindAsync(options.EndPoint, multiplexedConnectionDelegate, options, onBindCancellationToken).ConfigureAwait(false);

In the TransportManager, you can see the start of receiving in the BindAsync method

StartAcceptLoop(new GenericConnectionListener(transport), c => connectionDelegate(c), endpointConfig);

The StartAcceptingConnections method is invoked in the StartAcceptLoop method.

var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(connectionListener);

Add the method to be executed to the queue in the StartAcceptingConnections method

ThreadPool.UnsafeQueueUserWorkItem(StartAcceptingConnectionsCore, listener, preferLocal: false);

Start listening for reception in StartAcceptingConnectionsCore, which is the key. kestrelConnection is executed here, and kestrelConnection contains_ connectionDelegate

var connection = await listener.AcceptAsync();

var kestrelConnection = new KestrelConnection<T>(
    id, _serviceContext, _transportConnectionManager, _connectionDelegate, connection, Log);

_transportConnectionManager.AddConnection(id, kestrelConnection);

ThreadPool.UnsafeQueueUserWorkItem(kestrelConnection, preferLocal: false);

In kestrelConnection, you can see that the entire ExecuteAsync method only executes_ connectionDelegate

await _connectionDelegate(connectionContext);

Realize_ After the importance of connectionDelegate, go back to find out how it came in. You can find that it was built through ListenOptions in kestrelserver impl

var connectionDelegate = options.Build();

In the Build method, you can see that it is a pipeline

ConnectionDelegate app = context =>
{
    return Task.CompletedTask;
};

for (var i = _middleware.Count - 1; i >= 0; i--)
{
    var component = _middleware[i];
    app = component(app);
}

return app;

Pass_ No valuable information was found for the reference of middleware's Use method

public IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware)
{
    _middleware.Add(middleware);
    return this;
}

So go back to kestrelserver impl and look at the UseHttpServer method

options.UseHttpServer(ServiceContext, application, options.Protocols, addAltSvcHeader);

You can see that this method builds an HttpConnectionMiddleware

var middleware = new HttpConnectionMiddleware<TContext>(serviceContext, application, protocols, addAltSvcHeader);
return builder.Use(next =>
{
    return middleware.OnConnectionAsync;
});

Entering HttpConnectionMiddleware, you can see a core method OnConnectionAsync, create a HttpConnection, and then call ProcessRequestsAsync.

var connection = new HttpConnection(httpConnectionContext);

return connection.ProcessRequestsAsync(_application);

In the ProcessRequestsAsync method, you can see the core logic of KestrelServer, which executes different logic according to different protocols; At the same time, you can see how it processes the request through the requestProcessor

switch (SelectProtocol())
{
    case HttpProtocols.Http1:
        requestProcessor = _http1Connection = new Http1Connection<TContext>((HttpConnectionContext)_context);
        _protocolSelectionState = ProtocolSelectionState.Selected;
        break;
    case HttpProtocols.Http2:
        requestProcessor = new Http2Connection((HttpConnectionContext)_context);
        _protocolSelectionState = ProtocolSelectionState.Selected;
        break;
    case HttpProtocols.Http3:
        requestProcessor = new Http3Connection((HttpMultiplexedConnectionContext)_context);
        _protocolSelectionState = ProtocolSelectionState.Selected;
        break;
}

await requestProcessor.ProcessRequestsAsync(httpApplication);

requestProcessor is an IRequestProcessor interface, which has multiple implementations. Take Http2Connection as an example

internal partial class Http2Connection : IHttp2StreamLifetimeHandler, IHttpStreamHeadersHandler, IRequestProcessor

In the ProcessRequestsAsync method of Http2Connection, read the stream, parse, transform and process

await _frameWriter.WriteWindowUpdateAsync(0, diff);

var result = await Input.ReadAsync();

await ProcessFrameAsync(application, framePayload);

After the OnConnectionAsync of HttpConnectionMiddleware is processed, how to splice with the application layer code is only Kestrel's processing

You can find the ProcessRequestsAsync method of HttpProtocol through the implementation of ProcessRequestsAsync method of IRequestProcessor interface. You can see that it executes a ProcessRequests method

await ProcessRequests(application);

In the ProcessRequests method, encapsulate the content obtained from the Body into a context, which is the real HttpContext, and then run the application layer code. Previously, it was Kestrel's parsing logic, and here is the pipeline application connected in series to our construction

InitializeBodyControl(messageBody);

var context = application.CreateContext(this);

// Run the application code for this request
await application.ProcessRequestAsync(context);

Next, let's take a look at how the application is transmitted. We always find HttpConnection, HttpConnectionMiddleware, httpconnectionbuilder extensions and kestrelserver impl

public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull

It can be seen that the Host calls the StartAsync of the Server, which reflects the principle of separation of responsibilities. For the pipeline of the application layer, an ihttpapplication is defined, which is requestDelegate

From Host to Server, Server completed the binding of network ports, the network's listening and receiving, and the conversion of network binary into specific c# recognizable HTTPContext. Then, a pipeline of application application layer encapsulated by Host was called, which is defined by Host in Startup, which is a complete process.

file: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/?view=aspnetcore-6.0&tabs=windows#kestrel

Course links

https://appsqsyiqlk5791.h5.xiaoeknow.com/v1/course/video/v_5f39bdb8e4b01187873136cf?type=2

This work adopts Knowledge sharing Attribution - non commercial use - sharing in the same way 4.0 international license agreement License.

Welcome to reprint, use and republish, but be sure to keep the signature Zheng Ziming (including link: http://www.cnblogs.com/MingsonZheng/ ), shall not be used for commercial purposes, and the works modified based on this article must be distributed under the same license.

If you have any questions, please contact me( MingsonZheng@outlook.com) .

Added by eektech909 on Sat, 22 Jan 2022 23:46:15 +0200