ASP.NET Core真实管道详解[2]:Server是如何完成针对请求的监听、接收与响应的【上】
Server是ASP .NET Core管道的第一個(gè)節(jié)點(diǎn),負(fù)責(zé)完整請(qǐng)求的監(jiān)聽(tīng)和接收,最終對(duì)請(qǐng)求的響應(yīng)同樣也由它完成。Server是我們對(duì)所有實(shí)現(xiàn)了IServer接口的所有類(lèi)型以及對(duì)應(yīng)對(duì)象的統(tǒng)稱(chēng),如下面的代碼片段所示,這個(gè)接口具有一個(gè)只讀屬性Features返回描述自身特性集合的FeatureCollection對(duì)象,另一個(gè)Start方法用于啟動(dòng)服務(wù)器。
1: public interface IServer : IDisposable 2: { 3: IFeatureCollection Features { get; } 4: void Start<TContext>(IHttpApplication<TContext> application); 5: }當(dāng)我們Start方法啟動(dòng)指定的Server的時(shí)候,它必須指定一個(gè)類(lèi)型為IHttpApplication<TContext>的參數(shù),我們將實(shí)現(xiàn)才接口的所有類(lèi)型及其對(duì)應(yīng)對(duì)象統(tǒng)稱(chēng)為HttpApplication。當(dāng)Server在接收到抵達(dá)的請(qǐng)求之后,實(shí)際上會(huì)直接交給這個(gè)HttpApplication對(duì)象來(lái)處理,所以我們需要先來(lái)認(rèn)識(shí)一下這個(gè)對(duì)象。
目錄
一、HttpApplication
二、請(qǐng)求的處理與執(zhí)行上下文的創(chuàng)建與釋放
三、日志記錄
??? 請(qǐng)求處理開(kāi)始與結(jié)束時(shí)記錄的日志
??? 針對(duì)請(qǐng)求的日志上下文范圍
??? 請(qǐng)求唯一標(biāo)識(shí)的生成
一、HttpApplication
對(duì)于ASP.NET Core管道來(lái)說(shuō),HttpApplication被用來(lái)處理Server接收的請(qǐng)求,這個(gè)對(duì)象可以視為對(duì)注冊(cè)的所有中間件的封裝,它對(duì)請(qǐng)求的處理工作實(shí)際上最終會(huì)委托這些中間件來(lái)完成。HttpApplication針對(duì)請(qǐng)求的處理實(shí)際上會(huì)在一個(gè)執(zhí)行上下文中完成,這個(gè)上下文實(shí)際上為應(yīng)用對(duì)單一請(qǐng)求的整個(gè)處理過(guò)程定義了一個(gè)邊界。單純描述HTTP請(qǐng)求的HttpContext是這個(gè)執(zhí)行上下文中最為核心的部分,除此之外,我們還可以根據(jù)需要將其他相關(guān)的信息定義其中,所以IHttpApplication<TContext>接口采用泛型參數(shù)的形式來(lái)表示定義這個(gè)上下文的類(lèi)型。
HttpApplication不僅僅需要在這個(gè)執(zhí)行上下文中處理Server轉(zhuǎn)發(fā)給它的請(qǐng)求,這個(gè)上下文對(duì)象的創(chuàng)建和回收釋放同樣需要由它來(lái)完成。如下面的代碼片段所示,IHttpApplication<TContext>接口的CreateContext和DisposeContext方法分別體現(xiàn)了針對(duì)執(zhí)行上下文的創(chuàng)建和釋放,CreateContext方法的參數(shù)contextFeatures表示描述原始上下文的特性集合。在此上下文中針對(duì)請(qǐng)求的處理實(shí)現(xiàn)在另一個(gè)方法ProcessRequestAsync之中。
1: public interface IHttpApplication<TContext> 2: { 3: TContext CreateContext(IFeatureCollection contextFeatures); 4: void DisposeContext(TContext context, Exception exception); 5: Task ProcessRequestAsync(TContext context); 6: }在默認(rèn)情況下創(chuàng)建的HttpApplication是一個(gè)HostingApplication對(duì)象。對(duì)于HostingApplication來(lái)說(shuō),它創(chuàng)建的執(zhí)行上下文的類(lèi)型是一個(gè)具有如下定義的結(jié)構(gòu)體Context,它內(nèi)嵌于HostingApplication類(lèi)之中。對(duì)于這個(gè)Context對(duì)象表示的針對(duì)當(dāng)前請(qǐng)求的執(zhí)行上下文來(lái)說(shuō),描述當(dāng)前HTTP請(qǐng)求的HttpContext是最為核心的部分。除了這個(gè)HttpContext屬性之外,Context還具有額外兩個(gè)屬性,其中Scope是為追蹤診斷而創(chuàng)建的日志上下文范圍,該范圍將針對(duì)同一個(gè)請(qǐng)求的多項(xiàng)日志記錄進(jìn)行關(guān)聯(lián),而另一個(gè)屬性StartTimestamp表示應(yīng)用開(kāi)始處理請(qǐng)求的時(shí)間戳。
1: public class HostingApplication : IHttpApplication<Context> 2: { 3: //省略成員 4: public struct Context 5: { 6: public HttpContext HttpContext { get; set; } 7: public IDisposable Scope { get; set; } 8: public long StartTimestamp { get; set; } 9: } 10: }
二、請(qǐng)求的處理與執(zhí)行上下文的創(chuàng)建與釋放
由于HostingApplication針對(duì)請(qǐng)求的處理是通過(guò)注冊(cè)的中間件來(lái)完成的,而后者最終會(huì)利用上面介紹的ApplicationBuilder對(duì)象轉(zhuǎn)換成一個(gè)類(lèi)型為RequestDelegate的委托對(duì)象,所以我們?cè)趧?chuàng)建HostingApplication的時(shí)候需要提供這么一個(gè)RequestDelegate對(duì)象。有HostingApplication創(chuàng)建的Context對(duì)象包含表示HTTP上下文的HttpContext對(duì)象,而后者是通過(guò)對(duì)應(yīng)的工廠(chǎng)HttpContextFactory創(chuàng)建的,所以HttpContextFactory在創(chuàng)建時(shí)也是必須要提供的。如下面的代碼片段所示,HostingApplication類(lèi)型的構(gòu)造函數(shù)需要將這兩個(gè)對(duì)象作為輸入?yún)?shù),至于另外兩個(gè)參數(shù)(logger和diagnosticSource),它們與日志記錄有關(guān),我們稍后會(huì)對(duì)此作專(zhuān)門(mén)的介紹。
1: public class HostingApplication : IHttpApplication<HostingApplication.Context> 2: { 3: private readonly RequestDelegate _application; 4: private readonly DiagnosticSource _diagnosticSource; 5: private readonly IHttpContextFactory _httpContextFactory; 6: private readonly ILogger _logger; 7:? 8: public HostingApplication(RequestDelegate application, ILogger logger, DiagnosticSource diagnosticSource, IHttpContextFactory httpContextFactory) 9: { 10: _application = application; 11: _logger = logger; 12: _diagnosticSource = diagnosticSource; 13: _httpContextFactory = httpContextFactory; 14: } 15: }下面給出的代碼片段基本體現(xiàn)了HostingApplication創(chuàng)建和釋放Context對(duì)象,以及在此上下文中處理請(qǐng)求的邏輯。在CreateContext方法中,它直接利用初始化提供的HttpContextFactory創(chuàng)建一個(gè)HttpContext并將其作為Context對(duì)象的同名屬性,至于Context額外兩個(gè)屬性(Scope和StartTimestamp)該作何設(shè)置,我們會(huì)在本節(jié)后續(xù)部分對(duì)此作專(zhuān)門(mén)介紹。實(shí)現(xiàn)在ProcessRequestAsync方法中針對(duì)請(qǐng)求的處理最終體現(xiàn)在對(duì)構(gòu)造時(shí)指定的這個(gè)RequestDelegate對(duì)象的執(zhí)行。當(dāng)DisposeContext方法被執(zhí)行的時(shí)候,Context的Scope屬性會(huì)率先被釋放,在此之后HttpContextFactory的Dispose方法被調(diào)用以完成對(duì)Context對(duì)象自身的回收釋放。
1: public class HostingApplication : IHttpApplication<HostingApplication.Context> 2: { 3: public Context CreateContext(IFeatureCollection contextFeatures) 4: { 5: //省略其他實(shí)現(xiàn)代碼 6: return new Context 7: { 8: HttpContext = _httpContextFactory.Create(contextFeatures), 9: Scope = ..., 10: StartTimestamp = ... 11: }; 12: } 13:? 14: public Task ProcessRequestAsync(Context context) 15: { 16: Return _application(context.HttpContext); 17: } 18:? 19: public void DisposeContext(Context context, Exception exception) 20: { 21: //省略其他實(shí)現(xiàn)代碼 22: context.Scope.Dispose(); 23: _httpContextFactory.Dispose(context.HttpContext); 24: } 25: }
三、日志記錄
由于管道處理其中總是在一個(gè)由HttpApplication創(chuàng)建的執(zhí)行上下文中進(jìn)行,所有上下文的創(chuàng)建和回收釋放可以視為 整個(gè)請(qǐng)求處理流程開(kāi)始和結(jié)束的標(biāo)識(shí)。對(duì)于HostingApplication來(lái)說(shuō),CreateContext和DisposeContext方法分別被調(diào)用的時(shí)候,它會(huì)利用初始化時(shí)指定的Logger對(duì)象作相應(yīng)的日志記錄。除此之外,作為開(kāi)始處理請(qǐng)求標(biāo)志的CreateContext方法還是創(chuàng)建一個(gè)日志上下文范圍,其目的是將針對(duì)同一請(qǐng)求的日志時(shí)間關(guān)聯(lián)起來(lái)。這個(gè)上下文范圍對(duì)應(yīng)著Context對(duì)象的Scope對(duì)象,通過(guò)上面的代碼片段我們可以看出針對(duì)這個(gè)日志上下文范圍的釋放同樣發(fā)生在DisposeContext方法中。
請(qǐng)求處理開(kāi)始與結(jié)束時(shí)記錄的日志
接下來(lái)我們通過(guò)實(shí)例演示的形式來(lái)看看究竟怎樣的日志消息分別被它的CreateContext和DisposeContext方法記錄下來(lái)。在一個(gè)ASP.NET Core控制臺(tái)應(yīng)用中,為了將記錄的日志消息直接打印到控制臺(tái)上,我們需要為管道使用的LoggerFactory注冊(cè)一個(gè)ConsoleLoggerProvider。在添加相應(yīng)NuGet包(“Microsoft.Extensions.Logging.Console”)之后,我們定義了如下一個(gè)Startup類(lèi)型,它采用構(gòu)造函數(shù)注入的方式得到這個(gè)LoggerFactory并調(diào)用擴(kuò)展方法AddConsole實(shí)現(xiàn)了對(duì)ConsoleLoggerProvider的注冊(cè)。
1: public class Startup 2: { 3: public Startup(ILoggerFactory loggerFactory) 4: { 5: loggerFactory.AddConsole(); 6: } 7:? 8: public void Configure(IApplicationBuilder app) 9: { 10: app.Run(context => context.Response.WriteAsync("Hello World!")); 11: } 12: }我們啟動(dòng)這個(gè)控制臺(tái)應(yīng)用讓它開(kāi)始利用KestrelServer在默認(rèn)的端口(5000)進(jìn)行請(qǐng)求監(jiān)聽(tīng),然后利用瀏覽器向?qū)?yīng)的地址(我們將目標(biāo)地址設(shè)定為“http://localhost:5000/helloworld”)發(fā)送請(qǐng)求,控制臺(tái)上將會(huì)輸出管道在請(qǐng)求處理過(guò)程中寫(xiě)入的日志消息。如下所示的兩條等級(jí)為Information的日志就是在開(kāi)始和完成請(qǐng)求時(shí)分別被HostingApplication的CreateContext和DisposeContext方法寫(xiě)入的。第一條日志包含不僅僅包含請(qǐng)求的目標(biāo)地址,還包括請(qǐng)求采用的協(xié)議(HTTP/1.1)和HTTP方法(GET),第二條則反映了整個(gè)請(qǐng)求處理過(guò)程所花的時(shí)間。
上面演示的時(shí)候請(qǐng)求被正常處理的情況下管道自身記錄的日志,如果在處理過(guò)程中拋出異常,該異常會(huì)作為參數(shù)傳遞給HostingApplication的DisposeContext方法,后者會(huì)額外寫(xiě)入一條等級(jí)為Error的日志記錄發(fā)生的錯(cuò)誤。下面的代碼片段展現(xiàn)了出現(xiàn)異常情況下寫(xiě)入的三條日志。
針對(duì)請(qǐng)求的日志上下文范圍
為了查看HostingApplication在CreateContext方法針對(duì)當(dāng)前請(qǐng)求創(chuàng)建的日志上下文范圍,我們?cè)跒長(zhǎng)oggerFactory注冊(cè)ConsoleLoggerProvider的時(shí)候需要顯式開(kāi)始針對(duì)日志上下文范圍的支持,所以我們?cè)谡{(diào)用AddConsole方法的時(shí)候?qū)rue作為額外的參數(shù)。除此之外,我們?cè)贑onfigure方法中利用注入的LoggerFactory創(chuàng)建相應(yīng)的Logger,并利用它記錄一條等級(jí)為Information的日志,日志內(nèi)容為“Write \"Hello World!\"”。
1: public class Startup 2: { 3: public Startup(ILoggerFactory loggerFactory) 4: { 5: loggerFactory.AddConsole(true); 6: } 7:? 8: public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) 9: { 10: app.Run(context => 11: { 12: loggerFactory.CreateLogger("App").LogInformation("Write \"Hello World!\""); 13: return context.Response.WriteAsync("Hello World!"); 14: }); 15: } 16: }程序啟動(dòng)后我們采用瀏覽器向相同的目標(biāo)地址(“http://localhost:5000/helloworld”)發(fā)送兩次請(qǐng)求。對(duì)于這兩次請(qǐng)求記錄的日志,它們分別是在不同的日志上下文中被寫(xiě)入的,我們可以根據(jù)這個(gè)上下文范圍對(duì)記錄下來(lái)的日志消息進(jìn)行有效地分組。針對(duì)這兩次請(qǐng)求,服務(wù)端一共有如下6條日志消息被記錄下來(lái),針對(duì)同一請(qǐng)求的三條日志具有相同的上下文范圍信息,該體現(xiàn)不僅僅包含請(qǐng)求的路徑(“/helloworld”),還具有一個(gè)唯一標(biāo)識(shí)請(qǐng)求的ID。
請(qǐng)求唯一標(biāo)識(shí)的生成
日志上下文范圍攜帶的用于唯一標(biāo)識(shí)當(dāng)前請(qǐng)求的ID,同時(shí)也可以視為當(dāng)前HttpContext的唯一標(biāo)識(shí),它對(duì)應(yīng)著HttpContext的TranceIdentifier屬性。對(duì)于DefaultHttpContext來(lái)說(shuō),針對(duì)這個(gè)屬性的讀寫(xiě)是借助一個(gè)名為HttpRequestIdentifierFeature的特性實(shí)現(xiàn)的,下面的代碼提供了該對(duì)象對(duì)應(yīng)的接口IHttpRequestIdentifierFeature和默認(rèn)實(shí)現(xiàn)類(lèi)HttpRequestIdentifierFeature的定義。
1: public abstract class HttpContext 2: { 3: //省略其他成員 4: public abstract string TraceIdentifier { get; set; } 5: } 6:? 7: public interface IHttpRequestIdentifierFeature 8: { 9: string TraceIdentifier { get; set; } 10: } 11:? 12: public class HttpRequestIdentifierFeature : IHttpRequestIdentifierFeature 13: { 14: private string _id; 15: private static long _requestId = DateTime.UtcNow.Ticks; 16: private static unsafe string GenerateRequestId(long id); 17: public string TraceIdentifier 18: { 19: get { return _id??(id= GenerateRequestId(Interlocked.Increment(ref _requestId)));} 20: set { this._id = value; } 21: } 22: }HttpRequestIdentifierFeature生成TraceIdentifier的邏輯很簡(jiǎn)單。如上面的代碼片斷所示,它具有一個(gè)靜態(tài)長(zhǎng)整型字段_requestId,其初始值為當(dāng)前時(shí)間戳。對(duì)于某個(gè)具體的HttpRequestIdentifierFeature對(duì)象來(lái)說(shuō),它的TraceIdentifier屬性的默認(rèn)值返回的是這個(gè)字段_requestId加1之后轉(zhuǎn)換的字符串。具體的轉(zhuǎn)換邏輯定義在GenerateRequestId方法中,它會(huì)采用相應(yīng)的算法 將指定的整數(shù)轉(zhuǎn)換一個(gè)長(zhǎng)度為13的字符串(比如“0HKSDQNPC0424”)。
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core真实管道详解[2]:Server是如何完成针对请求的监听、接收与响应的【上】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 漆黑一片也能万物可见 红外5G手机AGM
- 下一篇: .NET异步程序设计之任务并行库