最简 HTTP 服务启动需调用 http.ListenAndServe(":8080", nil),但须确保路由注册在启动前、显式检查返回 error、注意路径匹配规则及 request.Body 只可读一次。

如何用 http.ListenAndServe 启动一个基础 HTTP 服务
最简启动方式就是调用 http.ListenAndServe,它默认使用 http.DefaultServeMux 路由器。但要注意:端口被占用、监听地址未指定或 TLS 配置缺失时会直接 panic 或静默失败。
- 监听所有 IPv4/IPv6 地址用
":8080",只监听本地用"127.0.0.1:8080" - 返回值是
error,必须显式检查,否则服务挂了也无提示 - 如果想复用已有
net.Listener(比如加 SO_REUSEPORT),改用http.Serve -
http.ListenAndServe不支持 HTTPS,要上 TLS 必须用http.ListenAndServeTLS并提供证书路径
为什么 http.HandleFunc 注册的路由不生效
常见原因是注册时机错误——在 http.ListenAndServe 启动之后才调用 http.HandleFunc,此时 mux 已锁定,新注册无效;或者用了自定义 http.ServeMux 却忘了传给 http.Server。
- 所有
http.HandleFunc/http.Handle必须在ListenAndServe前执行 - 若用自定义 mux(如
myMux := http.NewServeMux()),需通过&http.Server{Handler: myMux}显式绑定 - 注意路径匹配规则:
"/api/"会匹配/api/users,但"/api"不会(末尾斜杠影响子路径匹配) - 函数签名必须是
func(http.ResponseWriter, *http.Request),多一个参数或少一个都会编译失败
如何安全读取 request.Body 并避免重复读取
request.Body 是 io.ReadCloser,只能读一次。直接 io.ReadAll(r.Body) 后再调用 r.ParseForm() 会报错 invalid memory address or nil pointer dereference,因为底层 buffer 已耗尽。
- 需要多次读取时,先
body, _ := io.ReadAll(r.Body),再用bytes.NewReader(body)构造新 reader - 解析表单前确保没提前读过 body,否则
r.FormValue返回空字符串 - JSON 解析推荐直接用
json.NewDecoder(r.Body).Decode(&v),它内部按需读取,不缓存全文 - 记得在读完后调用
r.Body.Close(),尤其在中间件中,否则连接可能无法复用
如何在中间件中修改响应状态码和 Header
Go 的 http.ResponseWriter 是接口,标准实现(如 responseWriter)允许在写入 body 前任意修改 status 和 header,但一旦调用 WriteHeader 或 Write,header 就被冻结。
立即学习“go语言免费学习笔记(深入)”;
- 中间件应包装原始
http.ResponseWriter,拦截WriteHeader和Write调用,才能捕获最终状态码 - 修改 header 必须在第一次
Write前,否则会被忽略(net/http不报错也不警告) - 不要在中间件里直接
w.WriteHeader(500)后就 return,下游 handler 可能仍会写 body,造成 HTTP 协议错误 - 常用做法是用
ResponseWriter包装器(如responseWriter类型)记录 status,并在 defer 中统一处理日志或 CORS 头
真正麻烦的不是写逻辑,而是 body 读取时机、header 冻结边界、以及 mux 注册顺序这些隐式契约——它们不会报错,只会让行为和预期差一点,而这点偏差往往要到压测或上线后才暴露。










