今天要來介紹 Go 的 Middleware!讓 To-do List API 錯誤回應更簡潔、更好管理~
一開始會先介紹一下 Middleware,然後再說明要怎麼修改 To-do List API。
Middleware 就是「 在請求和回應的中間過程中,先做一些額外處理 」的函式,它叫做中介軟體或是中間件。
請求 → Middleware → Handler (API 邏輯) → Middleware → 回應
簡單來說,可以想成:
每個人進公司的時候,都會經過保全,保全確認你有停車證就會讓你通過。如果沒有停車證,就會檢查你的身份證,然後再簽名本上留下紀錄,才會讓你進去。
所以 Middleware 就很像是保全,基本上每個人的進出都需要經過,然後他會在你出入大門的時候,檢查你的身份,並且做紀錄。
👉 一種 攔截器 的概念!
也因為 Middleware 可以攔截請求、處理回應,所以它常被用來做以下功能:
Middleware 的用法:r.Use()
。位置則是放在 server 建立之後;router建立之前。
可以新增一個或多個 Middleware 到 r.Use()
,這樣每個請求就都會依序經過,然後再透過 c.Next()
決定要在 handler 前執行還是之後執行。
r := gin.Default() // 放在 server 建立之後
// 寫法 1
r.Use(Middleware1, Middleware2, ...)
// 寫法 2
r.Use(Middleware1)
r.Use(Middleware2)
r.GET("/tasks", getTasks) // 放在 router 建立之前
範例:
// Middleware function 範例
func ExampleMiddleware(c *gin.Context) {
// 前置處理
fmt.Println("Before handler")
c.Next() // 執行真正的 handler
// 後置處理
fmt.Println("After handler")
}
透過 Middleware 的協助,我們就可以不用寫一堆重複的程式(Ex: 錯誤處理、身份驗證),可以讓程式結構更簡潔。
所以接下來就要來修改 To-do List API 的錯誤處理!
前幾天,我們已經完成了 GET、POST、PATCH、DELETE 的 API 設計,但不知道大家有沒有發現在這些函式之中,其實有一些重複的地方。
每一次呼叫 API 函式時,都會回應呼叫狀態是否有成功,而在回應 API 的錯誤訊息時,就會輸入以下內容:
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
而當這些錯誤訊息不斷重複出現時,就可以考慮把它移出來,請 Middleware 協助管理。這樣就可以透過傳入不同錯誤參數的方式,集中管理全部的錯誤訊息。
修改為:
c.Error("這邊帶錯誤參數")
首先,先來建立一個自訂錯誤的清單:
// 自訂錯誤
var (
ErrNotFound = errors.New("task not found")
ErrInvalidID = errors.New("invalid ID")
)
這邊把錯誤訊息都改成小寫的原因是因為 Go 的官方程式設計慣例裡,有以下的規則:
錯誤訊息(error string)不應該以大寫字母開頭,也不應該以標點符號結尾。
除非這個錯誤字串裡面本身有專有名詞(像是 HTTP、ID、SQL 等等)。
再來是,建立 Middleware 的函式(ErrorHandler):
func ErrorHandler(c *gin.Context) {
c.Next() // 在 handler 之後執行
if len(c.Errors) > 0 { // 檢查是否有錯誤
err := c.Errors.Last().Err
var statusCode int
var message string
switch { // 判斷錯誤類型
case errors.Is(err, ErrNotFound):
statusCode = http.StatusNotFound
message = err.Error()
case errors.Is(err, ErrInvalidID):
statusCode = http.StatusBadRequest
message = err.Error()
default:
statusCode = http.StatusInternalServerError
message = "internal server error"
}
c.JSON(statusCode, gin.H{"error": message})
c.Abort() // 中斷 Middleware(停止)
}
}
完成 ErrorHandler 之後,在 main 函式裡補上 r.Use(ErrorHandler)
,然後再依序把原本程式中處理 error message 的地方改成:
c.Error(err)
c.Error(ErrInvalidID)
c.Error(ErrNotFound)
把剛剛自訂的錯誤參數帶進去,這樣就完成了! 🎉
這樣 To-do List 也有了個雛形,但目前所有的程式都在 main.go
的檔案中,這樣其實不太符合實際開發的做法,所以明天會先介紹在實務中,一般會把專案拆成幾個層次。
那我們就明天見囉~ 👋👋👋