路径参数怎么搞

新手必踩的坑:
参数提取
// 这样写 - 3 参数 handler
router.GET(\"/user/:id\", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
userID := ps.ByName(\"id\") // 直接拿,快
// 处理逻辑...
})
// 能用但慢点 - 标准 handler
router.GET(\"/posts/:id\", func(w http.ResponseWriter, r *http.Request) {
ps := httprouter.ParamsFromContext(r.Context())
postID := ps.ByName(\"id\") // 多一次查找
})
通配符的坑
router.GET(\"/files/*filepath\", serveFile) // 正确
// 注意:通配符必须在最后,不能是 /files/*filepath/something
我之前试过 /api/*version/users
,结果发现根本注册不了,报了个 panic: wildcard segment '*version' conflicts with existing route
的错误。查了半天文档才搞明白,通配符就是通配符,会吃掉后面所有路径,不能再有别的东西。
错误处理踩过的坑
Panic Recovery 不配就死
第一次上生产没配 PanicHandler,某个 handler 有 nil pointer(到现在也不知道哪个逗比写的代码),整个服务直接挂了。半夜被叫醒,心态爆炸。配了这个至少还能返回 500:
router.PanicHandler = func(w http.ResponseWriter, r *http.Request, p interface{}) {
// 记录详细错误,但别把内部信息暴露给用户
log.Printf(\"PANIC %s %s: %v
Stack: %s\",
r.Method, r.URL.Path, p, debug.Stack())
w.Header().Set(\"Content-Type\", \"application/json\")
w.WriteHeader(500)
json.NewEncoder(w).Encode(map[string]string{
\"error\": \"Internal server error\",
\"request_id\": generateRequestID(), // 生产环境必备
})
}
404/405 要自定义
默认的 404 返回 HTML,API 服务这样不行:
router.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(\"Content-Type\", \"application/json\")
w.WriteHeader(404)
json.NewEncoder(w).Encode(map[string]interface{}{
\"error\": \"endpoint not found\",
\"path\": r.URL.Path,
\"timestamp\": time.Now().Unix(),
})
})
中间件的正确打开方式
HttpRouter 没有内置中间件链,但你可以这样搞。Ben Hoyt 的路由对比文章 分析了不同路由器的中间件实现方式,这个高级 HTTP 编程教程 展示了各种中间件模式:
全局中间件
func withMiddleware(router *httprouter.Router) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 请求日志
start := time.Now()
// CORS 头(API 服务必备)
w.Header().Set(\"Access-Control-Allow-Origin\", \"*\")
w.Header().Set(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,DELETE,OPTIONS\")
// 处理 OPTIONS 请求
if r.Method == \"OPTIONS\" {
w.WriteHeader(200)
return
}
router.ServeHTTP(w, r)
log.Printf(\"%s %s - %v\", r.Method, r.URL.Path, time.Since(start))
})
}
// 使用
handler := withMiddleware(router)
http.ListenAndServe(\":8080\", handler)
路由级中间件
如果你需要某些路由有特定的中间件(比如认证),可以这样包装。这篇认证实现教程 和 JWT 认证完整指南 提供了详细的认证中间件实现。CodeVoweb 的 JWT 授权指南 和 使用 PostgreSQL 的安全认证 也很实用:
func requireAuth(next httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
token := r.Header.Get(\"Authorization\")
if token == \"\" {
w.WriteHeader(401)
json.NewEncoder(w).Encode(map[string]string{\"error\": \"missing token\"})
return
}
// 验证 token...
if !isValidToken(token) {
w.WriteHeader(401)
json.NewEncoder(w).Encode(map[string]string{\"error\": \"invalid token\"})
return
}
next(w, r, ps) // 继续处理
}
}
// 使用
router.GET(\"/admin/users\", requireAuth(listUsers))
生产环境踩坑
路由冲突检测
HttpRouter 启动时检测路由冲突,好事,但报错有时候不清楚:
// 这样冲突
router.GET(\"/user/profile\", getUserProfile)
router.GET(\"/user/:id\", getUser) // 冲突!
// 改成这样
router.GET(\"/users/:id\", getUser)
router.GET(\"/users/:id/profile\", getUserProfile)
静态文件服务
用 HttpRouter 做静态文件服务器时要小心:
// 这样用于开发环境
router.ServeFiles(\"/static/*filepath\", http.Dir(\"./static/\"))
// 生产环境建议用 nginx,或者至少加上缓存头
router.GET(\"/static/*filepath\", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
w.Header().Set(\"Cache-Control\", \"public, max-age=31536000\") // 1年缓存
http.ServeFile(w, r, \"./static/\"+ps.ByName(\"filepath\"))
})
性能优化的实际经验
我们的一个 API 服务有 200 多个路由吧,一开始用 Gorilla Mux,高峰期 CPU 经常爆满。换成 HttpRouter 后:
- 响应时间从平均 50ms 左右降到 15ms(不过我怀疑主要是数据库查询优化的功劳)
- CPU 使用率从 80% 左右降到 30%(这个应该是 HttpRouter 的功劳)
- 内存使用基本没变化(HttpRouter 确实零分配,但你的业务代码不一定)
关键是要用对 HttpRouter 的特性,比如尽量用 3 参数 handler,避免频繁的 context 查找。
什么时候别用 HttpRouter
虽然我爱死 HttpRouter,但这些情况下别用:
要复杂中间件链的话用 Chi 或 Gin 吧。路由规则复杂,需要正则表达式匹配的只能用 Gorilla Mux。团队习惯 Express.js 风格的话 Gin 更合适。快速原型阶段,需要很多内置功能的用 Echo 或 Gin 能快点上线。
HttpRouter 适合知道自己要什么,追求性能和简洁的开发者。如果你需要更多中间件参考,可以看看 Go 中间件创建指南、最小化 CORS 实现 和 安全后端构建指南。