Go 的 net/http 需显式用 http.Server 控制生命周期,设 Read/WriteTimeout、结构化日志、端口检查;原生路由弱,推荐 gorilla/mux 或 chi;body 只能读一次,须限大小、用 json.Decoder;handler 应通过闭包注入依赖并利用 r.Context() 实现超时与可观测性。

Go 的 net/http 包足够写生产级 Web 服务,但直接裸用容易掉进路由、中间件、错误处理、超时控制的坑里。
如何启动一个带超时和日志的基础 HTTP 服务
很多人用 http.ListenAndServe 一跑就完事,结果请求卡住、连接堆积、panic 没捕获,线上直接雪崩。必须用 http.Server 显式控制生命周期。
- 设置
ReadTimeout和WriteTimeout(至少 30s),避免慢客户端拖垮服务 - 用
log.Printf或结构化日志库记录访问与错误,别只靠fmt.Println - 监听前检查端口是否被占用,否则
ListenAndServe会 panic - 示例关键片段:
srv := &http.Server{ Addr: ":8080", Handler: mux, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, } log.Printf("server starting on %s", srv.Addr) if err := srv.ListenAndServe(); err != http.ErrServerClosed { log.Fatal(err) }
为什么不能直接用 http.HandleFunc 处理复杂路由
http.HandleFunc 只支持简单前缀匹配,不支持路径参数(如 /user/:id)、方法限制(仅 GET/POST 区分)、子路由嵌套。一旦路由变多,代码立刻失控。
- 原生
http.ServeMux不支持通配符或正则,/api/v1/users/123和/api/v1/users/count必须手动字符串切分 - 没有中间件机制,鉴权、CORS、body 解析等逻辑只能重复写或用闭包包装 handler
- 推荐方案:用轻量第三方如
gorilla/mux或chi,它们完全兼容http.Handler接口,不绑架你整个生态 - 若坚持不用第三方,可用
http.StripPrefix+ 自定义http.Handler实现简易分组,但 path 解析仍得自己做
如何安全读取和解析 request body
常见错误包括:多次调用 r.Body 导致 EOF、未限制 body 大小被恶意上传打满内存、JSON 解析 panic 未 recover。
立即学习“go语言免费学习笔记(深入)”;
- body 只能读一次,后续再读就是空;需要复用时用
io.ReadCloser包装成可重放的bytes.Reader - 务必在 handler 开头加
r.ParseForm()或r.ParseMultipartForm(32 并设上限(如 32MB) - JSON 解析必须用
json.NewDecoder(r.Body).Decode(&v)而非json.Unmarshal,避免把整个 body 加载进内存 - 对非 JSON 请求(如 form、raw text),记得调用
r.Body.Close()防止连接不释放
如何让 handler 支持 context 取消和依赖注入
原生 http.HandlerFunc 签名是 func(http.ResponseWriter, *http.Request),无法直接传入 context.Context 或 DB 实例。硬塞全局变量或闭包会破坏可测试性。
- 正确做法:用闭包封装依赖,返回标准 handler,例如
func(db *sql.DB) http.HandlerFunc - 利用
r.Context()获取请求上下文,它已自带 cancel 和 timeout,适合传给下游调用(如数据库查询、HTTP client) - 不要在 handler 内部用
context.Background(),那会丢失请求生命周期控制 - 若需跨 handler 共享数据(如用户 ID),用
context.WithValue,但 key 必须是自定义类型,避免字符串冲突
真正难的不是写 handler,而是让每个请求都有明确的超时边界、可观测的日志链路、可中断的下游调用、可验证的输入约束——这些细节没补全,net/http 再简洁也只是玩具。










