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.
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) .