Written in front

ASP.NET Core’s web server uses Kestrel by default, which is a cross-platform, lightweight web server.

Before we start, let’s review the default main() method template of .NET Core 3.0. We will call the Host.CreateDefaultBuilder method. The main function of this method is to configure the application host and set the properties of the host, and set the Kestrel server to be a web server. In addition, it also includes log function, application configuration loading, etc., which is not expanded here.

As a lightweight Web Server, it does not have the large and full functions of IIS and Apache, but it can still be used alone or in combination with reverse proxy servers such as IIS and Apache.

This article will discuss the relevant knowledge of ASP.NET Core application in Kestrel from the source code.


The existence of Kestrel

To understand this problem, the first thing to emphasize is that the goal of .NET Core application is to cross-platform. Since you want to cross-platform, you need to apply the Web server on each platform. The startup, configuration, etc. of each server are different. Each server provides a set of implementations. If a new Web Server emerges in the future, and then a new implementation is added, this will cause the applicability of the .NET Core application to lag, and it will consume a lot of manpower. Cross-platform goals.

We can think of Kestrel as a middleware, an adaptation function that abstracts the characteristics of each server so that each application only needs to call the same interface to run on each platform to the maximum extent possible.

Operation mode

Under .NET Core 3.0, Kestrel’s integration is quite mature, and the corresponding custom configuration is provided to make Kestrel more flexible and configurable. It can be run standalone or in combination with a reverse proxy server.

Kestrel itself does not support multiple applications sharing the same port, but we can use a reverse proxy server to achieve unified external sharing of the same port.

The following is a schematic diagram of its individual operation:



The following is a schematic diagram of its use in conjunction with reverse proxy:





This class library is the core class library of Kestrel, which contains multiple logical implementations of this function. The following is referred to as the Kestrel.Core.

Kestrel adaptation logic

As mentioned earlier, Kestrel functions as an abstract server, so in the process of adapting to other servers, it is necessary to involve input, output, data interaction and Trace functions. In Kestrel.Core, this functionality is mainly implemented by the AdaptedPipeline class, which inherits from IDuplexPipe and gets the Pipe object through the constructor. IDuplexPipe and Pipe are located in the

namespace, and the details can be viewed by clicking on it.

AdaptedPipeline has two public methods:

RunAsync(): used for reading (flush operation after reading) and writing data, and loaded into the Task respectively

CompleteAsync(): completes the read and write operations and cancels the reading of the underlying stream

It also includes four public properties as follows:

   1:  public RawStream TransportStream { get; }
   3:  public Pipe Input { get; }
   5:  public Pipe Output { get; }
   7:  public IKestrelTrace Log { get; }

It defines the objects of the duplex pipeline from which data can be read and written. IDuplexPipe has two properties, System.IO.Pipelines.PipeReader Input { get; } and System.IO.Pipelines.PipeReader Output { get; }. AdaptedPipeline also gets the Pipe object through the constructor.

The RawStream class inherits from Stream and rewrites the key properties and methods of Stream. The main goal is to provide an internal wrapper for Kestrel to read and write data.

The LoggingStream class also inherits from Stream. The difference between RawStream and RawStream is that it increases the log record of the operation process. It is mainly used to record the information in the connection adaptation process. However, you need to enable the log to record the log information. The following is the external log. How to use:

   1:  public static class ListenOptionsConnectionLoggingExtensions
   2:   {
   3:      /// <summary>
   4:      /// Emits verbose logs for bytes read from and written to the connection.
   5:      /// </summary>
   6:      /// <returns>
   7:      /// The <see cref="ListenOptions"/>.
   8:      /// </returns>
   9:      public static ListenOptions UseConnectionLogging(this ListenOptions listenOptions)
  10:       {
  11:          return listenOptions.UseConnectionLogging(loggerName: null);
  12:       }
  14:      /// <summary>
  15:      /// Emits verbose logs for bytes read from and written to the connection.
  16:      /// </summary>
  17:      /// <returns>
  18:      /// The <see cref="ListenOptions"/>.
  19:      /// </returns>
  20:      public static ListenOptions UseConnectionLogging(this ListenOptions listenOptions, string loggerName)
  21:       {
  22:          var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<ILoggerFactory>();
  23:           var logger = loggerName == null ? loggerFactory.CreateLogger <LoggingConnectionAdapter> (): loggerFactory.CreateLogger (loggerName);
  24:          listenOptions.ConnectionAdapters.Add(new LoggingConnectionAdapter(logger));
  25:          return listenOptions;
  26:       }
  27:   }

