go net/http 开箱即用但易出错:需显式设addr、超时和panic恢复;servemux不支持路径参数,路由顺序关键;返回json须设content-type并正确设状态码。

Go 的 net/http 包开箱即用,不用装第三方框架就能跑起一个生产可用的 HTTP 服务器——但直接写容易忽略路由匹配顺序、中间件链断裂、panic 导致服务中断等问题。
用 http.ListenAndServe 启动最简服务
这是起点,也是最容易出错的地方:端口被占、没处理 panic、没设超时。默认监听 localhost:8080,但线上必须绑定 0.0.0.0:8080 才能被外部访问。
常见错误:listen tcp :8080: bind: address already in use —— 先用 lsof -i :8080(macOS/Linux)或 netstat -ano | findstr :8080(Windows)查进程并 kill。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 始终显式传入
http.Server{Addr: ":8080", Handler: mux},别只用http.ListenAndServe(":8080", nil) - 加
http.TimeoutHandler或在Server上设ReadTimeout/WriteTimeout - 启动前检查端口可用性(可选,用
net.Listen("tcp", ":8080")尝试并立即Close())
用 http.ServeMux 做基础路由分发
http.ServeMux 是 Go 标准库默认的多路复用器,但它不支持路径参数(如 /user/123)、不支持通配符、匹配是前缀式而非全匹配——/api 会意外匹配 /api/users 和 /apidoc。
使用场景:静态路由足够、团队小、无复杂 REST 风格需求。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 注册顺序很重要:长路径放前面,比如先
mux.HandleFunc("/api/users/", ...),再mux.HandleFunc("/api/", ...) - 用
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))托管静态文件 - 避免把
"/"放在最后做兜底,它会吞掉所有未匹配请求;改用http.NotFoundHandler()显式返回 404
手动捕获 panic 防止服务崩溃
HTTP handler 里一旦发生未捕获 panic(比如空指针解引用、数组越界),整个 goroutine 会终止,但服务器不会退出——只是该请求卡死或返回空响应,日志里也看不到错误。
这是 Go Web 服务最隐蔽的稳定性陷阱。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 写一个通用 wrapper:
func recoverPanic(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { log.Printf("PANIC: %v\n%v", err, debug.Stack()) http.Error(w, "Internal Server Error", http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) } - 把它套在
Handler链最外层:http.ListenAndServe(":8080", recoverPanic(mux)) - 不要在 handler 内部用
recover(),它只对当前 goroutine 有效;而每个请求由独立 goroutine 处理
返回 JSON 时注意 Content-Type 和错误处理
很多人直接 w.Write([]byte(`{"ok":true}`)),结果前端收不到 JSON,因为没设 Content-Type: application/json;更糟的是,错误路径返回 200 状态码却塞了个 error message。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 统一用封装函数:
func writeJSON(w http.ResponseWriter, status int, v interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(v) } - 成功返回
writeJSON(w, http.StatusOK, data),失败返回writeJSON(w, http.StatusBadRequest, errMap) - 避免在
json.Encoder.Encode()后继续写响应体——它可能已触发 header 发送,再调w.WriteHeader()无效
真正难的不是启动服务器,而是让每一次 http.HandlerFunc 都能安全、可观测、可调试。标准库足够轻量,但也意味着所有边界情况都得自己填。










