學(xué)習(xí)ASP.NET Core,怎能不了解請(qǐng)求處理管道:中間件究竟是個(gè)什么東西?
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
ASP.NET Core管道雖然在結(jié)構(gòu)組成上顯得非常簡(jiǎn)單,但是在具體實(shí)現(xiàn)上卻涉及到太多的對(duì)象,所以我們?cè)?“通過重建Hosting系統(tǒng)理解HTTP請(qǐng)求在ASP.NET Core管道中的處理流程”(上篇、中篇、下篇) 中圍繞著一個(gè)經(jīng)過極度簡(jiǎn)化的模擬管道講述了真實(shí)管道構(gòu)建的方式以及處理HTTP請(qǐng)求的流程。在本系列 中,我們會(huì)還原構(gòu)建模擬管道時(shí)可以舍棄和改寫的部分,向讀者朋友們呈現(xiàn)一個(gè)真是的HTTP請(qǐng)求處理管道。 ASP.NET Core 的請(qǐng)求處理管道由一個(gè)服務(wù)器與一組有序排列的中間件構(gòu)成,前者僅僅完成請(qǐng)求監(jiān)聽、接收和響應(yīng)這些與底層網(wǎng)絡(luò)相關(guān)的工作,至于請(qǐng)求接收之后和響應(yīng)之前的所有工作都交給中間件來完成。ASP.NET Core的中間件通過一個(gè)類型Func<RequestDelegate, RequestDelegate>的委托對(duì)象來表示,而RequestDelegate也是一個(gè)委托,它代表一項(xiàng)請(qǐng)求處理任務(wù)。 [本文已經(jīng)同步到《ASP.NET Core框架揭秘》之中]
一、RequestDelegate服務(wù)器接受到抵達(dá)的HTTP請(qǐng)求之后會(huì)構(gòu)建一個(gè)描述當(dāng)前請(qǐng)求的原始上下文,服務(wù)器的類型決定了這個(gè)原始上下文的類型,比如在我們模擬管道默認(rèn)采用的HttpListenerServer由于采用HttpListener來監(jiān)聽、接收并響應(yīng)請(qǐng)求,所以它對(duì)應(yīng)的原始上下文是一個(gè)HttpListenerContext對(duì)象。但是對(duì)于管道的后續(xù)部分,即由注冊(cè)的中間件構(gòu)建的鏈表,它們需要采用統(tǒng)一的方式來處理請(qǐng)求,所以服務(wù)器最終會(huì)根據(jù)原始的上下文來創(chuàng)建一個(gè)抽象的HTTP上下文,后者通過抽象類HttpContext來表示。 我們不僅可以利用這個(gè)HttpContext獲取描述當(dāng)前請(qǐng)求的上下文信息,同樣可以利用它來實(shí)現(xiàn)對(duì)響應(yīng)的控制。針對(duì)當(dāng)前請(qǐng)求的任何處理操作總是在這么一個(gè)上下文中進(jìn)行,所以一項(xiàng)請(qǐng)求處理任務(wù)完全可以抽象成一個(gè)類型Func<HttpContext,Task>的委托來表示,實(shí)際上具有如下定義的RequestDelegate委托具有類似的定義。 1: public delegate Task RequestDelegate(HttpContext context); 每個(gè)中間件都承載著獨(dú)立的請(qǐng)求處理任務(wù),它本質(zhì)上也體現(xiàn)了在當(dāng)前HttpContext下針對(duì)請(qǐng)求的處理操作,那么為什么中間件不直接通過一個(gè)RequestDelegate對(duì)象來表示,而是表示為一個(gè)類型為Func<RequestDelegate, RequestDelegate>的委托對(duì)象呢?原因很簡(jiǎn)單,中間件并不孤立地存在,所有注冊(cè)的中間件最終會(huì)根據(jù)注冊(cè)的先后順序組成一個(gè)鏈表,每個(gè)中間件不僅僅需要完成各自的請(qǐng)求處理任務(wù)外,還需要驅(qū)動(dòng)鏈表中的下一個(gè)中間件。 如上圖所示,對(duì)于一個(gè)由多個(gè)Func<RequestDelegate, RequestDelegate>對(duì)象組成的中間鏈表來說,某個(gè)中間件會(huì)將后一個(gè)Func<RequestDelegate, RequestDelegate>對(duì)象的返回值作為輸入,而自身的返回值則作為前一個(gè)中間件的輸入。某個(gè)中間件執(zhí)行之后返回的RequestDelegate對(duì)象不僅僅體現(xiàn)了自身對(duì)請(qǐng)求的處理操作,而是體現(xiàn)了包含自己和后續(xù)中間件一次對(duì)請(qǐng)求的處理。那么對(duì)于第一個(gè)中間件來說,它執(zhí)行后返回的RequestDelegate對(duì)象實(shí)際上體現(xiàn)了整個(gè)應(yīng)用對(duì)請(qǐng)求的處理邏輯。 二、 HttpContext對(duì)當(dāng)前上下文的抽象解除了管道對(duì)具體服務(wù)器類型的依賴, 這使我們可以為ASP.NET Core應(yīng)用自由地選擇承載(Hosting)方式,而不是像傳統(tǒng)的ASP.NET應(yīng)用一樣只能寄宿在IIS之中。抽象HTTP上下文的目的是為了實(shí)現(xiàn)對(duì)請(qǐng)求處理流程的抽象,只有這樣我們才能將針對(duì)請(qǐng)求的某項(xiàng)操作體現(xiàn)在一個(gè)標(biāo)準(zhǔn)的中間件上,有了這個(gè)這個(gè)標(biāo)準(zhǔn)化的中間件才有所謂的請(qǐng)求處理管道。 ASP.NET Core通過具有如下所示的HttpContext類來表示這么一個(gè)抽象的HTTP上下文。對(duì)于一個(gè)HttpContext對(duì)象來說,它的核心體現(xiàn)在用于描述請(qǐng)求和響應(yīng)的Request和Response屬性之上。除此之外,我們還可以通過它獲取與當(dāng)前請(qǐng)求相關(guān)的其他上下文信息,比如用來控制用戶認(rèn)證的AuthenticationManager對(duì)象和代表當(dāng)前請(qǐng)求用戶的ClaimsPrincipal對(duì)象,以及描述當(dāng)前HTTP連接的ConnectionInfo對(duì)象和用于控制WebSocket的WebSocketManager。我們可以獲取并控制當(dāng)前會(huì)話,也可以獲取或者設(shè)置調(diào)試追蹤的ID。 1: public abstract class HttpContext 2: { 3: 4: public abstract HttpRequest Request { get; } 5: public abstract HttpResponse Response { get; } 6: 7: public abstract AuthenticationManager Authentication { get; } 8: public abstract ClaimsPrincipal User { get; set; } 9: public abstract ConnectionInfo Connection { get; } 10: public abstract WebSocketManager WebSockets { get; } 11: public abstract ISession Session { get; set; } 12: public abstract string TraceIdentifier { get; set; } 13: public abstract CancellationToken RequestAborted { get; set; } 14: public abstract IDictionary<object, object> Items { get; set; } 15: 16: public abstract IServiceProvider RequestServices { get; set; } 17: public abstract IFeatureCollection Features { get; } 18: } 當(dāng)需要中指對(duì)請(qǐng)求的處理時(shí),我們可以通過為RequestAborted屬性設(shè)置一個(gè)CancellationToken對(duì)象從而將終止通知發(fā)送給管道。如果需要對(duì)整個(gè)管道共享一些與當(dāng)前上下文相關(guān)的數(shù)據(jù),我們可以將它保存在通過Items屬性表示的字典中。我們一再提到依賴注入被廣泛地應(yīng)用ASP.NET Core管道中,HttpContext的RequestServices屬性返回的根據(jù)在應(yīng)用啟動(dòng)時(shí)注冊(cè)的服務(wù)而創(chuàng)建的ServiceProvider。只要相應(yīng)的服務(wù)被預(yù)先注冊(cè)到指定的服務(wù)接口上,我們就可能利用這個(gè)ServiceProvider根據(jù)這個(gè)接口得到對(duì)應(yīng)的服務(wù)對(duì)象。 1: public abstract class HttpRequest 2: { 3: public abstract HttpContext HttpContext { get; } 4: public abstract string Method { get; set; } 5: public abstract string Scheme { get; set; } 6: public abstract bool IsHttps { get; set; } 7: public abstract HostString Host { get; set; } 8: public abstract PathString PathBase { get; set; } 9: public abstract PathString Path { get; set; } 10: public abstract QueryString QueryString { get; set; } 11: public abstract IQueryCollection Query { get; set; } 12: public abstract string Protocol { get; set; } 13: public abstract IHeaderDictionary Headers { get; } > 14: public abstract IRequestCookieCollection Cookies { get; set; } 15: public abstract string ContentType { get; set; } 16: public abstract Stream Body { get; set; } 17: public abstract bool HasFormContentType { get; } 18: public abstract IFormCollection Form { get; set; } 19: 20: public abstract Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken); 21: } 如上所示的是抽象類HttpRequest是對(duì)HTTP請(qǐng)求的描述,它是HttpContext的只讀屬性Request的返回類型。我們可以利用這個(gè)對(duì)象獲取到描述當(dāng)前請(qǐng)求的各種相關(guān)信息,比如請(qǐng)求的協(xié)議(HTTP或者HTTPS)、HTTP方法、地址,也可以獲取代表請(qǐng)求的HTTP消息的首部和主體。 在了解了表示請(qǐng)求的抽象類HttpRequest之后,我們?cè)賮碚J(rèn)識(shí)一個(gè)與之相對(duì)的用于描述響應(yīng)HttpResponse類型。如下面的代碼片斷所示,HttpResponse依然是一個(gè)抽象類,我們可以通過定義在它之上的屬性和方法來控制對(duì)請(qǐng)求的響應(yīng)。從原則上講,我們對(duì)請(qǐng)求的所做的任意類型的響應(yīng)都可以利用它來說實(shí)現(xiàn)。當(dāng)我們通過表示當(dāng)前上下文的HttpContext對(duì)象得到表示響應(yīng)的HttpResponse之后,我們不僅僅可以將希望的內(nèi)容寫入響應(yīng)消息的主體,還可以設(shè)置響應(yīng)狀態(tài)碼以及添加相應(yīng)的首部。 1: public abstract class HttpResponse 2: { 3: public abstract HttpContext HttpContext { get; } 4: public abstract int StatusCode { get; set; } 5: public abstract IHeaderDictionary Headers { get; } 6: public abstract Stream Body { get; set; } 7: public abstract long? ContentLength { get; set; } 8: public abstract IResponseCookies Cookies { get; } 9: public abstract bool HasStarted { get; } 10: 11: public abstract void OnStarting(Func<object, Task> callback, object state); 12: public virtual void OnStarting(Func<Task> callback); 13: public abstract void OnCompleted(Func<object, Task> callback, object state); 14: public virtual void RegisterForDispose(IDisposable disposable); 15: public virtual void OnCompleted(Func<Task> callback); 16: public virtual void Redirect(string location); 17: public abstract void Redirect(string location, bool permanent); 18: } FeatureCollectionHttpContext的另一個(gè)只讀屬性Features返回一組“特性”對(duì)象。在ASP.NET Core管道式處理設(shè)計(jì)中,特性是一個(gè)非常重要的概念,特性是實(shí)現(xiàn)抽象化HttpContext的途徑。具體來說,服務(wù)器在接收到請(qǐng)求之后會(huì)創(chuàng)建一個(gè)由自身類型決定的原始的上下文,管道不僅僅利用這個(gè)原始上下文來獲取與請(qǐng)求相關(guān)的信息,它對(duì)請(qǐng)求的最終響應(yīng)實(shí)際上也是通過這個(gè)原始上下文來完成的。所以對(duì)一個(gè)HttpContext對(duì)象來說,由它描述的上下文信息不僅僅來源于這個(gè)原始的上下文,我們針對(duì)HttpContext所做的任何響應(yīng)操作最終都需要分發(fā)給這個(gè)原始上下文來完成, 否則是不會(huì)生效的。抽象的HttpContext和原始上下文之間的“雙向綁定”究竟是如何實(shí)現(xiàn)的呢? 這個(gè)所謂的“雙向綁定”即使其實(shí)很簡(jiǎn)單。當(dāng)原始上下文被創(chuàng)建出來之后,服務(wù)器會(huì)將它封裝成一系列標(biāo)準(zhǔn)的特性對(duì)象,HttpContext正是對(duì)這些特性對(duì)象的封裝。一般來說,這些特性對(duì)象所對(duì)應(yīng)的類型均實(shí)現(xiàn)了某個(gè)預(yù)定義的標(biāo)準(zhǔn)接口,接口中不僅僅定義相應(yīng)的屬性來讀寫原始上下文中描述的信息,還定義了相應(yīng)的方法來操作原始上下文。HttpContext的屬性Features返回的就是這組特性對(duì)象的集合,它的返回類型為IFeatureCollection,我們將實(shí)現(xiàn)了該接口的類型以及對(duì)應(yīng)的對(duì)象統(tǒng)稱為FeatureCollection。 1: public interface IFeatureCollection : IEnumerable<KeyValuePair<Type, object>> 2: { 3: TFeature Get<TFeature>(); 4: void Set<TFeature>(TFeature instance); 5: 6: bool IsReadOnly { get; } 7: object this[Type key] { get; set; } 8: int Revision { get; } 一個(gè)FeatureCollection對(duì)象本質(zhì)上就是一個(gè)Key和Value分別為Type和Object類型的字段,話句話說,特性對(duì)象通過對(duì)應(yīng)的接口類型注冊(cè)到HttpContext之上。我們通過調(diào)用Set方法將一個(gè)特性對(duì)象針對(duì)指定的類型(一般為特性接口)注冊(cè)到這個(gè)字典對(duì)象上,并通過Get方法根據(jù)注冊(cè)的類型獲取它。特性對(duì)象的注冊(cè)和獲取也可以利用定義的索引來完成。如果IsReadOnly屬性返回True,我們將不能注冊(cè)新的特性或者修改已經(jīng)注冊(cè)的特性。 整數(shù)類型的之都屬性Revision可以視為整個(gè)FeatureCollection對(duì)象的版本,不論是采用何種方式注冊(cè)新的特性還是修改現(xiàn)有的特性,這個(gè)屬性的值都將改變。 具有如下定義的FeatureCollection類實(shí)現(xiàn)了IFeatureCollection接口,我們默認(rèn)使用的FeatureCollection就是這么一個(gè)類型的對(duì)象。FeatureCollection具有兩個(gè)構(gòu)造函數(shù)重載,默認(rèn)無參構(gòu)造函數(shù)幫助我們創(chuàng)建一個(gè)空的特性集合,另一個(gè)構(gòu)造函數(shù)則需要指定一個(gè)FeatureCollection對(duì)象來提供默認(rèn)特性。對(duì)于采用第二個(gè)構(gòu)造函數(shù)重載創(chuàng)建的 FeatureCollection對(duì)象來說,當(dāng)我們通過指定某個(gè)特性接口類型試圖獲取對(duì)應(yīng)的特性對(duì)象時(shí),如果對(duì)應(yīng)的特性沒有注冊(cè)到當(dāng)前FeatureCollection對(duì)象上,而是注冊(cè)到提供默認(rèn)特性的FeatureCollection對(duì)象上,后者將會(huì)提供最終的特性。 1: public class FeatureCollection : IFeatureCollection 2: { 3: //其他成員 4: public FeatureCollection(); 5: public FeatureCollection(IFeatureCollection defaults); 6: } 對(duì)于FeatureCollection類型來說,它 的IsReadOnly總是返回False,所以它永遠(yuǎn)是可讀可寫的。對(duì)于調(diào)用默認(rèn)無參構(gòu)造函數(shù)創(chuàng)建的FeatureCollection對(duì)象來說,它 的Revision默認(rèn)返回零。如果我們通過指定另一個(gè)FeatureCollection對(duì)象為參數(shù)調(diào)用第二個(gè)構(gòu)造函數(shù)來創(chuàng)建一個(gè)FeatureCollection對(duì)象,前者的Revision屬性值將成為后者同名屬性的默認(rèn)值。不論我們采用何種形式(調(diào)用Set方法或者索引)添加一個(gè)新的特性或者改變了一個(gè)已經(jīng)注冊(cè)的特性,F(xiàn)eatureCollection對(duì)象的Revision屬性都將自動(dòng)遞增。上述的這些關(guān)于FeatureCollection的特性都體現(xiàn)在如下所示的代碼片段中。 1: FeatureCollection defaults = new FeatureCollection(); 2: Debug.Assert(defaults.Revision == 0); 3: 4: defaults.Set<IFoo>(new Foo()); 5: Debug.Assert(defaults.Revision == 1); 6: 7: defaults[typeof(IBar)] = new Bar(); 8: Debug.Assert(defaults.Revision == 2); 9: 10: FeatureCollection features = new FeatureCollection(defaults); 11: Debug.Assert(features.Revision == 2); 12: Debug.Assert(features.Get<IFoo>().GetType() == typeof(Foo)); 13: 14: features.Set<IBaz>(new Baz()); 15: Debug.Assert(features.Revision == 3); DefaultHttpContextASP.NET Core默認(rèn)使用的HttpContext類型為DefaultHttpContext,上面我們介紹的針對(duì)描述原始上下文“特性集合”來創(chuàng)建HttpContext的策略就體現(xiàn)在這個(gè)類型之上。DefaultHttpContext具有一個(gè)如下的構(gòu)造函數(shù),作為參數(shù)的FeatureCollection對(duì)象就是這么一個(gè)特性集合。 1: public class DefaultHttpContext : HttpContext 2: { 3: public DefaultHttpContext(IFeatureCollection features); 4: } 不論是組成管道的中間件還是建立在管道上的應(yīng)用,在默認(rèn)的情況下都是利用這個(gè)DefaultHttpContext對(duì)象來獲取當(dāng)前請(qǐng)求的相關(guān)信息,并利用這個(gè)對(duì)象來控制最終發(fā)送的響應(yīng)。但是DefaultHttpContext對(duì)象這個(gè)這個(gè)過程中僅僅是一個(gè)“代理”,針對(duì)它的調(diào)用(屬性或者方法)最終都需要轉(zhuǎn)發(fā)給由具體服務(wù)器創(chuàng)建的那個(gè)原始上下文,在構(gòu)造函數(shù)中指定的這個(gè)FeatureCollection對(duì)象所代表的特性集合成為了這兩個(gè)上下文對(duì)象進(jìn)行溝通的唯一渠道。對(duì)于定義在DefaultHttpContext中的所有屬性,它們幾乎都具有一個(gè)對(duì)應(yīng)的特性,這些特性都對(duì)應(yīng)著一個(gè)接口。表1列出了部分特性接口以及DefaultHttpContext對(duì)應(yīng)的屬性。 表1 描述原始HTTP上下文的特性接口
對(duì)于上面列出的眾多特性接口,我們?cè)诤罄m(xù)相關(guān)章節(jié)中都會(huì)涉及到,目前來說我們只需要了解一下兩個(gè)最重要的特性接口,即表示請(qǐng)求和響應(yīng)的IHttpRequestFeature和IHttpResponseFeature。從下面給出的代碼片斷我們不難看出,這兩個(gè)接口的定義分別與抽象類HttpRequest和HttpResponse具有一致的定義。對(duì)于DefaultHttpContext類型來說,它的Request和Response屬性分別返回的是一個(gè)DefaultHttpRequest和DefaultHttpResponse對(duì)象。DefaultHttpRequest和DefaultHttpResponse分別繼承自HttpRequest和HttpResponse,它們分別利用這個(gè)兩個(gè)特性實(shí)現(xiàn)了從基類繼承下來的所有抽象成員。 1: public interface IHttpRequestFeature 2: { 3: Stream Body { get; set; } 4: IHeaderDictionary Headers { get; set; } 5: string Method { get; set; } 6: string Path { get; set; } 7: string PathBase { get; set; } 8: string Protocol { get; set; } 9: string QueryString { get; set; } 10: string Scheme { get; set; } 11: } 12: 13: public interface IHttpResponseFeature 14: { 15: Stream Body { get; set; } 16: bool HasStarted { get; } 17: IHeaderDictionary Headers { get; set; } 18: string ReasonPhrase { get; set; } 19: int StatusCode { get; set; } 20: 21: void OnCompleted(Func<object, Task> callback, object state); 22: void OnStarting(Func<object, Task> callback, object state); 23: } 對(duì)于實(shí)現(xiàn)請(qǐng)求監(jiān)聽、接收和響應(yīng)的服務(wù)器來說,它們都需要通過實(shí)現(xiàn)上面這些特性接口來定義針對(duì)性的特性類。如下圖所示,當(dāng)成功接收到請(qǐng)求之后,服務(wù)器會(huì)創(chuàng)建相應(yīng)的特性并將它們組合成一個(gè)FeatureCollection對(duì)象,最后創(chuàng)建出一個(gè)DefaultHttpContext對(duì)象,我們注冊(cè)的所有中間件針對(duì)這個(gè)DefaultHttpContext完成各自的請(qǐng)求處理工作。 HttpContextFactory在服務(wù)器接收到抵達(dá)的請(qǐng)求時(shí),它并不會(huì)直接利用原始的上下文去創(chuàng)建HttpContext對(duì)象,HttpContext在管道中的創(chuàng)建是間接地通過HttpContextFactory來完成的。 HttpContextFactory是對(duì)所有實(shí)現(xiàn)了IHttpContextFactory接口的所有類型及其對(duì)象的統(tǒng)稱。如下面的代碼片段所示,IHttpContextFactory接口除了定義創(chuàng)建HttpContext對(duì)象的Create方法之外,還定義了另一個(gè)方法Dispose來釋放指定的HttpContext對(duì)象。HttpContextFactory類是該接口的默認(rèn)實(shí)現(xiàn)者,由它的Create方法創(chuàng)建并返回的自然是一個(gè)DefaultHttpContext對(duì)象。 1: public interface IHttpContextFactory 2: { 3: HttpContext Create(IFeatureCollection featureCollection); 4: void Dispose(HttpContext httpContext); 5: } 6: 7: public class HttpContextFactory : IHttpContextFactory 8: { 9: //省略其他成員 10: public HttpContext Create(IFeatureCollection featureCollection); 11: public void Dispose(HttpContext httpContext); 12: } 綜上所述,組成管道的所有中間件在一個(gè)標(biāo)準(zhǔn)化的上下文中完整對(duì)請(qǐng)求的處理,這個(gè)上下文通過抽象類HttpContext表示,ASP.NET Core默認(rèn)使用的是它的子類DefaultHttpContext。一個(gè)DefaultHttpContext對(duì)象是根據(jù)描述原始上下文的特性集合,每個(gè)特性對(duì)應(yīng)的類型都實(shí)現(xiàn)了標(biāo)準(zhǔn)的接口,接口IHttpRequestFeature和IHttpResponseFeature分別代表針對(duì)請(qǐng)求和響應(yīng)的特性。HttpContext默認(rèn)情況下是通過注冊(cè)的工廠創(chuàng)建的,該工廠通過接口IHttpContextFactory表示,默認(rèn)使用的HttpContext工廠類型為HttpContextFactory,它也是DefaultHttpContext對(duì)象的創(chuàng)建者。 三、ApplicationBuilder以類型為Func<RequestDelegate, RequestDelegate>的委托對(duì)象表示的中間件需要在啟動(dòng)的時(shí)候注冊(cè)到應(yīng)用程序上。所有注冊(cè)的中間件最終會(huì)轉(zhuǎn)換成一個(gè)RequestDelegate類型的委托對(duì)象,它們按照注冊(cè)順序?qū)φ?qǐng)求的處理流程最終體現(xiàn)在對(duì)這個(gè)委托對(duì)象的執(zhí)行。不論是最終將中間件轉(zhuǎn)換成RequestDelegate對(duì)象,還是最初對(duì)它們的注冊(cè),都是通過一個(gè)名為ApplicationBuilder的對(duì)象來完成的。 ApplicationBuilder是我們對(duì)所有實(shí)現(xiàn)了IApplicationBuilder接口的所有類型以及對(duì)應(yīng)對(duì)象的統(tǒng)稱。接口IApplicationBuilder定義如下,中間件的注冊(cè)和RequestDelegate對(duì)象的生成分別通過調(diào)用它的Use和Build方法來完成。除了這兩個(gè)核心方法,IApplicationBuilder接口還定義了三個(gè)屬性,其中ApplicationServices返回根據(jù)最初服務(wù)注冊(cè)生成的ServiceProvider對(duì)象,而ServerFeatures屬性返回的FeatureCollection對(duì)象是描述Server的特性集合。字典類型的Properties屬性用戶存儲(chǔ)任意自定義的屬性,而New方法會(huì)根據(jù)自己“克隆”出一個(gè)新的ApplicationBuilder對(duì)象,這兩個(gè)ApplicationBuilder對(duì)象應(yīng)用具有相同的屬性集合。 1: public interface IApplicationBuilder 2: { 3: IServiceProvider ApplicationServices { get; set; } 4: IFeatureCollection ServerFeatures { get; } 5: IDictionary<string, object> Properties { get; } 6: 7: RequestDelegate Build(); 8: IApplicationBuilder New(); 9: IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); 10: } 具有如下定義的ApplicationBuilder類型是對(duì)IApplicationBuilder接口的默認(rèn)實(shí)現(xiàn)。ApplicationBuilder類型利用一個(gè)List<Func<RequestDelegate, RequestDelegate>>對(duì)象來保存注冊(cè)的中間件,所以Use方法只需要將指定的中間件添加到這個(gè)列表中即可,而Build方法只需要逆序調(diào)用這些注冊(cè)的中間件對(duì)應(yīng)的Func<RequestDelegate, RequestDelegate>對(duì)象就能得到我們需要的RequestDelegate對(duì)象。值得一提的是,Build方法實(shí)際上在中間件鏈條的尾部添加了一個(gè)額外的中間件,該中間件會(huì)負(fù)責(zé)將響應(yīng)狀態(tài)碼設(shè)置為404,如果我們沒有注冊(cè)一個(gè)中間件對(duì)請(qǐng)求作最終的響應(yīng)(這樣的中間件將不會(huì)試圖調(diào)用后續(xù)中間件),整個(gè)管道比較回復(fù)一個(gè)狀態(tài)碼為404的響應(yīng)。 1: public class ApplicationBuilder : IApplicationBuilder 2: { 3: private readonly IList<Func<RequestDelegate, RequestDelegate>> middlewares = new List<Func<RequestDelegate, RequestDelegate>>(); 4: 5: public IDictionary<string, object> Properties { get; } 6: 7: public IServiceProvider ApplicationServices 8: { 9: get { return GetProperty<IServiceProvider>("application.Services"); } 10: set { SetProperty<IServiceProvider>("application.Services", value); } 11: } 12: 13: public IFeatureCollection ServerFeatures 14: { 15: get { return GetProperty<IFeatureCollection>("server.Features"); } 16: } 17: 18: 19: public ApplicationBuilder(IServiceProvider serviceProvider) 20: { 21: this.Properties = new Dictionary<string, object>(); 22: ApplicationServices = serviceProvider; 23: } 24: 25: public ApplicationBuilder(IServiceProvider serviceProvider, object server) 26: : this(serviceProvider) 27: { 28: SetProperty("server.Features", server); 29: } 30: 31: public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) 32: { 33: middlewares.Add(middleware); 34: return this; 35: } 36: 37: public IApplicationBuilder New() 38: { 39: return new ApplicationBuilder(this); 40: } 41: 42: public RequestDelegate Build() 43: { 44: RequestDelegate app = context => 45: { 46: context.Response.StatusCode = 404; 47: return Task.FromResult(0); 48: }; 49: foreach (var component in middlewares.Reverse()) 50: { 51: app = component(app); 52: } 53: return app; 54: } 55: 56: private ApplicationBuilder(ApplicationBuilder builder) 57: { 58: this.Properties = builder.Properties; 59: } 60: 61: private T GetProperty<T>(string key) 62: { 63: object value; 64: return Properties.TryGetValue(key, out value) ? (T)value : default(T); 65: } 66: 67: private void SetProperty<T>(string key, T value) 68: { 69: this.Properties[key] = value; 70: } 71: } 通過上面的代碼片段我們不難看到,不論是通過ApplicationServices屬性返回的ServiceProvider對(duì)象,還是通過ServerFeatures屬性返回的用于描述Server特性的FeatureCollection對(duì)象,它們實(shí)際上都保存在通過Properties屬性返回字典對(duì)象上。ApplicationBuilder具有兩個(gè)公共構(gòu)造函數(shù)重載,它們具有一個(gè)公共的參數(shù),即用來初始化ApplicationServices屬性的參數(shù)serviceProvider。 一個(gè)構(gòu)造函數(shù)具有一個(gè)名為server的參數(shù),但是這個(gè)參數(shù)并不是表示管道使用的服務(wù)器,而是承載服務(wù)器相關(guān)特性的FeatureCollection對(duì)象,不過這個(gè)參數(shù)類型被定義成Object,而不是IFeatureCollection接口。New方法直接調(diào)用私有構(gòu)造函數(shù)創(chuàng)建出一個(gè)新的ApplicationBuilder對(duì)象,這個(gè)對(duì)象與自己的Properties屬性共享同一個(gè)字典對(duì)象,由于ApplicationServices和ServerFeatures屬性的返回值也存放在這個(gè)字典對(duì)象上,所以New方法得到的ApplicationBuilder對(duì)象與自身對(duì)象其實(shí)是完全等效的。 ApplicationBuilderFactoryApplicationBuilderFactory是ASP.NET Core它用來創(chuàng)建ApplicationBuilder的工廠,它是對(duì)所有實(shí)現(xiàn)了接口IApplicationBuilderFactory的所有類型以及對(duì)應(yīng)對(duì)象的統(tǒng)稱。如下面的代碼片段所示,該接口定義了唯一個(gè)方法CreateBuilder根據(jù)提供的FeatureCollection對(duì)象創(chuàng)建出對(duì)應(yīng)的ApplicationBuilder對(duì)象,這個(gè)FeatureCollection對(duì)象正是承載與服務(wù)器相關(guān)特性的集合。ApplicationBuilderFactory類型是該接口的默認(rèn)實(shí)現(xiàn)者,當(dāng)CreateBuilder方法被調(diào)用的時(shí)候,它會(huì)直接將構(gòu)造時(shí)提供ServiceProvider對(duì)象和serverFeatures參數(shù)表示的FeatureCollection對(duì)象來創(chuàng)建返回的ApplicationBuilder對(duì)象。 1: public interface IApplicationBuilderFactory 2: { 3: IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures); 4: } 5: 6: public class ApplicationBuilderFactory : IApplicationBuilderFactory 7: { 8: private readonly IServiceProvider _serviceProvider; 9: 10: public ApplicationBuilderFactory(IServiceProvider serviceProvider) 11: { 12: this._serviceProvider = serviceProvider; 13: } 14: 15: public IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures) 16: { 17: return new ApplicationBuilder(_serviceProvider, serverFeatures); 18: } 19: } 中間件類型雖然中間件最終體現(xiàn)為一個(gè)類型為 Func<RequestDelegate, RequestDelegate>的委托對(duì)象,但是我們?cè)诖蟛糠智闆r下都會(huì)將中間件定義成一個(gè)單獨(dú)的類型。雖然這樣的中間件類型不要求實(shí)現(xiàn)某個(gè)預(yù)定義的接口或者繼承某個(gè)預(yù)定義的基類,但是卻要遵守幾個(gè)必要的約定。接下來我們直接如下這個(gè)ContentMiddleware類說說一個(gè)合法的中間件類型應(yīng)該如何定義。 1: public class ContentMiddleare 2: { 3: public RequestDelegate _next; 4: public byte[] _content; 5: public string _contentType; 6: 7: public ContentMiddleare(RequestDelegate next, byte[] content, string contentType) 8: { 9: _next = next; 10: _content = content; 11: _contentType = contentType; 12: } 13: 14: public async Task Invoke(HttpContext context, ILoggerFactory loggerFactory) 15: { 16: loggerFactory.CreateLogger<ContentMiddleare>().LogInformation($"Write content ({_contentType})"); 17: context.Response.ContentType = _contentType; 18: await context.Response.Body.WriteAsync(_content,0, _content.Length); 19: } 20: } 如上所示的這個(gè)中間件(ContentMiddleware)可以幫助我們將任何類型的內(nèi)容響應(yīng)給客戶端,它的兩個(gè)字段_content和_contentType分別代表響應(yīng)內(nèi)容和媒體類型(內(nèi)容類型或者M(jìn)IME類型),它體現(xiàn)了一個(gè)典型中間件類型的定義規(guī)則或者約定:
中間件類型的注冊(cè)中間件類型的注冊(cè)可以通過調(diào)用 IApplicationBuilder接口的擴(kuò)展方法UseMiddleware 和UseMiddleware 1: public static class UseMiddlewareExtensions 2: { 3: public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args); 4: public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args); 5: } 對(duì)于上面定義的這個(gè) ContentMiddleare類型,我們按照如下的方式對(duì)它進(jìn)行了注冊(cè)。當(dāng)這個(gè)中間件執(zhí)行的時(shí)候,它會(huì)響應(yīng)客戶端一張PNG圖片。如果客戶端是能夠支持圖片呈現(xiàn)的瀏覽器,這張圖片會(huì)直接顯示在瀏覽器上。 1: new WebHostBuilder() 2: .Configure(app=>app.UseMiddleware<ContentMiddleare>(File.ReadAllBytes("girl.png"),"image/png")) 3: ... 雖然中間件可以定義成任何一個(gè)遵循約定的類型,但是中間件自身在ASP.NET Core框架中總是體現(xiàn)為一個(gè)類型為Func<RequestDelegate, RequestDelegate>的委托對(duì)象,所以上述的這個(gè)UseMiddleware方法在執(zhí)行的時(shí)候需要在內(nèi)部根據(jù)注冊(cè)的中間件類型和指定的參數(shù)列表創(chuàng)建這么一個(gè)Func<RequestDelegate, RequestDelegate>對(duì)象。其中的邏輯并不復(fù)雜,它之需要將中間件對(duì)象的創(chuàng)建和針對(duì)Invoke方法的調(diào)用實(shí)現(xiàn)在返回的委托對(duì)象中就可以了。值得一提的是,針對(duì)Invoke方法的調(diào)用并沒有直接通過反射的方式來實(shí)現(xiàn),而是采用表達(dá)式,后者具有更好的性能。在如下所示的代碼片段中,我采用最精簡(jiǎn)的代碼模擬了UseMiddleware方法的實(shí)現(xiàn)。 1: public static class WebHostBuilderExtensions 2: { 3: private static MethodInfo GetServiceMethod = typeof(WebHostBuilderExtensions).GetMethod("GetService", BindingFlags.Static | BindingFlags.NonPublic); 4: 5: public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args) 6: { 7: return UseMiddleware2(app, typeof(TMiddleware), args); 8: } 9: 10: public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middlewareType, params object[] args) 11: { 12: return app.Use(next => 13: { 14: return context => { 15: var factory = Compile<object>(middlewareType.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public)); 16: object middleware = CreateMiddleware(app, middlewareType, next, args); 17: return factory(middleware, context, app.ApplicationServices); 18: }; 19: }); 20: } 21: 22: private static object CreateMiddleware(IApplicationBuilder app, Type middlewareType, RequestDelegate next, params object[] args) 23: { 24: object[] arguments = new object[args.Length + 1]; 25: arguments[0] = next; 26: args.CopyTo(arguments, 1); 27: return ActivatorUtilities.CreateInstance(app.ApplicationServices, middlewareType, arguments); 28: } 29: 30: //將對(duì)Invoke方法的調(diào)用轉(zhuǎn)換成一個(gè)Func<TMiddleware, HttpContext, IServiceProvider, Task>對(duì)象 31: private static Func<TMiddleware, HttpContext, IServiceProvider, Task> Compile<TMiddleware>(MethodInfo invokeMethod) 32: { 33: ParameterExpression middleware = Expression.Parameter(typeof(TMiddleware), "middleware"); 34: ParameterExpression httpContext = Expression.Parameter(typeof(HttpContext), "httpContext"); 35: ParameterExpression serviceProvider = Expression.Parameter(typeof(IServiceProvider), "serviceProvider"); 36: 37: var arguments = from parameter in invokeMethod.GetParameters() 38: select GetArgument(httpContext, serviceProvider, parameter.ParameterType); 39: 40: Expression instance = middleware; 41: if (invokeMethod.DeclaringType != typeof(TMiddleware)) 42: { 43: instance = Expression.Convert(instance, invokeMethod.DeclaringType); 44: } 45: 46: Expression invoke = Expression.Call(instance, invokeMethod, arguments.ToArray()); 47: return Expression.Lambda<Func<TMiddleware, HttpContext, IServiceProvider, Task>>(invoke, middleware, httpContext, serviceProvider).Compile(); 48: } 49: 50: //生成調(diào)用Invoke方法的參數(shù)表達(dá)式 51: private static Expression GetArgument(Expression httpContext, Expression serviceProvider, Type parameterType) 52: { 53: if (parameterType == typeof(HttpContext)) 54: { 55: return httpContext; 56: } 57: Expression serviceType = Expression.Constant(parameterType, typeof(Type)); 58: Expression callGetService = Expression.Call(GetServiceMethod, serviceProvider, serviceType); 59: return Expression.Convert(callGetService, parameterType); 60: } 61: 62: private static object GetService(IServiceProvider serviceProvider, Type serviceType) 63: { 64: return serviceProvider.GetService(serviceType); 65: } 66: } ?轉(zhuǎn)自https://www.cnblogs.com/artech/p/asp-net-core-real-pipeline-01.html 該文章在 2025/1/16 10:49:25 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |