文章出處

有關Hosting的基礎知識 

    Hosting是一個非常重要,但又很難翻譯成中文的概念。翻譯成:寄宿,大概能勉強地傳達它的意思。我們知道,有一些病毒離開了活體之后就會死亡,我們把那些活體稱為病毒的宿主。把這種概念應用到托管程序上來,CLR不能單獨存在,它必須依賴于某一個進程,我們把這種狀況稱之為:CLR必須寄宿于某一個進程中,而那個進程就是宿主

   ASP.NET Core的一個大的改變就是就是將Web應用程序改成了自寄宿。什么意思呢?我們知道,在之前的ASP.NET版本中,ASP.NET的Web應用程序都是深度依賴IIS和Windows Server,以至于ASP.NET只能在Windows Server上運行。之所以出現這種情況,就是應為我們開發的所有Web應用程序都是寄宿在IIS進程中的。一般來說,一個進程只能加載一個CLR(不同進程之間可以加載不同的版本的CLR),為了托管多個Web應用程序,IIS使用了應用程序池這種東西來模擬進程的行為,從而為不同的Web程序加載不同的運行時來托管它們。

    有關CLR和寄宿的知識,如果有興趣,可以參閱《CLR via C#》。

    我們可以查看一下以前版本的ASP.NET程序,它是沒有Main()函數的,也就是說它沒有程序入口點,不是單獨的進程。對于應用程序開發來說,這個問題并不大,因為開發者在意的Web程序的邏輯、數據安全等問題,而不是應用程序如何被加載。但對于一個Web框架來說,這個問題非常嚴重,因為它高度依賴IIS和Windows Server,減少了它的適用范圍。如果我們查看ASP.NET Core的程序,你會發現它本質上就是一個控制臺程序,如果我們把那些在Main()函數中自動生成的代碼都刪掉(VS2015的模板會自帶一些代碼),加上Console.WriteLine("Hello World!"); 它就會在控制臺中打出Hello World!由于ASP.NET Core的程序自身有程序入口點,所以自身就是一個進程,它可以為自己加載合適的CLR來運行Web應用,這種情況就是自寄宿。這么做的最大的好處就是可以脫離IIS,從而脫離Windows Server的桎梏。只要對應操作系統上有符合CLR規范的運行時,那ASP.NET Core的應用就可以部署在那個操作系統上。.NET Core里包含了微軟開發的跨平臺CLR運行時,可以運行在Windows,Linux和OSX上,借助它ASP.NET Core的應用程序就可以部署在這些操作系統上。

    說到這里,就只能下最后一個問題,IIS還扮演什么角色?當應用部署在Windows上時,微軟推薦將IIS通過ASP.NET Core Module(之前的HttpPlatformHandler)模塊作為Web應用的反向代理服務器(reverse-proxy server)。這個服務器的作用就是將請求轉發到Web應用真正的服務器:

  • WebListener (只能在Windows平臺)
  • Kestrel         (跨平臺服務器,比WebListener功能稍弱)

    服務器的問題會在下一篇文章中說明。前面上了那么多開胃菜,終于可以上正菜了。以下出現的所有源碼都可以在Microsoft.AspNetCore.Hosting項目中找到。

