中间件执行顺序 #

一、洋葱模型 #

1.1 模型图解 #

text
                    ┌─────────────────────────────────┐
                    │         Middleware 1            │
                    │   ┌─────────────────────────┐   │
                    │   │     Middleware 2        │   │
                    │   │   ┌─────────────────┐   │   │
                    │   │   │  Middleware 3   │   │   │
                    │   │   │   ┌─────────┐   │   │   │
                    │   │   │   │ Handler │   │   │   │
                    │   │   │   └─────────┘   │   │   │
                    │   │   │                 │   │   │
                    │   │   └─────────────────┘   │   │
                    │   │                         │   │
                    │   └─────────────────────────┘   │
                    │                                 │
                    └─────────────────────────────────┘

请求 ─────────────────────────────────────────────────────▶
    
响应 ◀─────────────────────────────────────────────────────

1.2 执行流程 #

text
请求进入
    │
    ▼
┌─────────────────┐
│ Middleware 1    │ ─── 前置处理
│   next(c)       │ ─── 调用下一个
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Middleware 2    │ ─── 前置处理
│   next(c)       │ ─── 调用下一个
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Middleware 3    │ ─── 前置处理
│   next(c)       │ ─── 调用下一个
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Handler         │ ─── 业务处理
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Middleware 3    │ ─── 后置处理
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Middleware 2    │ ─── 后置处理
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Middleware 1    │ ─── 后置处理
└─────────────────┘
    │
    ▼
响应返回

二、执行顺序示例 #

2.1 基本示例 #

go
package main

import (
    "fmt"
    "net/http"
    "github.com/labstack/echo/v4"
)

func middleware1(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        fmt.Println("M1 - Before")
        err := next(c)
        fmt.Println("M1 - After")
        return err
    }
}

func middleware2(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        fmt.Println("M2 - Before")
        err := next(c)
        fmt.Println("M2 - After")
        return err
    }
}

func middleware3(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        fmt.Println("M3 - Before")
        err := next(c)
        fmt.Println("M3 - After")
        return err
    }
}

func handler(c echo.Context) error {
    fmt.Println("Handler")
    return c.String(http.StatusOK, "OK")
}

func main() {
    e := echo.New()
    
    e.Use(middleware1)
    e.Use(middleware2)
    e.Use(middleware3)
    
    e.GET("/", handler)
    
    e.Start(":8080")
}

输出:

text
M1 - Before
M2 - Before
M3 - Before
Handler
M3 - After
M2 - After
M1 - After

2.2 执行顺序图 #

text
注册顺序:middleware1 → middleware2 → middleware3

执行顺序:
┌─────────────────────────────────────────────────────────┐
│                                                         │
│   M1 Before                                             │
│   └── M2 Before                                         │
│       └── M3 Before                                     │
│           └── Handler                                   │
│           └── M3 After                                  │
│       └── M2 After                                      │
│   └── M1 After                                          │
│                                                         │
└─────────────────────────────────────────────────────────┘

三、中间件级别 #

3.1 三种级别 #

go
e := echo.New()

// 1. 全局中间件
e.Use(globalMiddleware)

// 2. 分组中间件
api := e.Group("/api")
api.Use(groupMiddleware)

// 3. 路由中间件
api.GET("/users", handler, routeMiddleware)

3.2 级别执行顺序 #

go
package main

import (
    "fmt"
    "net/http"
    "github.com/labstack/echo/v4"
)

func globalMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        fmt.Println("Global - Before")
        err := next(c)
        fmt.Println("Global - After")
        return err
    }
}

func groupMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        fmt.Println("Group - Before")
        err := next(c)
        fmt.Println("Group - After")
        return err
    }
}

func routeMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        fmt.Println("Route - Before")
        err := next(c)
        fmt.Println("Route - After")
        return err
    }
}

func handler(c echo.Context) error {
    fmt.Println("Handler")
    return c.String(http.StatusOK, "OK")
}

func main() {
    e := echo.New()
    
    e.Use(globalMiddleware)
    
    api := e.Group("/api")
    api.Use(groupMiddleware)
    
    api.GET("/users", handler, routeMiddleware)
    
    e.Start(":8080")
}

输出:

text
Global - Before
Group - Before
Route - Before
Handler
Route - After
Group - After
Global - After

3.3 执行顺序图 #

