博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何远程关闭一个ASP.NET Core应用?
阅读量:7018 次
发布时间:2019-06-28

本文共 6785 字,大约阅读时间需要 22 分钟。

在《》演示系统自动注册服务的实例中,我们会发现输出的列表包含两个特殊的服务,它们的对应的服务接口分别是IApplicationLifetime和IHostingEnvironment,我们将分别实现这两个接口的服务统称在ApplicationLifetime和HostingEnvironment。我们从其命名即可以看出ApplicationLifetime与应用的声明周期有关,而HostingEnvironment则用来表示当前的执行环境,本篇文章我们着重来了解ApplicationLifetime与整个AASP.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
();
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...

转载地址:http://aewxl.baihongyu.com/

你可能感兴趣的文章
使用POI实现报表打印功能
查看>>
英国电信 云计算还不成熟
查看>>
运行于显卡(GPU)的Rootkit木马和键盘记录器问世
查看>>
三星推出高容量、企业级绿色SSD
查看>>
PHP/如何在Linux服务器中隐藏PHP版本
查看>>
高速加密:IBM发布更猛更快的z14大型机
查看>>
任何口令都可访问Advantech工业网关
查看>>
如何让CIO的ITIL实施“一帆风顺”
查看>>
迅雷移动端业务发展迅猛 Q2广告收入同比上涨41.6%
查看>>
迎接数字化社会,华为积极布局
查看>>
阿里云成为CNCF金牌会员 提供云端Kubernetes解决方案
查看>>
F5:亡羊补牢为时已晚,投资网络安全比处理漏洞更划算
查看>>
青云QingCloud推出HBase集群服务 支持SQL等高级功能
查看>>
Windows远程桌面漏洞Esteemaudit(CVE-2017-9073)补丁简要分析
查看>>
Arbor Networks凭借业界全面的 DDoS 防御组合为各类客户提供可用性保护
查看>>
达沃时代帮助VSAN提供NAS方案
查看>>
买服务器网络设备时该考虑哪些因素?
查看>>
悲催的CISO:数据泄露事故的替罪羊
查看>>
2015年十大安全故事回顾
查看>>
数据挖掘与预测分析术语总结
查看>>