天天看點

.net core 中間件管道底層剖析

.net core 中間件管道底層剖析

.net core 管道(Pipeline)是什麼?

由上圖可以看出,.net core 管道是請求抵達伺服器到響應結果傳回的中間的一系列的處理過程,如果我們簡化一下成下圖來看的話,.net core 的管道其實就是中間件的部分。微軟中間件文檔

為什麼管道就是中間件的部分了呢?我是這麼了解的,.net core 是通過Startup 類配置服務和應用的請求管道,是以狹義點來講這個管道就是指的請求管道,就是我們今天要了解的中間件管道。

.net core 核心體系結構的特點就是一個中間件系統,它是處理請求和響應的代碼段。中間件彼此連結,形成一個管道。傳入的請求通過管道傳遞,其中每個中間件都有機會在将它們傳遞到下一個中間件之前對它們進行處理。傳出響應也以相反的順序通過管道傳遞。

PS:簡單來講就是請求開始到響應結束的中間的一大部分。你可以了解成 " 汽車銷售 " (開始買車到提車的過程,但願不會坐在奔馳車蓋上哭),哈哈……

還有我們來看看,為什麼我們要簡化來看,在運作時 .net core 會預先注入一些必要的服務及依賴項,預設注入(ServiceCollection)的服務清單如下:

我們先斷章取義地看,這裡面有 Kestrel 處理請求,将接收到的請求内容(字元串流)轉化成結構化的資料(HttpContext)供後面的中間件使用的服務。欸,服務喲。那其實也就是 Kestrel 服務也是中間件嘛。

而第一張圖中的MVC本身也作為中間件來實作的。

還有一些相關的服務都可以看看上面截圖的服務,而且旁邊标注的生命周期的類型就是之前所講到的 .net core 的三種注入模式 。

那麼從程式入口來講,過程是怎麼樣的呢?

從應用程式主入口 Main() --> WebHost --> UseStartup

複制代碼

///

/// Specify the startup type to be used by the web host.

/// The to configure.

/// The to be used.

/// The .

public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)

{

string name = startupType.GetTypeInfo().Assembly.GetName().Name;
return hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, name).ConfigureServices((Action<IServiceCollection>)delegate(IServiceCollection services)
{
    if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
    {
        ServiceCollectionServiceExtensions.AddSingleton(services, typeof(IStartup), startupType);
    }
    else
    {
        ServiceCollectionServiceExtensions.AddSingleton(services, typeof(IStartup), (Func<IServiceProvider, object>)delegate(IServiceProvider sp)
        {
            IHostingEnvironment requiredService = ServiceProviderServiceExtensions.GetRequiredService<IHostingEnvironment>(sp);
            return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, requiredService.get_EnvironmentName()));
        });
    }
});           

}

上面的代碼就可以解釋說,會預先注入的必要的服務,在通過委托的方式,注入 Startup 裡的服務。具體可以繼續探究:

UseSetting:Add or replace a setting in the configuration.

/// Add or replace a setting in the configuration.

/// The key of the setting to add or replace.

/// The value of the setting to add or replace.

public IWebHostBuilder UseSetting(string key, string value)

_config.set_Item(key, value);
return this;           

ConfigureServices:Adds a delegate for configuring additional services for the host or web application. This may be called multiple times.

/// Adds a delegate for configuring additional services for the host or web application. This may be called

/// multiple times.

/// A delegate for configuring the .

public IWebHostBuilder ConfigureServices(Action configureServices)

if (configureServices == null)
{
    throw new ArgumentNullException("configureServices");
}
return ConfigureServices(delegate(WebHostBuilderContext _, IServiceCollection services)
{
    configureServices(services);
});           

ConventionBasedStartup

public class ConventionBasedStartup : IStartup

private readonly StartupMethods _methods;

public ConventionBasedStartup(StartupMethods methods)
{
    _methods = methods;
}

public void Configure(IApplicationBuilder app)
{
    try
    {
        _methods.ConfigureDelegate(app);
    }
    catch (Exception ex)
    {
        if (ex is TargetInvocationException)
        {
            ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
        }
        throw;
    }
}

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    try
    {
        return _methods.ConfigureServicesDelegate(services);
    }
    catch (Exception ex)
    {
        if (ex is TargetInvocationException)
        {
            ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
        }
        throw;
    }
}           

