リモートでASPを閉じる方法NET Coreアプリ?



一、アプリケーションライフタイム


名前付けの観点から、ApplicationLifetimeは現在のアプリケーションライフサイクルの記述のように見えますが、実際には、アプリケーションの起動と停止時に関連するコンポーネントに対して対応する信号または通知を送信することを目的としています.次のコードフラグメントに示すように、IApplicationLifetimeインタフェースには、3つのCancellationTokenタイプのプロパティ(ApplicationStarted、ApplicationStopping、およびApplicationStopped)があり、アプリケーションの自動および終了の前後で何らかの操作を実行する必要がある場合は、対応するコールバックを3つのCancellationTokenオブジェクトに登録できます.この3つのタイプがCancellationTokenのプロパティに加えて、IApplicationLifetimeインタフェースはStoppApplicationメソッドを定義し、このメソッドを呼び出してアプリケーションを閉じる信号を送信し、最終的に本当にアプリケーションを閉じることができます.
   1: public interface IApplicationLifetime
   2: {
   3:     CancellationToken ApplicationStarted { get; }
   4:     CancellationToken ApplicationStopping { get; }
   5:     CancellationToken ApplicationStopped { get; }
   6:
   7:     void StopApplication();
   8: }
ASP.NET Coreのデフォルトで使用されるApplicationLifetimeは、次のように定義された同じ名前のタイプです.実装された3つの属性が返すCancellationTokenオブジェクトは、3つの対応するCancellationTokenSourceによって生成されることがわかる.IApplicationLifetimeインタフェースを実装するStopApplicationメソッドは、「クローズ中」通知を送信するために使用されるほか、このタイプでは、「オープン/クローズ済」通知を送信するための追加の2つのメソッド(NotifyStartedおよびNotifyStopped)も定義されています.
   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();
   6:
   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:     }
  19:
  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: }
WebHostがStartメソッドの実行によってオンになった場合、アプリケーションが正常に起動した信号を外部に送信するためにApplicationLifetimeのNotifyStartedメソッドが呼び出されます.WebHostはアプリケーションを起動するStartメソッドのみを定義し、アプリケーションを終了するStopまたはCloseメソッドを定義していないことに気づいた.DisposeメソッドでApplicationLifetimeのStopApplicationメソッドを呼び出しただけだ.
   1: public class WebHost : IWebHost
   2: {
   3:     private ApplicationLifetime _applicationLifetime;
   4:     public IServiceProvider Services { get;}
   5:
   6:     public void Start()
   7:     {
   8:        ...
   9:         _applicationLifetime.NotifyStarted();
  10:     }
  11:
  12:     public void Dispose()
  13:     {
  14:         _applicationLifetime.StopApplication();
  15:         (this.Services as IDisposable)?.Dispose();
  16:         _applicationLifetime.NotifyStopped();
  17:     }
  18:     ...
  19: }

二、WebHostのRun方法


起動アプリケーションは最終的にホストであるWebHostのStartメソッドを呼び出すことによって達成されることを知っているが,これまで実証したすべてのインスタンスはこのメソッドを明示的に呼び出すことはなく,その拡張メソッドRunを呼び出す.間違いなくWebHostのRunメソッドは必ずStartメソッドを呼び出してWebHostを開くが、それ以外にこのRunメソッドには何か特別なところがあるのだろうか.
Runメソッドの目的は、WebHostを起動することに加えて、実際にはアプリケーションが閉じるまで現在のプロセスをブロックします.アプリケーションのクローズの意図はApplicationLifetimeを用いて対応する信号を送信することによって実現されることを知っているので,このRun法はWebHostを起動すると,この信号が受信されるまで現在のスレッドをブロックするように待機する.この2つの拡張方法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:     
   8:             Console.CancelKeyPress +=  (sender, args) =>
   9:             {
  10:                 cts.Cancel();
  11:                 args.Cancel = true;
  12:             };
  13:             host.Run(cts.Token);
  14:         }
  15:     }
  16:
  17:     public static void Run(this IWebHost host, CancellationToken token)
  18:     {
  19:         using (host)
  20:         {
  21:             //        
  22:             host.Start();
  23:             IApplicationLifetime applicationLifetime = host.Services.GetService();
  24:             token.Register(state => ((IApplicationLifetime)state).StopApplication(), applicationLifetime);
  25:             applicationLifetime.ApplicationStopping.WaitHandle.WaitOne();
  26:         }
  27:     }
  28: }
