今天主題雖然是 Express,但我的重點其實是想放在 Express 的核心 Middleware
這個東西上面,因為它對程式的執行流程有很重大的影響,希望各位讀者看完本篇後,未來寫 Code 時都能好好善用 Middleware。
Middleware 的中文通常翻譯作「中間件」,不過我個人認為翻得不好,從中文字面無法領悟到背後的意義。假設我們有一個程式的執行流程是 A → B → C → D,進入點是 A,結束點是 D,而且後面執行的程式需要依賴前面執行的結果,在這個例子中,A、B、C 都可以算是 Middleware。
上面例子其實很抽象,我們轉換到 Web 的思維,當一個 Request 進到 Server,我們要做的事情可能依序包括:
而這些流程還夾帶著後者依賴前者的關係:登入過的使用者才需要檢查權限,權限許可的使用者才能進行後續操作,解析 Payload 之後才能知道 Recaptcha 欄位填寫的值,才能驗證 Recaptcha,確認不是機器人後,再繼續往下驗證表單資料是否都符合格式,當前面的每一步都過關之後,才允許真正地將資料寫入資料庫。
每一個路由都要對應不同的執行流程,不同的路由又可能會有重複或雷同的執行流程,如果你對於實作這樣複雜繁瑣的 Server 程式已經感到頭暈目眩,請不要害怕!Middleware 就是為了這樣的情境而生的。你可以將上述的 7 個步驟拆成 7 個不同的 Middleware 模組,在不同路由中只需要載入不同的 Middleware 模組就可以達到複用的效果,同時又能輕鬆管理錯縱的執行流程。
直接舉一個 Boilerplate 中實際使用的例子:
app.get(
'/api/users',
authRequired,
roleRequired([Roles.ADMIN]),
userController.list
);
當使用者想要存取 /api/users
這個資源時,先經過 authRequired
這個 Middleware 檢查使用者是否已經登入,再經由 roleRequired
檢查使用者是否具有 Admin 權限,當前面兩個 Middlewares 都通過了,再透過 userController 來回傳結果給 Client 端。
Express 的 Middleware 是一個長這樣的 Function:
let authRequired = (req, res, next) => {
if (!req.isAuth) {
return res.json({
error: '使用者未登入',
});
} else {
return next();
}
};
req
和 res
物件是 Express 處理 Request 和 Response 的機制,各位應該不陌生,而 next
是一個 Callback Function,也是掌控 Middleware 流程的核心,當我們呼叫 next 時可以進到下一個 Middleware。所以上面的例子中,當使用者還沒登入時,直接送回 Reject 訊息;當使用者是登入狀態時,我們呼叫 next
進入下一個 Middleware,繼續執行後續程式。這樣的寫法是不是很漂亮呢?
讀者們如果有興趣可以翻翻 Source Code 中的 /src/server/middlewares
資料夾,我們提供了大量的 Middlewares,有的是一次性的(如:initCookie),有的是按照 Request 需求選擇是否要使用(如:bodyParser、authRequired、roleRequired),有的則是對所有 Request 都使用(如:morgan)。
Middleware 的概念其實不只用於 Express 中,例如 Python 的著名架站框架 Django 就是以 Decorator 的形式來管理 Middleware,下例中的 csrf_exempt
和 login_required
就是 Middleware:
@csrf_exempt
@login_required
def apiWhiteListRenew(request):
if request.method == 'POST':
# ...
另一個例子是 Redux,在 Dispatch 每一個 Action 時,會先經過預先設定好的每一個 Redux Middleware,我就點到為止吧,再講下去就離題太遠了,另外,Redux 是狀態管理(State Management)的 Library,在後面的章節裡會再次提到。
在程式語言中,便利通常是犧牲效率換來的,Middleware 也是如此。在 Express 依序進入 Middlewares 時,會產生內部管理必要的 Overhead,在一些探討效率的文章中通常會建議少用 Middleware,但筆者認為,Middleware 帶來的便利也是同等重要,一來程式碼有高度複用性,二來容易維護。若要在效率和便利之間取捨,我還是會選擇便利。