解讀大內老A的《.NET Core框架本質》

老A說的一句話讓我很受啓發,想要深刻了解框架,你要把精力聚焦在 架構設計的層面來思考問題。而透徹瞭解底層原理,最好的笨辦法就是根據原理對框架核心進行 重建或者說 再造。看起來沒有捷徑,也是最快的捷徑。

題外話

  相信不少讀者已經看過老A寫的這篇文章《200行代碼,7個對象——讓你瞭解ASP.NET Core框架的本質》,這是一篇模仿和重建的典範。重建說白了就是模仿,模仿有一個前置條件就是你對底層原理要爛熟於心。不然畫虎難畫骨,本來要畫虎,最後出來的是隻貓。html

要理解原理就要去閱讀源碼,就像新人學開車,如何使用尚且磕磕碰碰,更況且讓你去了解汽車的構造和引擎。編程

  因此老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)三大重要的對象,以下圖所示:app

  WebHostBuilder這個父親生出WebHost這個孩子,WebHost又生成整個ASP.NET Core最核心的內容,即由Server和中間件(Middleware)構成的管道Pipeline。咱們看下Pipeline的放大圖:框架

  繼續把Pipeline拆開,有個很重要的ApplicationBuilder對象,裏面包含MiddlewareRequestDelegate。至於HttpContext是獨立共享的對象,貫穿在整個管道中間,至此7大對象所有出場完畢。異步

Configure是個什麼玩意?看下代碼:async

public IWebHostBuilder Configure(Action<IApplicationBuilder> configure)
{
    _configures.Add(configure);
    return this;
}

  咱們看到他是一個接受IApplicationBuilder的委託!繼續刨根問底,IApplicationBuilder是什麼玩意?看下源碼:ide

public interface IApplicationBuilder
{
    IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> 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一個一個對象的詳細介紹。

7個對象解讀

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<HttpContext>對象」。

  可是因爲ASP.NET Core推崇異步編程,因此你應該想獲得Task對象,那麼HttpHandler天然就能夠表示爲一個Func<HttpContext,Task>對象。因爲這個委託對象實在過重要了,因此咱們將它定義成一個獨立的類型。下圖展現的就是整個RequestDelegate的設計思路

public delegate Task RequestDelegate(HttpContext context);

  這就是委託的由來!

  • 爲何是委託,而不是別的函數?

  委託是架構設計的底層技術,很是常見。由於委託能夠承載約定的函數,遵循開閉原則,能很好的把擴展對外進行開放,保證了底層架構的穩定性。

3.Middleware

  這個對象比較費解。根據源碼咱們知道Middleware也是一個委託對象(代碼以下所示),中間件其實就是一個Func<RequestDelegate, RequestDelegate>對象:

private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>();

  該對象的輸入和輸入都是RequestDelegate,爲何要這麼設計呢?咱們想一下,當前中間件處理完成後須要將請求分發給後續中間件進行處理,他如何讓後續的中間件參與當前的請求呢?因此他必需要拿到表明後續中間件管道構成的那個Handler。

以下圖所示,也就是說,後續三個中間件構成的管道就是一個輸入,執行完畢後,當前中間件也將被「融入」這個管道(此時該新管道就會由四個中間件構成的一個委託鏈),而後再輸出給你由全部的中間件構成的新管道。以下圖所示:

4.ApplicationBuilder

  這又是一個builder,可見builder模式在ASP.NET Core有很是普遍的應用。可是該Builder構建的不是Application,到構建什麼內容呢?從下面代碼聲明咱們能夠看到他有兩個功能。

  從Use的使用來看,第一個功能是註冊器,他把一個個中間件串聯成一個管道。

public interface  IApplicationBuilder
{
    IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
    RequestDelegate Build();
}

  第二個功能是Build,以下所示:

public class ApplicationBuilder : IApplicationBuilder
{
    private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>();
    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<RequestDelegate, RequestDelegate> 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();
        //綁定默認監聽地址(默認端口爲5000)
        _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)
        {
        //該方法將阻塞進程(這裏使用了await),等待傳入的請求,直到收到請求
            var listenerContext = await _httpListener.GetContextAsync(); 
        //打印狀態行: 請求方法, URL, 協議版本
         Console.WriteLine("{0} {1} HTTP/{2}", 
                    listenerContext.Request.HttpMethod, 
                    listenerContext.Request.RawUrl, 
                    listenerContext.Request.ProtocolVersion);
                    
          // 獲取抽象封裝後的HttpListenerFeature
                var feature = new HttpListenerFeature(listenerContext);
                
                // 獲取封裝後的Feature集合
                var features = new FeatureCollection()
                    .Set<IHttpRequestFeature>(feature)
                    .Set<IHttpResponseFeature>(feature);
                    
                // 建立HttpContext
                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<Action<IApplicationBuilder>> _configures = new List<Action<IApplicationBuilder>>();   
    public IWebHostBuilder Configure(Action<IApplicationBuilder> 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<IApplicationBuilder> configure);
    IWebHost Build();
}

  父親除了建立WebHost以外,他還提供了註冊服務器的UseServer方法和用來註冊中間件的Configure方法。說到Configure方法,咱們必定還記得ApplicationBuilder方法的Use也是一個註冊器。這兩個註冊器有何不一樣呢?咱們對比一下代碼:

  • WebHostBuilder
public IWebHostBuilder Configure(Action<IApplicationBuilder> configure)
{
    _configures.Add(configure);
    return this;
}
  • ApplicationBuilder
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> 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();
}

參加文章:

相關文章
相關標籤/搜索