上記のコードフラグメントは、もう一つの詳細を示しています.WebHostはIDisposableインタフェースを実装していますが、原則としてオフのときにDisposeメソッドを明示的に呼び出す必要があります.このメソッドの呼び出しは、そのServiceProviderがこのメソッドが呼び出されたときにのみ回収されて解放されるため、非常に重要です.しかし、Runメソッドは指定されたWebHostを自動的に回収して解放するのに役立つため、これまでのすべてのプレゼンテーションのインスタンスはそうしていませんでした.

三、リモートシャットダウン応用


WebHostは起動後にApplicationLifetimeを利用してStopping信号の送信を待つので、これはASPを構成することを意味する.NET Coreパイプラインのサーバとどのミドルウェアも、アプリケーションを閉じるために適切なタイミングでApplicationLifetimeのStoppApplicationを呼び出すことができます.「パイプ内のサーバーのリーダーシップ」で紹介されているKestrelServerでは、このオブジェクトを構築するときにアプリケーションLifetimeオブジェクトを指定する必要があることを知っています.根本的な目的は、リカバリできないエラーを送信したときに、このオブジェクトを使用してアプリケーションを閉じることです.
次に、アプリケーションのリモートシャットダウンを1つのミドルウェアでこのApplicationLifetimeオブジェクトを使用して実現する方法を例に示します.そのため、このミドルウェアをRemoteStopMiddlewareと命名します.RemoteStopMiddlewareは、リモートシャットダウンアプリケーションを実装する原理が簡単で、リモートでHeadリクエストを送信し、このリクエストに「Stop-APplication」というヘッダを追加してアプリケーションをシャットダウンする意図に伝え、ミドルウェアはこのリクエストを受信するとアプリケーションをシャットダウンし、応答に「Application-Stopped」ヘッダが追加されることは、アプリケーションがシャットダウンされたことを示す.
   1: public class RemoteStopMiddleware
   2: {
   3:     private RequestDelegate _next;
   4:     private const string     RequestHeader      = "Stop-Application";
   5:     private const string     ResponseHeader     = "Application-Stopped";
   6:
   7:     public RemoteStopMiddleware(RequestDelegate next)
   8:     {
   9:         _next = next;
  10:     }
  11:
  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: }
上記のコードフラグメントはRemoteStopMiddlewareというミドルウェアの完全な定義であり,実装ロジックは簡単で,これ以上説明する必要はない.コンソールアプリケーションでは、次のようなプログラムを使用してHello Worldアプリケーションを起動し、このRemoteStopMiddlewareミドルウェアを登録します.このアプリケーションを起動した後,Fiddlerを用いてターゲットアドレスに3回の要求を送信し,そのうち1回目と3回目は通常のGET要求であり,2回目はアプリケーションのHEAD要求をリモートで閉じるためである.以下に示すように、3回目の要求と応答の内容は、アプリケーションが2回目の要求によって閉じられるため、3回目の要求はステータスコード502の応答を返す.
   1: // 1      
   2: GET http://localhost:5000/ HTTP/1.1
   3: User-Agent: Fiddler
   4: Host: localhost:5000
   5:
   6: HTTP/1.1 200 OK
   7: Date: Sun, 06 Nov 2016 06:15:03 GMT
   8: Transfer-Encoding: chunked
   9: Server: Kestrel
  10:
  11: Hello world!
  12:
  13: // 2      
  14: HEAD http://localhost:5000/ HTTP/1.1
  15: Stop-Application: Yes
  16: User-Agent: Fiddler
  17: Host: localhost:5000
  18:
  19: HTTP/1.1 200 OK
  20: Date: Sun, 06 Nov 2016 06:15:34 GMT
  21: Server: Kestrel
  22: Application-Stopped: Yes
  23:
  24: // 3      
  25: GET http://localhost:5000/ HTTP/1.1
  26: User-Agent: Fiddler
  27: Host: localhost:5000
  28:
  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
  35:
  36: [Fiddler] The connection to 'localhost' failed...