How to remotely close an ASP Net core application?

Posted by d3vilr3d on Thu, 13 Jan 2022 08:54:16 +0100

In< N games of calendar dependency injection >In the example of demonstrating the system's automatic registration service, we will find that the output list contains two special services. Their corresponding service interfaces are iaapplicationlife and IHostingEnvironment. We will collectively refer to the services that implement these two interfaces as applicationlife and HostingEnvironment. From its name, we can see that applicationlife is related to the application declaration cycle, and the hosting environment is used to represent the current execution environment. In this article, we focus on understanding applicationlife and the whole AASP Net core application life cycle. [this article has been synchronized to< ASP.NET Core framework >In]

1, Applicationlife
2, Run method of WebHost
3, Remote shutdown application

1, Applicationlife

From the perspective of naming, application lifetime seems to be a description of the current application life cycle, but in fact, its purpose is only to send corresponding signals or notifications to relevant components when the application is started and closed. As shown in the following code snippet, the iaapplicationlife interface has three CancellationToken type attributes (ApplicationStarted, ApplicationStopping, and applicationsstopped). If we need to perform some operations before and after the application is automatically terminated, we can register the corresponding callbacks on the three CancellationToken objects. In addition to the three attributes of CancellationToken, the iaapplicationlife interface also defines a StopApplication method. We can call this method to send a signal to close the application and finally really close the application.

   1: public interface IApplicationLifetime
   2: {
   3:     CancellationToken ApplicationStarted { get; }
   4:     CancellationToken ApplicationStopping { get; }
   5:     CancellationToken ApplicationStopped { get; }
   7:     void StopApplication();
   8: }

ASP. The applicationlife used by net core by default is a type with the same name as defined below. It can be seen that the CancellationToken object returned by the three properties it implements is generated through three corresponding cancellationtokensources. In addition to the StopApplication method that implements the iaapplicationlife interface to send the "closing" notification, this type also defines two additional methods (NotifyStarted and NotifyStopped) to send the "opened / closed" Notification.

   1: public class ApplicationLifetime : IApplicationLifetime
   2: {
   3:     private readonly CancellationTokenSource _startedSource = new CancellationTokenSource();
   4:     private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource();
   5:     private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource();    
   7:     public CancellationToken ApplicationStarted
   8:     {
   9:         get { return _startedSource.Token; }
  10:     }
  11:     public CancellationToken ApplicationStopped
  12:     {
  13:         get { return _stoppedSource.Token; }
  14:     }
  15:     public CancellationToken ApplicationStopping
  16:     {
  17:         get { return _stoppingSource.Token; }
  18:     }
  20:     public void NotifyStarted()
  21:     {
  22:         _startedSource.Cancel(false);
  23:     }
  24:     public void NotifyStopped()
  25:     {
  26:         _stoppedSource.Cancel(false);
  27:     }
  28:     public void StopApplication()
  29:     {
  30:         _stoppingSource.Cancel(false);
  31:     }
  32: }

When webhost is started due to the execution of the start method, it will eventually call the NotifyStarted method of applicationlife to send a signal that the application has been successfully started. I don't know that readers' friends have noticed that WebHost just defines the Start method for launching applications, and has never defined the Stop or Close method that terminates the application. It only invokes the StopApplication method of ApplicationLifetime in the Dispose method.

   1: public class WebHost : IWebHost
   2: {    
   3:     private ApplicationLifetime _applicationLifetime;
   4:     public IServiceProvider Services { get;}
   6:     public void Start()
   7:     {
   8:        ...
   9:         _applicationLifetime.NotifyStarted();
  10:     }
  12:     public void Dispose()
  13:     {
  14:         _applicationLifetime.StopApplication();
  15:         (this.Services as IDisposable)?.Dispose();
  16:         _applicationLifetime.NotifyStopped();
  17:     }
  18:     ...
  19: }

2, Run method of WebHost

We know that starting the application is finally completed by calling the Start method of WebHost as the host, but none of the instances we demonstrated before have explicitly called this method. We call its extension method Run. There is no doubt that the Run method of WebHost will call the Start method to Start WebHost, but what else is special about this Run method?

