如何远程关闭一个ASP.NET Core应用?

在《历数依赖注入的N种玩法》演示系统自动注册服务的实例中,我们会发现输出的列表包含两个特殊的服务,它们的对应的服务接口分别是IApplicationLifetime和IHostingEnvironment,我们将分别实现这两个接口的服务统称在ApplicationLifetime和HostingEnvironment。我们从其命名即可以看出ApplicationLifetime与应用的声明周期有关,而HostingEnvironment则用来表示当前的执行环境,本篇文章我们着重来了解ApplicationLifetime与整个AASP.NET
Core应用的生命周期有何关系。[本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、ApplicationLifetime
二、WebHost的Run方法
三、远程关闭应用

一、ApplicationLifetime

从命名的角度来看,ApplicationLifetime貌似是对当前应用生命周期的描述,而实际上它存在的目的仅仅是在应用启动和关闭时对相关组件发送相应的信号或者通知而已。如下面的代码片段所示,IApplicationLifetime接口具有三个CancellationToken类型的属性(ApplicationStarted、ApplicationStopping和ApplicationStopped),如果需要在应用自动和终止前后执行某种操作,我们可以注册相应的回调在这三个CancellationToken对象上。除了这三个类型为CancellationToken的属性,IApplicationLifetime接口还定义了一个StopApplication方法,我们可以调用这个方法发送关闭应用的信号,并最终真正地关闭应用。

   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是具有如下定义的一个同名类型。可以看出它实现的三个属性返回的CancellationToken对象是通过三个对应的CancellationTokenSource生成。除了实现IApplicationLifetime接口的StopApplication方法用于发送“正在关闭”通知之外,这个类型还定义了额外两个方法(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的时候,会以阻塞当前线程的方式等待直至接收到这个信号。如下所示的代码片段基本上体现了这两个扩展方法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<IApplicationLifetime>();
  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的StopApplication来关闭应用。对于《服务器在管道中的“龙头”地位》介绍的KestrelServer,我们知道在构造这个对象的时候必须指定一个ApplicationLifetime对象,其根本的目的在于当发送某些无法恢复的错误时,它可以利用这个对象关闭应用。

接下来我们通过实例的方式来演示如何在一个中间件中利用这个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发送向目标地址发送三次请求,其中第一次和第三次普通的GET请求,而第二次则是为了远程关闭应用的HEAD请求。如下所示的是三次请求与响应的内容,由于应用被第二次请求关闭,所以第三次请求会返回一个状态码为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: 
  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: 
  23:  
  24: //第3次请求与响应
  25: GET http://localhost:5000/ HTTP/1.1
  26: User-Agent: Fiddler
  27: Host: localhost:5000
  28:  
  29: HTTP/1.1  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...

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-09-24 05:20:08

如何远程关闭一个ASP.NET Core应用?的相关文章

【翻译】在Mac上使用VSCode创建你的第一个Asp.Net Core应用

Setting Up Your Development Environment 设置你的开发环境 To setup your development machine download and install .NET Core and Visual Studio Code with the C# extension. Node.js and npm is also required. If not already installed visit nodejs.org. 首先在你的机器上下载.Ne

.NET Core RC2发布在即,我们试着用记事本编写一个ASP.NET Core RC2 MVC程序

在.NET Core 1.0.0 RC2即将正式发布之际,我也应应景,针对RC2 Preview版本编写一个史上最简单的MVC应用.由于VS 2015目前尚不支持,VS Code的智能感知尚欠火候,所以我们直接采用最原始的记事本来编写这个MVC应用.[源代码从这里下载] 目录 步骤一.安装最新的.NET Core SDK 步骤二.定义源代码和配置         定义NuGet.xml         定义Project.json         定义入口程序         定义初始化类型  

解析如何利用一个ASP.NET Core应用来发布静态文件_实用技巧

虽然ASP.NET Core是一款"动态"的Web服务端框架,但是在很多情况下都需要处理针对静态文件的请求,最为常见的就是这对JavaScript脚本文件.CSS样式文件和图片文件的请求.针对不同格式的静态文件请求的处理,ASP.NET Core为我们提供了三个中间件,它们将是本系列文章论述的重点.不过在针对对它们展开介绍之前,我们照理通过一些简单的实例来体验一下如何在一个ASP.NET Core应用中发布静态文件. 目录 一.以Web的形式读取文件 二.浏览目录内容 三.显示默认页面

ASP.NET Core框架揭秘(持续更新中…)

之前写了一系列关于.NET Core/ASP.NET Core的文章,但是大都是针对RC版本.到了正式的RTM,很多地方都发生了改变,所以我会将之前发布的文章针对正式版本的.NET Core 1.0进行改写.除此之外,我还会撰写一系列与此相关的文章,这些文章以ASP.NET Core为核心,我个人将它们分成三个主要的部分,即编程基础.支撑框架和管道详解.其中编程基础主要涉及与ASP.NET Core独特的编程模型和相关编程技巧.支撑框架则介绍支撑ASP.NET Core的多个独立的框架,比如依赖

浅谈如何在ASP.NET Core中实现一个基础的身份认证_实用技巧

ASP.NET终于可以跨平台了,但是不是我们常用的ASP.NET, 而是叫一个ASP.NET Core的新平台,他可以跨Windows, Linux, OS X等平台来部署你的web应用程序,你可以理解为,这个框架就是ASP.NET的下一个版本,相对于传统ASP.NET程序,它还是有一些不同的地方的,比如很多类库在这两个平台之间是不通用的. 今天首先我们在ASP.NET Core中来实现一个基础的身份认证,既登陆功能. 前期准备: 1.推荐使用 VS 2015 Update3 作为你的IDE,下

ASP.NET Core中如影随形的”依赖注入”[上]: 从两个不同的ServiceProvider说起

我们一致在说 ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点.由于前面两章已经涵盖了依赖注入在管道构建过程中以及管道在处理请求过程的应用,但是内容相对分散和零碎,我们有必要针对这个主题作一个归纳性的介绍.采用依赖注入的服务均由某个ServiceProvider来提供,但是在ASP.NET Core管道涉及到两个不同的ServiceProvider,其中一个是在管道成功构建后创建并绑定到WebHost上的ServiceProvider,对应着

ASP.NET Core中的依赖注入(3): 服务的注册与提供

在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core中的DI容器体现为一个实现了IServiceProvider接口的对象. ServiceProvider与ServiceDescriptor 服务的注册与提供     利用ServiceProvider来提供服务     提供一个服务实例的集合     获取ServiceProvider自身对象  

聊聊ASP.NET Core默认提供的这个跨平台的服务器——KestrelServer

跨平台是ASP.NET Core一个显著的特性,而KestrelServer是目前微软推出了唯一一个能够真正跨平台的Server.KestrelServer利用一个名为KestrelEngine的网络引擎实现对请求的监听.接收和响应.KetrelServer之所以具有跨平台的特质,源于KestrelEngine是在一个名为libuv的跨平台网络库上开发的. 目录 一.libuv 二.KestrelServer 三.KestrelServerOptions 四.ApplicationLifetim

ASP.NET Core 2.0 支付宝当面付之扫码支付

原文:ASP.NET Core 2.0 支付宝当面付之扫码支付 前言 自从微软更换了CEO以后,微软的战略方向有了相当大的变化,不再是那么封闭,开源了许多东西,拥抱开源社区,.NET实现跨平台,收购xamarin并免费提供给开发者等等.我本人是很喜欢.net的,并希望.net core能够崛起.我是从.net core 1.1的时候开始使用的,到现在的.net core 2.0..net core 2.0比1.1有了一些改变,api也增加了很多,用着更顺手了,最近在做asp.net core 对