Kestrel feature abstraction

The Kestrel feature under this module, the more important is the connection timeout settings (including setting the timeout, reset timeout and canceling the timeout limit. This feature makes our connection more controllable, for example, in some special scenarios, Under special conditions, we need to cancel the timeout limit or dynamically reset the timeout). The TLS application protocol function, based on the Http2.0 StreamId record function, is used to stop the connection count function.

The following is the source code for the connection timeout interface:

   1:  /// <summary>
   2:  /// Feature for efficiently handling connection timeouts.
   3:  /// </summary>
   4:  public interface IConnectionTimeoutFeature
   5:   {
   6:      /// <summary>
   7:      /// Close the connection after the specified positive finite <see cref="TimeSpan"/>
   8:      /// unless the timeout is canceled or reset. This will fail if there is an ongoing timeout.
   9:      /// </summary>
  10:      void SetTimeout(TimeSpan timeSpan);
  12:      /// <summary>
  13:      /// Close the connection after the specified positive finite <see cref="TimeSpan"/>
  14:      /// unless the timeout is canceled or reset. This will cancel any ongoing timeouts.
  15:      /// </summary>
  16:      void ResetTimeout(TimeSpan timeSpan);
  18:      /// <summary>
  19:      /// Prevent the connection from closing after a timeout specified by <see cref="SetTimeout(TimeSpan)"/>
  20:      /// or <see cref="ResetTimeout(TimeSpan)"/>.
  21:      /// </summary>
  22:      void CancelTimeout();
  23:   }

Kestrel options and restrictions

Kestrel’s option controls include listening, Kestrel server, and HTTPS connection adaptation.

1, the listener option function is implemented in ListenOptions, the class inherits from IConnectionBuilder, the main role of ListenOptions is to describe the open sockets in Kestrel, including Unix domain socket path, file descriptor, ipendpoint. ListenOptions internally maintains a read-only List<Func<ConnectionDelegate, ConnectionDelegate>>() object, and loads the new Func<ConnectionDelegate, ConnectionDelegate> object via the Use() method, and then returns the last joined Func<ConnectionDelegate via Build. The ConnectionDelegate object, the source code is as follows:

   1:  public IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware)
   2:   {
   3:      _middleware.Add(middleware);
   4:      return this;
   5:   }
   7:  public ConnectionDelegate Build()
   8:   {
   9:      ConnectionDelegate app = context =>
  10:       {
  11:          return Task.CompletedTask;
  12:       };
  14:      for (int i = _middleware.Count - 1; i >= 0; i--)
  15:       {
  16:          var component = _middleware[i];
  17:          app = component(app);
  18:       }
  20:      return app;
  21:   }

Note that ListenOptions also has two subclasses inside the class library, AnyIPListenOptions and LocalhostListenOptions, for use in listening for specific scenes.

2, Kestrel server option is implemented in KestrelServerOptions, this class is used to provide the programming level configuration of Kestrel specific functions, this class will maintain the list object of ListenOptions, this class will further expand the function of ListenOptions, and add HTTPS, certificate The default configuration and application, this class is relatively large, this article does not post the source code, interested students can go through it.

3, HTTPS connection adaptation options are implemented in HttpsConnectionAdapterOptions, this class is used to set how Kestrel handles HTTPS connections. Here, it introduces certificate function, SSL protocol, HTTP protocol, timeout function, and can also customize the certificate processing when HTTPS connection. The mode (AllowCertificate, RequireCertificate, etc.), the following is the constructor of HttpsConnectionAdapterOptions:

   1:  public HttpsConnectionAdapterOptions()
   2:   {
   3:       ClientCertificateMode = ClientCertificateMode.NoCertificate;
   4:      SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11;
   5:      HandshakeTimeout = TimeSpan.FromSeconds(10);
   6:   }

