老A說的一句話讓我很受啟發,想要深入瞭解框架,你要把精力聚焦在架構設計的層面來思考問題。而透徹瞭解底層原理,最好的笨辦法就是根據原理對框架核心進行重建或者說再造。看起來沒有捷徑,也是最快的捷徑。
相信很多讀者已經看過老A寫的這篇文章《200行程式碼,7個物件——讓你瞭解ASP.NET Core框架的本質》,這是一篇模仿和重建的典範。重建說白了就是模仿,模仿有一個前置條件就是你對底層原理要爛熟於心。否則畫虎難畫骨,原本要畫虎,最後出來的是隻貓。
要理解原理就要去閱讀原始碼,就像新人學開車,如何使用尚且磕磕碰碰,更何況讓你去瞭解汽車的構造和引擎。
所以老A是引路人,我像個門外漢一樣對前輩的文章解讀不下5遍。我有幾個疑問,1.為什麼是7個物件?2.這些物件如何分類,如何排序?3.這些物件發明的那個“無”是什麼?
在我深入學習和解讀的時候,我越加感覺到老A的這篇文章很值得去深入解讀,所謂知其然,知其所以然,這樣在編碼過程才會遊刃有餘,以下開始我個人的解讀。
|
public class Program
{
public static void Main()
=> new WebHostBuilder()
.UseKestrel()
.Configure(app => app.Use(context => context.Response.WriteAsync( "Hello World!" )))
.Build()
.Run();
}
|
以上是原文的程式碼,我們可以看到WebHostBuilder、Server(即Kestrel)、ApplicationBuilder(即app)三大重要的物件,如下圖所示:
WebHostBuilder這個父親生出WebHost這個孩子,WebHost又生成整個ASP.NET Core最核心的內容,即由Server和中介軟體(Middleware)構成的管道Pipeline。我們看下Pipeline的放大圖:
繼續把Pipeline拆開,有個很重要的ApplicationBuilder物件,裡麵包含Middleware、RequestDelegate。至於HttpContext是獨立共享的物件,貫穿在整個管道中間,至此7大物件全部出場完畢。
Configure是個什麼玩意?看下程式碼:
|
public IWebHostBuilder Configure(Action configure)
{
_configures.Add(configure);
return this ;
}
|
我們看到他是一個接受IApplicationBuilder的委託!繼續刨根問底,IApplicationBuilder是什麼玩意?看下原始碼:
|
public interface IApplicationBuilder
{
IApplicationBuilder Use(Func middleware);
RequestDelegate Build();
}
|
他是一個註冊中介軟體和生成Application的容器,那麼Application是什麼呢?原始碼沒有這個物件,但是看程式碼(如下所示)我們可以知道他是真正的委託執行者(Handler),執行是一個動作可以理解為app,我猜想這是取名為ApplicationBuilder的原因。
|
public RequestDelegate Build()
{
_middlewares.Reverse();
return httpContext =>
{
RequestDelegate next = _ => { _.Response.StatusCode = 404; return Task.CompletedTask; };
foreach ( var middleware in _middlewares)
{
next = middleware(next);
}
return next(httpContext);
};
}
|
更詳細的過程可以參考下麵這張圖(圖片來源),
WebHostBuilder開始Build的那一刻開始,WebHost被構造,Server被指定,Middlewares被指定,等WebHost真正啟動的時候,Server開始監聽,收到請求後,Middleware開始執行。
到此,一個完整的ASP.NET Core的流程就簡單的走完了。接下來,我們跟著老A一個一個物件的詳細介紹。
1.HttpContext
這個物件應該是最容易理解的,也是我們在程式設計時候遇到的最多的,最重要的(沒有之一)物件。請看這個物件的簡要程式碼:
|
public class HttpContext
{
public HttpRequest Request { get ; }
public HttpResponse Response { get ; }
}
public class HttpRequest
{
public Uri Url { get ; }
public NameValueCollection Headers { get ; }
public Stream Body { get ; }
}
public class HttpResponse
{
public NameValueCollection Headers { get ; }
public Stream Body { get ; }
public int StatusCode { get ; set ;}
}
|
我們知道一個Http事務包括最核心的Request(輸入)和Response(輸出),所以HttpContext包含這兩個核心的東西。
老A建議大家從管道的角度來理解該物件的作用,管道和HTTP請求流程一脈相承。在Server接收到請求後,HttpContext被建立。
在伺服器和中介軟體,中介軟體之間透過什麼來傳遞資訊?就是共享背景關係,這個背景關係就是HttpContext。可以說HttpContext是根據HTTP請求原理包裹的在管道之間的共享的一個背景關係物件。
為什麼這裡要把HttpContext放在第一個來介紹,因為這是一個最基礎的物件。這7大物件的講解順序,我感覺是從底層基礎開始講起,再層層往上,最後到WebHostBuilder。
2.RequestDelegate
這個委託太重要了,和HttpContext一樣,老A建議大家從管道的角度來理解這個委託。我們再複習一下管道的含義,如圖所示:
這裡的管道:Pipeline = Server + Middlewares
還能更簡單一點嗎?可以的:如下圖所示
這裡的管道:Pipeline =Server + HttpHandler。
多個Middlewares構成一個HttpHandler物件,這是整個管道的核心,那麼應該如何用程式碼來表示呢?
老A講到:“既然針對當前請求的所有輸入和輸出都透過HttpContext來表示,那麼HttpHandler就可以表示成一個Action物件”。
但是由於ASP.NET Core推崇非同步程式設計,所以你應該想得到Task物件,那麼HttpHandler自然就可以表示為一個Func物件。由於這個委託物件實在太重要了,所以我們將它定義成一個獨立的型別。下圖展示的就是整個RequestDelegate的設計思路
1
|
public delegate Task RequestDelegate(HttpContext context);
|
這就是委託的由來!
委託是架構設計的底層技術,非常常見。因為委託可以承載約定的函式,遵循開閉原則,能很好的把擴充套件對外進行開放,保證了底層架構的穩定性。
3.Middleware
這個物件比較費解。根據原始碼我們知道Middleware也是一個委託物件(程式碼如下所示),中介軟體其實就是一個Func<RequestDelegate, RequestDelegate>物件:
1
|
private readonly List> _middlewares = new List>();
|
該物件的輸入和輸入都是RequestDelegate,為什麼要這麼設計呢?我們想一下,當前中介軟體處理完成後需要將請求分發給後續中介軟體進行處理,他如何讓後續的中介軟體參與當前的請求呢?所以他必須要拿到代表後續中介軟體管道構成的那個Handler。
如下圖所示,也就是說,後續三個中介軟體構成的管道就是一個輸入,執行完畢後,當前中介軟體也將被“融入”這個管道(此時該新管道就會由四個中介軟體構成的一個委託鏈),然後再輸出給你由所有的中介軟體構成的新管道。如下圖所示:
4.ApplicationBuilder
這又是一個builder,可見builder樣式在ASP.NET Core有非常廣泛的應用。但是該Builder構建的不是Application,到構建什麼內容呢?從下麵程式碼宣告我們可以看到他有兩個功能。
從Use的使用來看,第一個功能是註冊器,他把一個個中介軟體串聯成一個管道。
|
public interface IApplicationBuilder
{
IApplicationBuilder Use(Func middleware);
RequestDelegate Build();
}
|
第二個功能是Build,如下所示:
|
public class ApplicationBuilder : IApplicationBuilder
{
private readonly List> _middlewares = new List>();
public RequestDelegate Build()
{
_middlewares.Reverse();
return httpContext =>
{
RequestDelegate next = _ => { _.Response.StatusCode = 404; return Task.CompletedTask; };
foreach ( var middleware in _middlewares)
{
next = middleware(next);
}
return next(httpContext);
};
}
public IApplicationBuilder Use(Func middleware)
{
_middlewares.Add(middleware);
return this ;
}
}
|
Build真正做的事情是迴圈組裝中介軟體,最後把組裝好的委託鏈進行傳回。從_middlewares.Reverse();我們又可以知道,對於委託鏈來說,中介軟體的註冊順序和執行順序是相反的,這裡需要進行反轉,然後才能保證先註冊的中介軟體先執行。
5.Server
Server物件相對比較簡單,我們看下他的介面定義:
|
public interface IServer
{
Task StartAsync(RequestDelegate handler);
}
|
我們可以看到Server有個啟動函式StartAsync,StartAsync內部封裝了RequestDelegate中介軟體,同時內部也會new一個HttpContext(features),這樣Server、RequestDelegate、HttpContext三者就全部聚齊了。
|
public class HttpListenerServer : IServer
{
private readonly HttpListener _httpListener;
private readonly string [] _urls;
public HttpListenerServer( params string [] urls)
{
_httpListener = new HttpListener();
_urls = urls.Any()?urls: new string [] { "http://localhost:5000/" };
}
public async Task StartAsync(RequestDelegate handler)
{
Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url));
_httpListener.Start();
Console.WriteLine( "Server started and is listening on: {0}" , string .Join( ';' , _urls));
while ( true )
{
var listenerContext = await _httpListener.GetContextAsync();
Console.WriteLine( "{0} {1} HTTP/{2}" ,
listenerContext.Request.HttpMethod,
listenerContext.Request.RawUrl,
listenerContext.Request.ProtocolVersion);
var feature = new HttpListenerFeature(listenerContext);
var features = new FeatureCollection()
.Set(feature)
.Set(feature);
var httpContext = new HttpContext(features);
Console.WriteLine( "[Info]: Server process one HTTP request start." );
await handler(httpContext);
Console.WriteLine( "[Info]: Server process one HTTP request end." );
listenerContext.Response.Close();
}
}
}
public static partial class Extensions
{
public static IWebHostBuilder UseHttpListener( this IWebHostBuilder builder, params string [] urls)
=> builder.UseServer( new HttpListenerServer(urls));
|
透過以上程式碼分析,我們可以畫個圖做總結:
由於ASP.NET Core可以支援不同的WebServer,比如Kestrel和IIS,不同的WebServer傳回的HttpContext各不相同,所以這裡又增加了一個中間層進行適配。這個中間層是什麼呢?如下圖所示,就是IRequestFeature和IResponseFeature。這一層是典型的配接器樣式。
這裡重點講解的7大物件,這個配接器樣式的實現細節暫且略過。
6.WebHost
|
public interface IWebHost
{
Task StartAsync();
}
|
根據這段定義,我們只能知道簡單知道WebHost只要是用來啟動什麼物件用的,具體什麼物件似乎都可以。直到我們看了實現,如下程式碼所示:
|
public class WebHost : IWebHost
{
private readonly IServer _server;
private readonly RequestDelegate _handler;
public WebHost(IServer server, RequestDelegate handler)
{
_server = server;
_handler = handler;
}
public Task StartAsync() => _server.StartAsync(_handler);
}
|
透過StartAsync,我們知道WebHost是用來啟動管道的中介軟體的,管道是在作為應用宿主的WebHost物件啟動的時候被構建出來的。
而WebHost是如何被建立的呢?接下來就要講他的父親WebHostBuilder
7.WebHostBuilder
|
public class WebHostBuilder : IWebHostBuilder
{
private IServer _server;
private readonly List> _configures = new List>();
public IWebHostBuilder Configure(Action configure)
{
_configures.Add(configure);
return this ;
}
public IWebHostBuilder UseServer(IServer server)
{
_server = server;
return this ;
}
public IWebHost Build()
{
var builder = new ApplicationBuilder();
foreach ( var configure in _configures)
{
configure(builder);
}
return new WebHost(_server, builder.Build());
}
}
|
我們看到該物件有個Build方法,內部傳回一個WebHost物件,這就是父親的職責,負責生娃,他的娃就是WebHost。生出來的時候,給孩子一個ApplicationBuilder作為食物。而這個食物其實是包裹起來的,展開來看就是一個個RequestDelegate委託鏈。
|
public interface IWebHostBuilder
{
IWebHostBuilder UseServer(IServer server);
IWebHostBuilder Configure(Action configure);
IWebHost Build();
}
|
父親除了建立WebHost之外,他還提供了註冊伺服器的UseServer方法和用來註冊中介軟體的Configure方法。說到Configure方法,我們一定還記得ApplicationBuilder方法的Use也是一個註冊器。這兩個註冊器有何不同呢?我們對比一下程式碼:
|
public IWebHostBuilder Configure(Action configure)
{
_configures.Add(configure);
return this ;
}
|
|
public IApplicationBuilder Use(Func middleware)
{
_middlewares.Add(middleware);
return this ;
}
|
其中Use只是增加一個中介軟體,Configure輸入的是中介軟體構成的委託鏈。我們看下入口函式的程式碼就知道了:
|
public static async Task Main()
{
await new WebHostBuilder()
.UseHttpListener()
.Configure(app => app
.Use(FooMiddleware)
.Use(BarMiddleware)
.Use(BazMiddleware))
.Build()
.StartAsync();
}
|
參加文章:
- 200行程式碼,7個物件——讓你瞭解ASP.NET Core框架的本質
朋友會在“發現-看一看”看到你“在看”的內容