Main函數里發生了什么

    如果新建一個ASP.NET Core應用,那么最先和我們打交道的就是Main函數中的WebHostBuilder,我們先來看看它的源碼:

 1     //所有方法刪除了錯誤處理,只保留主邏輯
 2     public class WebHostBuilder : IWebHostBuilder
 3     {
 4         private readonly IHostingEnvironment _hostingEnvironment;
 5         private readonly List<Action<IServiceCollection>> _configureServicesDelegates;
 6         private readonly List<Action<ILoggerFactory>> _configureLoggingDelegates;
 7 
 8         private IConfiguration _config = new ConfigurationBuilder().AddInMemoryCollection().Build();
 9         private ILoggerFactory _loggerFactory;
10         private WebHostOptions _options;
11         
12         //_config里面存儲的是每個應用都有的東西,比如根目錄,StartUp類的程序集公鑰等。
13         public IWebHostBuilder UseSetting(string key, string value)
14         {
15             _config[key] = value;
16             return this;
17         }
18         public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices)
19         {
20             _configureServicesDelegates.Add(configureServices);
21             return this;
22         }
23         public IWebHost Build()
24         {
25             var hostingServices = BuildHostingServices();//在字段中,服務是以委托的形式存在,這個方法將配置的服務放入ServiceCollection,同時加入很多其他服務。
26             var hostingContainer = hostingServices.BuildServiceProvider();
27 
28             var host = new WebHost(hostingServices, hostingContainer, _options, _config);//構造一個WebHost
29 
30             host.Initialize();//這個方法很重要,后面講
31 
32             return host;
33         }
34        //在這個方法中,_options會根據_config字段生成,StartUp類會以IStartUp->StartUp的形式注冊到依賴注入容器中,即使StartUp不實現接口
35        //注意這個方法只在Build()方法中被調用,所以UseStartUp()方法應該在Build()方法之前被調用
36        private IServiceCollection BuildHostingServices(){...}

     在每個ASP.NET Core程序的Main函數里面,都有很多UseXXX()的擴展方法,這些方法最終會調用WebHostBuilder.UseSetting()或者ConfigureService()方法。這兩個東西的區別在于,_config里面存的東西是每個Web程序都有的部分,比如應用的根目錄,StartUp類的程序集信息等等;而Service是Web應用的可選部分,比如UseKestrel()最終調用的是ConfigureServices,因為服務器并不是必須的,可以開發符合Owin規范的程序使應用部分和服務器部分分開來。還有一個區別在于_config中的內容比較簡單,比如存儲的應用根目錄這些東西不必以服務的形式存在,當然有關StartUp的信息還是會以服務的形式注冊到依賴注入容器。

    WebHostBuilder的這些信息最終會傳給WebHost,注意構造WebHost之后,還調用了它的Initialize()方法。我們來看看WebHost類的源碼。

 1     public class WebHost : IWebHost
 2     {
 3         private readonly IServiceCollection _applicationServiceCollection;//UseStartUp等服務
 4         private IStartup _startup;//StartUp
 5 
 6         private readonly IServiceProvider _hostingServiceProvider;//上面那個服務集合生成的Provider
 7         private readonly ApplicationLifetime _applicationLifetime;//為了異步能取消
 8         private readonly WebHostOptions _options;
 9         private readonly IConfiguration _config;
10 
11         private IServiceProvider _applicationServices; //StartUp ConfigureService方法生成的服務+原本applicationService
12         private RequestDelegate _application;
13         private IServer Server { get; set; } //服務器字段,包含了處理的請求的信息
14         //下面是相關方法
15         public void Initialize()//轉發給BuildApplication()方法
16         {
17             if (_application == null)
18             {
19                 _application = BuildApplication();
20             }
21         }
22         private void EnsureApplicationServices()//把在StartUp.ConfigureServices()方法中注冊的服務加到集合中
23         {
24             if (_applicationServices == null)
25             {
26                 EnsureStartup();
27                 _applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
28             }
29         }
30         private void EnsureStartup()//構造StartUp服務類
31         {
32             _startup = _hostingServiceProvider.GetRequiredService<IStartup>();
33         }
34 
35         //構造委托鏈_application ,ApplicationService存儲現在注冊的服務
36         private RequestDelegate BuildApplication()
37         {
38             try
39             {
40                 EnsureApplicationServices();
41                 EnsureServer();
42 
43                 var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
44                 var builder = builderFactory.CreateBuilder(Server.Features);
45                 builder.ApplicationServices = _applicationServices;
46 
47                 var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
48                 Action<IApplicationBuilder> configure = _startup.Configure;
49                 foreach (var filter in startupFilters.Reverse())
50                 {
51                     configure = filter.Configure(configure);//委托鏈
52                 }
53 
54                 configure(builder);
55 
56                 return builder.Build();//Use方法最終都會ApplicationBuilder.Use()方法,以Fun<RequestDelegate, RequestDelegate>的形式存在List中
57                                        //Build的時候利用上面的List,會生成一個委托鏈。
58                                        //在每一個RequestDelegate中,會用反射生成對應的Middleware類,然后調用Invoke()方法
59             }
60             catch (Exception ex) when (_options.CaptureStartupErrors)
61             {//省略出錯的處理,返回HTTP500狀態碼}
62         }

     直接看BuildApplication()方法,

  1. 調用EnsureApplicationServices()方法,實際上就是調用StartUp.ConfigureServices()這個方法,這個方法大家肯定很熟,就是把那些在ConfigureServices()注冊的服務放到_applicationServices字段里;
  2. 調用EnsureServer()方法,確保Server存在,并監聽正確的端口,默認是 http://localhost:5000 ,這個方法這里沒有列出。
  3. 構造一個ApplicationBuilder,并把注冊的服務轉移給它;
  4. 用StartUp.Configure以及StartUpFilters.Configure構造一個服務委托鏈;
  5. 引發服務委托鏈,相當于調用里面的UseXXX()方法,注意看我注釋的解釋,此時所有服務都以使用順序、以Func<RequestDelegate, RequestDelegate>形式存儲在一個ApplicationBuilder的List字段中
  6. Invoke List字段中的所有對象,生成一個委托鏈,就是我們所說的請求管道(Pipeline)。

    請求管道已經生成完畢,剩下的就是請HttpContext進入這個管道了,我們看看WebHost.Run()發生了什么

Host.Run()方法中發生了什么?

     Host.Run()內部會調用Host.Start()方法,然后再在控制臺輸出一些信息,并且傳入一個CancellationToken 允許隨時中斷程序。那么看來得去瞧瞧Host.Start()方法了。

1         public virtual void Start()
2         {
3             //省略一些參數構造,以及有關logger的代碼
4             Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory)); //只有這句是關鍵 
5         }

     它調用了Server.Start()方法,從方法名上大概能猜出來是干嘛,具體細節留在有關Server的文章里面講。去看看HostingApplication這個類:

 1     public class HostingApplication : IHttpApplication<HostingApplication.Context>
 2     {
 3         private readonly RequestDelegate _application;
 4         private readonly ILogger _logger;
 5         private readonly DiagnosticSource _diagnosticSource;
 6         private readonly IHttpContextFactory _httpContextFactory;
 7 
 8         public Context CreateContext(IFeatureCollection contextFeatures){...}//創建一個上下文
 9 
10         public void DisposeContext(Context context, Exception exception){...}
11 
12         public Task ProcessRequestAsync(Context context)
13         {
14             return _application(context.HttpContext);
15         }
16 
17         public struct Context
18         {
19             public HttpContext HttpContext { get; set; }
20             public IDisposable Scope { get; set; }
21             public long StartTimestamp { get; set; }
22         }

     顯然它的作用就是配合Server去創建上下文,大概的過程就是

  1. 服務器把請求的信息放入一個IFeatureCollection的變量里面;
  2. 利用上面的信息構造上下文;
  3. 調用ProcessRequestAsync()方法處理請求,此時請求進入處理管道(Pipeline)。

總結

  1. 首先使用WebHostBuildler注冊基本信息,比如用哪個服務器?根目錄是哪個?StartUp的元數據等等;
  2. 在WebHost = WebHostBuildler.Build()過程中,添加大量基本服務+StartUp.ConfigureServices()方法中的服務;
  3. 在WebHost.Initialize()方法中,利用StartUp.Configure()方法中使用的服務+一些默認使用的服務組建請求管道,并存儲在_application字段中;
  4. 使用_application構造一個HostingApplication,并傳入WebHost.Run()->WebHost.Start()->Server.Start()方法;
  5. Server使用HostingApplication來構造HttpContext,并使用請求管線(Pipeline)處理它。

文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()