使用 http.ListenAndServe 启动服务器时需手动 recover panic,优先用自定义 ServeMux 替代 DefaultServeMux,读取请求体前设 context 超时,上传限流用 MaxBytesReader,返回 JSON 必须显式设置 Content-Type。

用 http.ListenAndServe 启动最简服务器,但默认不处理 panic
直接调用 http.ListenAndServe(":8080", nil) 就能跑起一个 HTTP 服务,nil 表示使用默认的 http.DefaultServeMux。但要注意:如果 handler 函数里发生 panic,整个服务器会崩溃退出,没有任何恢复机制。生产环境必须自己包装 handler,用 recover() 捕获异常。
注册路由别只靠 http.HandleFunc,优先用自定义 http.ServeMux
http.HandleFunc 看似方便,但它往全局的 DefaultServeMux 注册,多个包之间容易冲突,也不利于测试和隔离。更可控的方式是显式创建 mux:
mux := http.NewServeMux()
mux.HandleFunc("/api/users", usersHandler)
mux.HandleFunc("/health", healthHandler)
http.ListenAndServe(":8080", mux)
- 每个服务实例可拥有独立 mux,避免隐式共享
- 便于在测试中传入 mock mux 或替换 handler
- 支持嵌套路由前缀:
http.StripPrefix("/v1", mux)
处理请求体前务必设超时,否则 req.Body.Read 可能永久阻塞
HTTP 请求体(尤其是 POST/PUT)读取没有内置超时,客户端若只发 header 不发 body,req.Body.Read 会一直等下去。正确做法是在读取前设置上下文超时:
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
r = r.WithContext(ctx)
body, err := io.ReadAll(r.Body)
if err != nil {
// 可能是 context.DeadlineExceeded
}
- 不要依赖
http.Server.ReadTimeout(已弃用),它只管连接建立阶段 -
http.Server.ReadHeaderTimeout和http.Server.IdleTimeout才是现代替代项 - 上传大文件时,建议配合
http.MaxBytesReader限流
返回 JSON 别忘了设 Content-Type: application/json
很多人只写 json.NewEncoder(w).Encode(data),但浏览器或前端 SDK 可能因缺失 Content-Type 头而解析失败。必须手动设置:
立即学习“go语言免费学习笔记(深入)”;
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
- 如果用了
http.Error,它不会自动设 Content-Type,错误响应也要自己补 - 对非 UTF-8 字符(比如 GBK),需显式声明 charset;纯 ASCII 场景可省略 charset 部分
- 中间件里统一设置 header 更安全,避免每个 handler 都重复写
net/http 接口看似简单,但超时控制、panic 恢复、header 设置这些点,一旦漏掉就容易在线上出隐蔽问题。尤其在微服务间调用或公网暴露时,每个 handler 的健壮性都得单独验证。










