17370845950

Golang实现一个基础的中间件示例
Go HTTP中间件是接收并返回http.Handler的函数,通过闭包在原handler前后插入逻辑;需调用next.ServeHTTP以确保链式执行,顺序影响执行流程,配置应作为参数传入避免共享,panic恢复中间件须置于最外层。

什么是 Go HTTP 中间件

Go 的中间件本质是函数套函数:接收一个 http.Handler,返回一个新的 http.Handler,在调用原 handler 前后插入逻辑。它不是框架内置概念,而是基于 http.Handler 接口和闭包的自然延伸。

最简中间件写法(日志示例)

核心是实现 func(http.Handler) http.Handler 签名。注意别漏掉 next.ServeHTTP(w, r),否则请求就断了。

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Started %s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r) // 必须调用,否则下游 handler 不会执行
        log.Printf("Completed %s %s", r.Method, r.URL.Path)
    })
}

使用时链式包装:

handler := http.HandlerFunc(yourHandler)

handler = loggingMiddleware(handler) handler = authMiddleware(handler) // 可叠加多个 http.ListenAndServe(":8080", handler)

带参数的中间件(如允许跨域)

中间件本身可以接受配置参数,返回“中间件工厂函数”。常见错误是把配置写死在闭包外,导致所有实例共享同一份配置。

  • allowedOrigins 应作为参数传入,而非全局变量
  • 每个 CORS() 调用返回独立的中间件实例
  • 注意 w.Header().Set() 必须在 next.ServeHTTP 之前设置响应头,否则可能被覆盖
func CORS(allowedOrigins []string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            origin := r.Header.Get("Origin")
            if len(allowedOrigins) == 0 || contains(allowedOrigins, origin) {
                w.Header().Set("Access-Control-Allow-Origin", origin)
                w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
                w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
            }
            if r.Method == "OPTIONS" {
                w.WriteHeader(http.StatusOK)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

func contains(slice []string, s string) bool {
    for _, v := range slice {
        if v == s {
            return true
        }
    }
    return false
}

中间件顺序很重要

中间件从外到内执行:第一个包装的最先运行,最后一个包装的最后运行(但它的前置逻辑在 next.ServeHTTP 前,后置逻辑在之后)。比如:

  • loggingMiddleware(authMiddleware(handler)):先打日志 → 再鉴权 → 再执行 handler → 再打完成日志
  • 如果鉴权失败,next.ServeHTTP 不会被调用,后续中间件和 handler 都不会执行
  • panic 恢复中间件必须放在最外层,才能捕获所有内层 panic

容易忽略的是:中间件内部的 http.Error 或直接写 w.WriteHeader 后再调用 next.ServeHTTP,可能导致重复写 header 报错 http: multiple response.WriteHeader calls