应检查 http.ListenAndServe 错误并用 log.Fatal(err),显式构造 http.Server 以配置超时、TLS等;注意 http.HandleFunc 与 http.Handle 类型差异;request.Body 只能读一次;务必设置 Read/Write/IdleTimeout 防连接泄漏。
http.ListenAndServe 启动最简服务,但别忽略错误处理直接调用 http.ListenAndServe(":8080", nil) 能跑起来,但一旦端口被占或网络异常,程序会静默 panic 或直接退出。Go 的 HTTP 服务器本身不自动重试、不自动日志、不自动超时控制。
实操建议:
log.Fatal(err),而不是忽略http.Server 结构体显式构造,便于后续配置 TLS、超时、连接池等":8080" 而非 "localhost:8080",后者会绑定到 IPv4 回环,可能无法从容器外访问http.HandleFunc 和 http.Handle 的区别:函数 vs 接口,选错会导致路由失效http.HandleFunc 注册的是函数(func(http.ResponseWriter, *http.Request)),而 http.Handle 要求传入实现了 http.Handler 接口的值(比如自定义 struct 或 http.ServeMux)。混用会导致编译失败或路由不生效。
常见错误现象:
http.Handle:编译报错 cannot use func(...) (type fun
c(...)) as type http.Handler
http.HandleFunc 注册了路径 "/api",但实际请求是 "/api/"(带尾斜杠)——默认 ServeMux 不自动重定向,需手动处理推荐做法:初期用 http.HandleFunc 快速验证;中后期迁移到自定义 http.ServeMux 或第三方路由器(如 chi),便于分组和中间件管理。
r.Body
*http.Request.Body 是一个 io.ReadCloser,只能读一次。多次调用 io.ReadAll(r.Body) 或 json.NewDecoder(r.Body).Decode(...) 会导致后续读取返回空或 EOF。
使用场景与对策:
io.ReadAll 读一次,再用 bytes.NewReader 构造新 reader 供后续使用r.Context() 或自定义 request wrapperr.ParseMultipartForm,它内部已处理 body 复用逻辑,无需额外缓存ReadTimeout、WriteTimeout 和 IdleTimeout
默认情况下,Go 的 http.Server 没有超时限制。客户端断连、慢攻击、长轮询未关连接,都会导致 goroutine 和文件描述符持续累积,最终 OOM 或 too many open files。
关键参数含义:
ReadTimeout:从连接建立到读完 request header 的最大时间(不含 body)WriteTimeout:从 request header 解析完成到 response 写完的总耗时上限IdleTimeout:keep-alive 连接空闲等待下一个 request 的最长时间(Go 1.8+ 推荐设为 30–60s)示例配置:
srv := &http.Server{
Addr: ":8080",
Handler: myRouter,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 30 * time.Second,
}注意:ReadTimeout 过短会影响大表单或上传;WriteTimeout 过短会中断正常业务逻辑,应按接口 SLA 设置,而非统一硬编码。
HTTP 服务器看似简单,真正难的是边界控制:连接生命周期、body 读取顺序、超时粒度、上下文传递。这些地方不显眼,但出问题时往往没有明确错误信息,只表现为延迟升高或连接泄漏。