text
┌─────────────────────────────────────────────────────────┐
│                                                         │
│   Global Middleware                                     │
│   └── Group Middleware                                  │
│       └── Route Middleware                              │
│           └── Handler                                   │
│           └── Route Middleware                          │
│       └── Group Middleware                              │
│   └── Global Middleware                                 │
│                                                         │
└─────────────────────────────────────────────────────────┘

四、中断执行链 #

4.1 返回错误 #

go
func authMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        token := c.Request().Header.Get("Authorization")
        
        if token == "" {
            return echo.NewHTTPError(http.StatusUnauthorized, "请登录")
        }
        
        return next(c)
    }
}

4.2 中断执行流程 #

text
请求
    │
    ▼
┌─────────────────┐
│ Logger          │
│   next(c)       │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Auth            │
│ return Error    │ ◀── 中断执行,直接返回
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Logger          │ ─── 后置处理
└─────────────────┘
    │
    ▼
响应

4.3 示例代码 #

go
package main

import (
    "fmt"
    "net/http"
    "github.com/labstack/echo/v4"
)

func loggerMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        fmt.Println("Logger - Before")
        err := next(c)
        fmt.Println("Logger - After")
        return err
    }
}

func authMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        fmt.Println("Auth - Before")
        
        token := c.Request().Header.Get("Authorization")
        if token == "" {
            fmt.Println("Auth - Error")
            return echo.NewHTTPError(http.StatusUnauthorized, "请登录")
        }
        
        fmt.Println("Auth - Pass")
        return next(c)
    }
}

func handler(c echo.Context) error {
    fmt.Println("Handler")
    return c.String(http.StatusOK, "OK")
}

func main() {
    e := echo.New()
    
    e.Use(loggerMiddleware)
    e.Use(authMiddleware)
    
    e.GET("/", handler)
    
    e.Start(":8080")
}

无Token时输出:

text
Logger - Before
Auth - Before
Auth - Error
Logger - After

有Token时输出:

text
Logger - Before
Auth - Before
Auth - Pass
Handler
Logger - After

五、中间件跳过 #

5.1 Skipper函数 #

go
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
    Skipper: func(c echo.Context) bool {
        return c.Path() == "/health"
    },
}))

5.2 自定义跳过 #

go
func skipMiddleware(skip func(c echo.Context) bool) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            if skip(c) {
                return next(c)
            }
            
            return authMiddleware(next)(c)
        }
    }
}

e.Use(skipMiddleware(func(c echo.Context) bool {
    return c.Path() == "/login" || c.Path() == "/register"
}))

5.3 跳过执行流程 #

text
请求
    │
    ▼
┌─────────────────┐
│ Middleware      │
│   Skipper=true  │ ◀── 跳过中间件逻辑
│   next(c)       │
└─────────────────┘
    │
    ▼
Handler

六、执行顺序调试 #

6.1 调试中间件 #

go
func debugMiddleware(name string) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            fmt.Printf("[%s] Before - Path: %s\n", name, c.Path())
            
            err := next(c)
            
            fmt.Printf("[%s] After - Status: %d\n", name, c.Response().Status)
            
            return err
        }
    }
}

e.Use(debugMiddleware("M1"))
e.Use(debugMiddleware("M2"))
e.Use(debugMiddleware("M3"))

6.2 执行时间追踪 #

go
func timingMiddleware(name string) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            start := time.Now()
            
            fmt.Printf("[%s] Start: %v\n", name, start)
            
            err := next(c)
            
            duration := time.Since(start)
            fmt.Printf("[%s] Duration: %v\n", name, duration)
            
            return err
        }
    }
}

6.3 调用栈追踪 #

go
func stackMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        fmt.Println("=== Middleware Stack ===")
        for i := 2; ; i++ {
            _, file, line, ok := runtime.Caller(i)
            if !ok {
                break
            }
            fmt.Printf("  %s:%d\n", file, line)
        }
        
        return next(c)
    }
}

七、中间件顺序最佳实践 #

7.1 推荐顺序 #

go
e := echo.New()

// 1. 请求ID - 最先生成ID
e.Use(middleware.RequestID())

// 2. 错误恢复 - 捕获所有panic
e.Use(middleware.Recover())

// 3. 日志记录 - 记录所有请求
e.Use(middleware.Logger())