In addition to starting WebHost, the Run method actually blocks the current process until the application closes. We know that the intention of closing an application is to use applicationlife to send a corresponding signal. Therefore, when starting WebHost, this Run method will wait by blocking the current thread until it receives the signal. The code snippets shown below basically reflect the implementation logic of the two extension methods Run.

   1: public static class WebHostExtensions
   2: {
   3:     public static void Run(this IWebHost host)
   4:     {
   5:         using (CancellationTokenSource cts = new CancellationTokenSource())
   6:         {
   7:             //Ctrl+C: close app
   8:             Console.CancelKeyPress +=  (sender, args) =>
   9:             {
  10:                 cts.Cancel();
  11:                 args.Cancel = true;
  12:             };
  13:             host.Run(cts.Token);
  14:         }
  15:     }
  17:     public static void Run(this IWebHost host, CancellationToken token)
  18:     {
  19:         using (host)
  20:         {
  21:             //Display application basic information
  22:             host.Start();
  23:             IApplicationLifetime applicationLifetime = host.Services.GetService<IApplicationLifetime>();
  24:             token.Register(state => ((IApplicationLifetime)state).StopApplication(), applicationLifetime);
  25:             applicationLifetime.ApplicationStopping.WaitHandle.WaitOne();
  26:         }
  27:     }
  28: }

The above code snippet also reflects another detail. Although WebHost implements the IDisposable interface, in principle, we need to explicitly call its Dispose method when closing. The call to this method is very important because its ServiceProvider can only be recycled and released when this method is called. However, none of the previously demonstrated examples did so, because the Run method will automatically help recycle and release the specified WebHost.

3, Remote shutdown application

Since the WebHost will use the application lifetime to wait for the stop signal to be sent after startup, this means that it forms ASP Net core pipeline server and any middleware can call the StopApplication of applicationlife to close the application at an appropriate time. For< Server's "leading" position in the pipeline >We know that an applicationlife object must be specified when constructing this object. Its fundamental purpose is to use this object to close applications when sending some unrecoverable errors.

Next, we will demonstrate how to use the applicationlife object in a middleware to remotely close applications through an example. For this reason, we name the middleware RemoteStopMiddleware. The principle of RemoteStopMiddleware is very simple. We send a Head request remotely, and add a header named "stop application" in the request to the intention of closing the application. After receiving the request, the middleware will close the application, An "application stopped" header will be added to the response, indicating that the application has been closed.

   1: public class RemoteStopMiddleware
   2: {
   3:     private RequestDelegate _next;
   4:     private const string     RequestHeader      = "Stop-Application";
   5:     private const string     ResponseHeader     = "Application-Stopped";
   7:     public RemoteStopMiddleware(RequestDelegate next)
   8:     {
   9:         _next = next;
  10:     }
  12:     public async Task Invoke(HttpContext context, IApplicationLifetime lifetime)
  13:     {
  14:         if (context.Request.Method == "HEAD" && context.Request.Headers[RequestHeader].FirstOrDefault() == "Yes")
  15:         {
  16:             context.Response.Headers.Add(ResponseHeader, "Yes");
  17:             lifetime.StopApplication();
  18:         }
  19:         else
  20:         {
  21:             await  _next(context);
  22:         }
  23:     }
  24: }

The code fragment shown above is the complete definition of RemoteStopMiddleware middleware. The implementation logic is very simple, so there is no need to explain it again. In a console application, we use the following program to start a Hello World application and register the RemoteStopMiddleware middleware. After starting the application, we use Fiddler to send three requests to the target address, including the first and third ordinary GET requests, and the second is to remotely close the application's HEAD request. The contents of the three requests and responses are shown below. Since the application is closed by the second request, the third request will return a response with status code 502.

   1: //1st request and response
   2: GET http://localhost:5000/ HTTP/1.1
   3: User-Agent: Fiddler
   4: Host: localhost:5000
   6: HTTP/1.1 200 OK
   7: Date: Sun, 06 Nov 2016 06:15:03 GMT
   8: Transfer-Encoding: chunked
   9: Server: Kestrel
  11: Hello world!
  13: //2nd request and response
  14: HEAD http://localhost:5000/ HTTP/1.1
  15: Stop-Application: Yes
  16: User-Agent: Fiddler
  17: Host: localhost:5000
  19: HTTP/1.1 200 OK
  20: Date: Sun, 06 Nov 2016 06:15:34 GMT
  21: Server: Kestrel
  22: Application-Stopped: Yes
  24: //3rd request and response
  25: GET http://localhost:5000/ HTTP/1.1
  26: User-Agent: Fiddler
  27: Host: localhost:5000
  29: HTTP/1.1 502 Fiddler - Connection Failed
  30: Date: Sun, 06 Nov 2016 06:15:44 GMT
  31: Content-Type: text/html; charset=UTF-8
  32: Connection: close
  33: Cache-Control: no-cache, must-revalidate
  34: Timestamp: 14:15:44.790
  36: [Fiddler] The connection to 'localhost' failed...