為什么需要中間件
我們可能需要對每個請求/返回做一些特定的操作,比如
- 記錄請求的 log 信息
- 在返回中插入一個 Header
- 部分接口進行鑒權
這些都需要一個統一的入口。這個功能可以通過引入 middleware 中間件來解決。Go 的 net/http 設計的一大特點是特別容易構建中間件。apiserver 所使用的 gin 框架也提供了類似的中間件。
gin里面的中間件
在 gin 中可以設置 3 種類型的 middleware:
- 全局中間件
router := gin.New()
// 添加自定義的 logger 中間件
router.Use(middleware.Logger(), gin.Recovery())
- 單個路由中間件
userRouter.GET("/profile/", middleware.Auth(), handler.UserProfile)
userRouter.POST("/update", middleware.Auth(), handler.UpdateUserProfile)
- 群組中間件
authorized := router.Group("/", MyMiddelware())
// 或者這樣用:
authorized := router.Group("/")
authorized.Use(MyMiddelware())
{authorized.POST("/login", loginEndpoint)
}
在請求和返回的 Header 中插入 X-Request-Id
X-Request-Id 值為 32 位的 UUID,用于唯一標識一次 HTTP 請求
func RequestId() gin.HandlerFunc{return func(c *gin.Context) {requestId := c.Request.Header.Get("X-Request-Id")if requestId==""{v4:= uuid.NewV4()requestId=v4.String()}c.Set("X-Request-Id", requestId)c.Writer.Header().Set("X-Request-Id", requestId)c.Next()}
日志中間件
- 獲取請求路徑,并且進行匹配(只對業務邏輯進行日志記錄)
path := c.Request.URL.Pathreg:= regexp.MustCompile("(/v1/user|/login)")if !reg.MatchString(path) {return}// Skip for the health check requests.if path == "/sd/health" || path == "/sd/ram" || path == "/sd/cpu" || path == "/sd/disk" {return}
- 獲取請求中的IP等信息,并且給請求重新賦值(請求讀取完會被置空)
var bodys []byteif c.Request.Body!=nil{bodys, _ = ioutil.ReadAll(c.Request.Body)}c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodys))method := c.Request.Methodip := c.ClientIP()
- 將響應重定向到指定IO流,并且提取里面的信息
blw := &bodyLogWriter{body: bytes.NewBufferString(""),ResponseWriter: c.Writer,}c.Writer=blwc.Next()var response handler.Responseif err := json.Unmarshal(blw.body.Bytes(), &response); err != nil {log.Println(err, "response body can not unmarshal to model.Response struct, body: %s", string(blw.body.Bytes()))code = errno.InternalServerError.Codemessage = err.Error()} else {code = response.Codemessage = response.Message}}
func (w bodyLogWriter) Write(b []byte) (int, error) {w.body.Write(b)return w.ResponseWriter.Write(b)
- 將從請求與響應中提取的信息進行輸出
log.Printf("%-13s | %-12s | %s %s | {code: %d, message: %s}", sub, ip, pad.Right(method, 5, ""), path, code, message)
測試
X-Request-id
可以看到,HTTP 返回的 Header 有 32 位的 UUID:
日志
每個請求的日志信息分為4個部分
- 耗時
- 請求 IP
- HTTP 方法 HTTP 路徑
- 返回的 Code 和 Message