As you can see, by default, there is no certificate mode, the SSL protocol includes Tls12 and Tls11, and the maximum time allowed to allow TLS/SSL handshake is ten seconds.

4. Kestrel’s restriction function is implemented in KestrelServerLimits, including:

  • Keep active timeout
  • Maximum number of connections to the client (by default, the maximum number of connections is unlimited (NULL))
  • The maximum size of the request body (the default request body size is 30,000,000 bytes, approximately 28.6 MB)
  • Request body minimum data rate (default minimum rate is 240 bytes/second, including a grace period of 5 seconds)
  • Request header timeout (default is 30 seconds)
  • Maximum stream per connection (default is 100)
  • Title table size (default is 4096)
  • Maximum frame size (default is 2^14)
  • Maximum request header size (default is 8,192)
  • Initial connection window size (default is 128 KB)
  • Initial stream window size (default is 96 KB)

The code looks like this:

   1:  .ConfigureKestrel((context, options) =>
   2:   {
   3:      options.Limits.MaxConcurrentConnections = 100;
   4:      options.Limits.MaxConcurrentUpgradedConnections = 100;
   5:      options.Limits.MaxRequestBodySize = 10 * 1024;
   6:      options.Limits.MinRequestBodyDataRate =
   7:          new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
   8:      options.Limits.MinResponseDataRate =
   9:          new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
  10:      options.Listen(IPAddress.Loopback, 5000);
  11:      options.Listen(IPAddress.Loopback, 5001, listenOptions =>
  12:       {
  13:          listenOptions.UseHttps("testCert.pfx", "testPassword");
  14:       });
  15:      options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
  16:       options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes (1);
  17:      options.Limits.Http2.MaxStreamsPerConnection = 100;
  18:      options.Limits.Http2.HeaderTableSize = 4096;
  19:      options.Limits.Http2.MaxFrameSize = 16384;
  20:      options.Limits.Http2.MaxRequestHeaderFieldSize = 8192;
  21:      options.Limits.Http2.InitialConnectionWindowSize = 131072;
  22:      options.Limits.Http2.InitialStreamWindowSize = 98304;
  23:   });

Some of its source code is as follows:

   1:  // Matches the non-configurable default response buffer size for Kestrel in 1.0.0
   2:  private long? _maxResponseBufferSize = 64 * 1024;
   4:  // Matches the default client_max_body_size in nginx.
   5:  // Also large enough that most requests should be under the limit.
   6:  private long? _maxRequestBufferSize = 1024 * 1024;
   8:  // Matches the default large_client_header_buffers in nginx.
   9:  private int _maxRequestLineSize = 8 * 1024;
  11:  // Matches the default large_client_header_buffers in nginx.
  12:  private int _maxRequestHeadersTotalSize = 32 * 1024;
  14:  // Matches the default maxAllowedContentLength in IIS (~28.6 MB)
  15:   // https://www.iis.net/configreference/system.webserver/security/requestfiltering/requestlimits#005
  16:  private long? _maxRequestBodySize = 30000000;
  18:  // Matches the default LimitRequestFields in Apache httpd.
  19:  private int _maxRequestHeaderCount = 100;
  21:  // Matches the default http.sys connectionTimeout.
  22:  private TimeSpan _keepAliveTimeout = TimeSpan.FromMinutes(2);
  twenty three:   
  24:  private TimeSpan _requestHeadersTimeout = TimeSpan.FromSeconds(30);
  26:  // Unlimited connections are allowed by default.
  27:  private long? _maxConcurrentConnections = null;
  28:  private long? _maxConcurrentUpgradedConnections = null;


Reference link:

Orignal link:https://www.cnblogs.com/edison0621/p/11111199.html