★ gin 的中间件与路由实现思路是什么?
一句话结论:gin 的核心是把 http.Handler 拆成一串 HandlerFunc:请求进来时,把全局中间件、路由组中间件、路由级中间件与最终 handler 按注册顺序拼成 handlers 数组,通过 c.Next() 推进下标形成洋葱模型;路由用压缩前缀树(radix tree)匹配,支持 :param 与 *wildcard。
中间件链怎么转起来
- 注册:engine.Use(m1, m2)、group.Use(...),把中间件追加到该路由 handlers 的前部。
- 执行:请求命中路由后,拷贝出完整 handlers 切片,从下标 0 开始逐个执行。
- c.Next():下标加一后调用下一个 handler;c.Abort():把下标跳到最大值,后续 handler 不再执行,但已开始的 defer 仍会收尾。
- 于是“执行前逻辑 → c.Next() → 执行后逻辑”就成了标准的前置/后置模式。
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
c.Next() // 放行后续中间件与 handler
log.Printf("cost=%v path=%s", time.Since(t), c.FullPath())
}
}
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
if !check(c.GetHeader("Authorization")) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"msg": "no"})
return
}
c.Next()
}
}
r := gin.New()
r.Use(Logger())
v1 := r.Group("/api", Auth())
v1.GET("/users/:id", getUser)
- 数据透传:c.Set(key, val) / c.Get;gin 的 Context 从 sync.Pool 复用,异步 goroutine 里要用 c.Copy(),别把原 c 带出请求生命周期。
路由实现思路
- 在 httprouter 的 radix tree 上演进:路径按公共前缀压缩成树节点,查找复杂度与路径长度相关而非路由数量。
- 支持 :name(段参数)与 *rest(通配剩余路径);动态段与静态段并存要遵守注册冲突规则。
- 找不到路由返回 404,方法不匹配返回 405;可自定义 NoRoute/NoMethod 兜底。
常见追问 / 记忆点
- 追问:中间件里 panic 会怎样?——gin.Recovery 中间件 recover 后返回 500 并把栈打到日志;自研中间件也可自行 recover。
- 追问:为什么 gin 的 Context 不能跨 goroutine 直接用?——Context 从池中复用、请求结束会被归还,并发读写内部字段是数据竞争。
- 记忆点:洋葱模型靠 Next/Abort 控链;前缀树路由;Context 用完即还、勿外带。