OK,到這裡就已經确定了,我們可控的是通過Startup注入我們所需的服務,就是Startup注入的中間件可以做所有的事情,如處理認證,錯誤,靜态檔案等等,并且如上面所說的 MVC 在 .net core 也是作為中間件實作的。

那麼 .net core 給我們内置了多少中間件呢?如下圖:

我們很經常用到的内置中間件有:

app.UseExceptionHandler(); //異常處理
app.UseStaticFiles(); //靜态檔案
app.UseAuthentication(); //Auth驗證
app.UseMvc(); //MVC           

我們知道可以在啟動類的 Configure 方法中配置 .net core 管道,通過調用 IApplicationBuilder 上的 Use* 方法,就可以向管道添加一個中間件,被添加的順序決定了請求周遊它們的順序。是以,如上面添加内置中間件的順序,傳入的請求将首先周遊異常處理程式中間件,然後是靜态檔案中間件,然後是身份驗證中間件,最終将由MVC中間件處理。

Use* 方法實際上隻是 .net core 提供給我們的“快捷方式”,以便更容易地建構管道。在幕後,它們最終都使用(直接或間接)這些關鍵字:Use 和 Run 。兩者都向管道中添加了一個中間件,不同之處在于Run添加了一個終端中間件,即管道中的最後一個中間件。

那麼有内置,就應該可以定制的。如何定制自己的中間件呢?上一些簡陋的demo示範一下:

(1)無分支管道

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)

// Middleware A
   app.Use(async (context, next) =>
   {
        Console.WriteLine("A (before)");
        await next();
        Console.WriteLine("A (after)");
   });

   // Middleware B
   app.Use(async (context, next) =>
   {
        Console.WriteLine("B (before)");
        await next();
        Console.WriteLine("B (after)");
   });

   // Middleware C (terminal)
   app.Run(async context =>
   {
        Console.WriteLine("C");
        await context.Response.WriteAsync("Hello world");
   });
           
列印結果:
           

A (before)

B (before)

C

B (after)

A (after)

那用管道圖展示的話就是:
           

(2)有分支管道,當使用無分支的管道時,相當于就是一條線直走到底再傳回響應結果。但一般情況下,我們都希望管道更具靈活性。建立有分支的管道就需要使用到 Map 擴充用作約定來建立管道分支。Map 是基于給定請求路徑的比對項來建立請求管道分支的,如果請求路徑以給定的路徑開頭,就執行分支。那麼就有兩種類型有分支的管道:

1.無連結分支,上官方demo:
           

public class Startup

private static void HandleMapTest1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

private static void HandleMapTest2(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 2");
    });
}

public void Configure(IApplicationBuilder app)
{
    app.Map("/map1", HandleMapTest1);

    app.Map("/map2", HandleMapTest2);

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
    });
}           
結果:
           
以上無連結分支很容易就了解了,就是不同的路徑跑不同的分支。如果是有參數比對的話,就要使用 MapWhen,而 MapWhen 基于給定謂詞的結果建立請求管道分支。Func<HttpContext, bool> 類型的任何謂詞均可用于将請求映射到管道的新分支。 謂詞用于檢測查詢字元串變量 branch 是否存在。

    2.有連結(重新連接配接上主管道)分支,建立有連結分支管道就要使用到 UseWhen,上demo:
           

public void Configure(IApplicationBuilder app)

app.Use(async (context, next) =>
{
    Console.WriteLine("A (before)");
    await next();
    Console.WriteLine("A (after)");
});

app.UseWhen(
    context => context.Request.Path.StartsWithSegments(new PathString("/foo")),
    a => a.Use(async (context, next) =>
    {
        Console.WriteLine("B (before)");
        await next();
        Console.WriteLine("B (after)");
    }));

app.Run(async context =>
{
    Console.WriteLine("C");
    await context.Response.WriteAsync("Hello world");
});           
像上面的代碼,當請求不是以 " /foo " 開頭的時候,結果為:
           
當請求是以 " /foo " 開頭的時候,結果為:
           
正如您所看到的,中間件管道背後的思想非常簡單,但是非常強大。大多數功能都是 .net core(身份驗證、靜态檔案、緩存、MVC等)作為中間件實作。當然,編寫自己的代碼也很容易!

     後面可以進階寫自己的中間件,官方文檔。           

原文位址

https://www.cnblogs.com/Vam8023/p/10700254.html