最简API服务需用http.Server配置超时与优雅关闭,路由用gorilla/mux支持路径变量和方法限定,JSON解析用json.NewDecoder并检查错误,响应前设Content-Type与状态码,错误统一用http.Error返回。

用 net/http 写最简 API 服务
Go 自带 net/http 就能跑起来一个可用的 API,不需要框架。关键不是“怎么搭”,而是“怎么避免写完就出错”。
常见错误:直接用 http.ListenAndServe 启动但没处理 panic、没设超时、没关掉 idle 连接,上线后遇到长连接堆积或 goroutine 泄漏。
- 必须设置
http.Server实例,而不是裸调http.ListenAndServe - 读写超时至少设 30 秒:
ReadTimeout: 30 * time.Second、WriteTimeout: 30 * time.Second - 加
IdleTimeout防止 keep-alive 耗尽连接数,比如IdleTimeout: 60 * time.Second - 启动后用
server.Shutdown处理优雅退出(SIGTERM 时)
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Fatal(srv.ListenAndServe())路由怎么分发才不踩坑
别用 http.HandleFunc 堆一堆全局函数——难测、难维护、中间件没法插。用 http.ServeMux 或更推荐 gorilla/mux(轻量、稳定、支持 path 变量)。
典型翻车点:路径末尾斜杠不一致导致 404;用 url.Path 手动解析参数而不是靠路由提取;没做 method 检查直接进 handler。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
router.HandleFunc("/users/{id}", getUserHandler).Methods("GET")—— 显式限定 method - 变量名必须是合法标识符,
{user_id}可以,{user-id}会静默失败 - 如果用标准库
http.ServeMux,它不支持 path 变量,只能靠strings.Split(r.URL.Path, "/"),容易越界 panic - 所有 handler 函数签名统一为
func(http.ResponseWriter, *http.Request),别漏掉*http.Request
怎么安全读取 JSON 请求体
别直接 json.Unmarshal(r.Body, &v) —— body 只能读一次,中间件(如日志、鉴权)可能已经读过,再读就是空。
也不要用 ioutil.ReadAll(Go 1.16+ 已弃用),更别用 bytes.Buffer 手动缓存,容易内存暴涨。
- 在中间件里用
io.ReadCloser包一层可复用 body:r.Body = io.NopCloser(bytes.NewBuffer(buf)) - 用
json.NewDecoder(r.Body).Decode(&v),它比Unmarshal更省内存,且自动处理流式读取 - 务必检查解码错误:
if err != nil { http.Error(w, "invalid json", http.StatusBadRequest); return } - 结构体字段加
json:"name,omitempty"控制序列化行为,避免零值污染
返回 JSON 时要注意 Content-Type 和状态码
很多新手只写 w.WriteHeader(200) 然后 json.Marshal,结果前端收不到 Content-Type: application/json,或者状态码被忽略(因为没在 WriteHeader 后立刻写 body)。
还有人用 log.Fatal 在 handler 里崩溃整个服务——一个请求出错,全站挂掉。
- 必须先设 header:
w.Header().Set("Content-Type", "application/json; charset=utf-8") - 状态码要早于
json.NewEncoder(w).Encode(v)调用,否则会被覆盖成 200 - 用
http.Error(w, msg, code)返回错误,它会自动设 header 和 status - 别在 handler 里 recover 全局 panic,应该用中间件统一捕获并返回 500
复杂点在于:body 流式写入时出错(比如 client 断连),Encode 会返回 write: broken pipe,这个 error 必须忽略,否则日志刷爆——Go 的 http.ResponseWriter 不提供“是否已写出”的判断,只能靠 http.CloseNotifier(已废弃)或第三方包兜底,实际项目中多数选择静默丢弃这类 error。

