// 4. Gzip压缩 - 压缩响应
e.Use(middleware.Gzip())

// 5. CORS - 跨域处理
e.Use(middleware.CORS())

// 6. 限流 - 防止滥用
e.Use(middleware.RateLimiter())

// 7. 安全头 - 安全防护
e.Use(middleware.Secure())

// 8. 认证 - 用户认证
e.Use(middleware.JWT())

// 9. 权限 - 权限验证
e.Use(permissionMiddleware)

7.2 顺序说明 #

顺序 中间件 原因
1 RequestID 生成请求ID供后续使用
2 Recover 捕获所有panic
3 Logger 记录完整请求
4 Gzip 压缩响应体
5 CORS 处理跨域预检
6 RateLimiter 限流保护
7 Secure 安全头
8 Auth 认证用户
9 Permission 验证权限

7.3 分组中间件顺序 #

go
e := echo.New()

// 全局中间件
e.Use(middleware.RequestID())
e.Use(middleware.Recover())
e.Use(middleware.Logger())

// 公开API
public := e.Group("/api/v1")
{
    public.POST("/login", login)
    public.POST("/register", register)
}

// 需要认证的API
protected := e.Group("/api/v1")
protected.Use(middleware.JWT([]byte("secret")))
{
    protected.GET("/profile", getProfile)
    protected.GET("/users", getUsers)
}

// 管理员API
admin := e.Group("/api/v1/admin")
admin.Use(middleware.JWT([]byte("secret")))
admin.Use(adminOnlyMiddleware)
{
    admin.GET("/stats", getStats)
    admin.DELETE("/users/:id", deleteUser)
}

八、完整示例 #

go
package main

import (
    "fmt"
    "net/http"
    "time"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func main() {
    e := echo.New()
    
    e.Use(middleware.RequestID())
    e.Use(middleware.Recover())
    e.Use(middleware.Logger())
    e.Use(timingMiddleware)
    
    e.GET("/health", health)
    
    api := e.Group("/api/v1")
    api.Use(authMiddleware)
    
    api.GET("/users", getUsers)
    api.GET("/profile", getProfile)
    
    admin := api.Group("/admin")
    admin.Use(adminMiddleware)
    
    admin.GET("/stats", getStats)
    
    e.Logger.Fatal(e.Start(":8080"))
}

func timingMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        start := time.Now()
        
        fmt.Printf("[Timing] Start: %s %s\n", c.Request().Method, c.Path())
        
        err := next(c)
        
        fmt.Printf("[Timing] End: %v\n", time.Since(start))
        
        return err
    }
}

func authMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        fmt.Println("[Auth] Checking...")
        
        token := c.Request().Header.Get("Authorization")
        if token == "" {
            fmt.Println("[Auth] Failed")
            return echo.NewHTTPError(http.StatusUnauthorized, "请登录")
        }
        
        fmt.Println("[Auth] Passed")
        c.Set("userID", "123")
        
        return next(c)
    }
}

func adminMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        fmt.Println("[Admin] Checking...")
        
        fmt.Println("[Admin] Passed")
        
        return next(c)
    }
}

func health(c echo.Context) error {
    fmt.Println("[Handler] Health")
    return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
}

func getUsers(c echo.Context) error {
    fmt.Println("[Handler] GetUsers")
    return c.JSON(http.StatusOK, []map[string]string{
        {"id": "1", "name": "张三"},
    })
}

func getProfile(c echo.Context) error {
    fmt.Println("[Handler] GetProfile")
    return c.JSON(http.StatusOK, map[string]string{
        "id":   c.Get("userID").(string),
        "name": "张三",
    })
}

func getStats(c echo.Context) error {
    fmt.Println("[Handler] GetStats")
    return c.JSON(http.StatusOK, map[string]int{"users": 100})
}

九、总结 #

中间件执行顺序要点:

要点 说明
洋葱模型 请求进入、响应返回
注册顺序 先注册先执行(前置)
执行级别 全局 > 分组 > 路由
中断执行 返回错误
跳过执行 Skipper函数

关键原则:

  1. 先注册先执行:前置处理按注册顺序
  2. 后注册先返回:后置处理按注册逆序
  3. 错误中断:返回错误会中断执行链
  4. 合理顺序:RequestID → Recover → Logger → …

准备好学习请求与响应了吗?让我们进入下一章!

最后更新:2026-03-28