中间件执行顺序 #
一、洋葱模型 #
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函数 |
关键原则:
- 先注册先执行:前置处理按注册顺序
- 后注册先返回:后置处理按注册逆序
- 错误中断:返回错误会中断执行链
- 合理顺序:RequestID → Recover → Logger → …
准备好学习请求与响应了吗?让我们进入下一章!
最后更新:2026-03-28