ASP.NET Core - 請求管道與中介軟體

啊晚發表於2023-02-15

1. 請求管道

請求管道是什麼?請求管道描述的是一個請求進到我們的後端應用,後端應用如何處理的過程,從接收到請求,之後請求怎麼流轉,經過哪些處理,最後怎麼返回響應。請求管道就是一次請求在後端應用的生命週期。瞭解請求管道,有助於我們明白後端應用是怎麼工作的,我們的程式碼是怎麼工作的,在我們的業務程式碼執行前後經過哪些步驟,有助於我們之後更好的實現一些AOP操作。

請求管道是 .net 應用的一個最基本的概念。在 .net core 中,微軟對框架底層進行了全新的設計,相對於原本的ASP.NET中的全家桶模式的管道模型,.net core的管道模型更加靈活便捷,可做到熱插拔,透過管道可以隨意註冊自己想要的服務或者第三方服務外掛,這也是.net core效能更好的原因。

image

以上是微軟官方文件中的管道模型圖。從圖中可以看到 伺服器接收到請求之後,將接收到的請求向後傳遞,依次經過一個個 Middleware 進行處理,然後由最後一個 MiddleWare 生成響應內容並回傳,再反向依次經過每一個 Middleware,直到由伺服器傳送出去。整個過程就像一條流水線一樣,管道這個詞是很形象的,而 Middleware 就像一層一層的“濾網”,過濾所有的請求和響應。

2. 中介軟體

管道之中,對請求、響應進行加工處理的模組是 Middleware,也就是中介軟體。中介軟體本質上是一個委託。

2.1 工作模式

從上面的圖可以看出,每一箇中介軟體都會被執行兩次,在下一個中介軟體執行之前和之後各執行一次,分別是在處理請求和處理響應,只有一箇中介軟體是例外的,那就是最後一箇中介軟體,它後面沒有下一個中介軟體,所以執行到它管道就會迴轉。

這代表了中介軟體的兩種工作模式,也是中介軟體的兩種基本註冊方式。中介軟體本質上是一個委託,在程式碼實現上就體現在委託的入參有所不同以及註冊呼叫的方法不同。

中介軟體兩種最基本的註冊方式:

  • Use 方法註冊
    • use 註冊的中介軟體會傳入next引數,在處理完本身的邏輯之後可以呼叫 next() 去執行下一個中介軟體
    • 如果不執行,就等於Run
  • Run 方法註冊
    • Run 只是執行,沒有去呼叫Next ,一般作為終結點。
    • Run 方法註冊,只是一個擴充套件方法,最終還是呼叫Use方法

在程式碼中分別是以下方式:

app.Use(async (context, next) =>
{
    await context.Response.WriteAsync("Hello Middlerware !");
    if(context.Request.Query.TryGetValue("query", out var query))
    {
        await context.Response.WriteAsync(query);
    }
    await next();
    await context.Response.WriteAsync("End Middleware !");
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello last Middleware");
});

最後的執行結果如下,也可以從程式碼執行的先後順序看出管道流動的順序。當前中介軟體手動呼叫 next() 之後,就進入下一個中介軟體,下一個中介軟體處理完成之後,按照管道的順序再一個一個回傳。在這個過程中一直不變,被管道傳遞的就是HttpContext,而我們拿到 HttpContext,即可以透過 Request 和 Response 對當前這一次的請求做任何處理了。

image

透過分析asp.net core的原始碼,可以看到在我們呼叫 Run() 的時候,實際上還是呼叫了 Use() 方法。

image

而 Use() 方法中,主要的邏輯僅僅只是將相應的委託存放到集合中

image

之後在 build 方法呼叫的時候才一個一個地呼叫中介軟體委託。

image

除了上面的 Use() 、Run() 兩個最基本的方法註冊中介軟體之外,還有另外一些方法,如透過 Map() 方法註冊中介軟體,這種方式會建立一個新的管道分支,在路由滿足Map的規則時,請求則轉型新的管道分支,最後沿著管道分支返回響應,而不走原有的管道。

app.Use(async (context, next) =>
{
    await context.Response.WriteAsync("Hello Middlerware1 ! ");
    if(context.Request.Query.TryGetValue("query", out var query))
    {
        await context.Response.WriteAsync(query);
    }
    await next();
    await context.Response.WriteAsync("End Middleware1 ! ");
});

app.Map("/map", app =>
{
    // map方法中的委託,傳入的時IApplicationBuilder, 在這裡相當於一個新的管道,也可以和主管道一樣進行任意操作
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello map Middleware pipeline ! ");
    });
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello last Middleware ! ");
});

執行結果如下:

image

其他的分支管道建立方式,如 MapWhen,和 Map 大同小異,只是對於匹配判斷的方式有所不同。像微軟內建中介軟體中的靜態檔案中介軟體,MVC 中介軟體,其實都是以分支管道的方式實現的,一旦匹配到請求就會走管道分支。

2.2 中介軟體的使用配置

使用一箇中介軟體需要在 .net core 的入口檔案中進行配置,如果是 .net 6版本,那隻要在 program.cs 檔案中進行配置即可,透過 WebApplication 物件,也就是 app 呼叫相關的方法。

image

如果是 .net 6 以下版本,可以在 startup.cs 檔案中的 Configure 方法中配置。.net 6 與之前版本入口檔案的不同上一篇文章也講過,這裡就不贅述了。

image

這裡可以看得到,一些中介軟體的呼叫並沒有直接使用 Use() 和 Run(),畢竟將各個中介軟體的處理邏輯全部放在入口檔案很不好管理,而且也很不優雅。這裡涉及到了中介軟體封裝的約定規則,一般情況下封裝一箇中介軟體都會提供一個 Use[Middleware] 方法以供使用者進行中介軟體的呼叫,WebApplication 物件的 UseXXX 方法都是中介軟體呼叫的方法。

2.3 ASP.NET Core 框架內建中介軟體

ASP.NET Core 框架之中內建有很多中介軟體,並且我們透過 VS 建立某一個型別的專案時,如MVC、Razor Page,初始化的專案程式碼中會幫我們配置好一些中介軟體。

image

以上為官方文件中列出的內建中介軟體,可以看到在列表中對每個中介軟體的順序進行了說明。

管道中的中介軟體排列是有先後之分的,請求和響應按照中介軟體的排列順序進行傳遞,這也是我們程式碼邏輯執行的順序,而且一些中介軟體需要依賴於其他中介軟體的處理結果,或者必須在某些中介軟體前先執行,否則就會出問題了。

而中介軟體插入到管道中的順序,就是依據我們在入口檔案中呼叫相應中間註冊方法的順序,所以程式碼的前後順序非常重要,一旦寫錯了就會出現很多意想不到的的Bug。

向 Program.cs 檔案中新增中介軟體元件的順序定義了針對請求呼叫這些元件的順序,以及響應的相反順序。 此順序對於安全性、效能和功能至關重要。這是官方文件中的原話。

官方文件給出了典型MVC應用的管道中介軟體順序,這裡其實不止MVC,Razor Page、Web Api 也是這樣的管道模型,如下圖。這裡也明確了我們自定義的中介軟體應該插入到哪個位置。

image

更多的內建中介軟體的作用,以及相應的管道順序要求,請詳細閱讀一下官方文件,這裡就不細說了。



參考文章:

ASP.NET Core 中介軟體



ASP.NET Core 系列:
目錄:ASP.NET Core 系列總結
上一篇: ASP.NET Core - .NET 6 的入口檔案

相關文章