http.listenandserve端口被占会返回错误但不自动退出,必须检查error;servemux仅前缀匹配;解析请求体需手动调用parseform或读body;并发下共享状态需同步机制。

用 http.ListenAndServe 启动最简服务,但端口被占会静默失败
Go 的 HTTP 服务器默认不报错退出,http.ListenAndServe 在端口已被占用时返回 http: Server closed 或更常见的 listen tcp :8080: bind: address already in use,但如果你没检查返回值,程序看似“跑起来了”,实则根本没监听。
- 务必检查
http.ListenAndServe返回的 error:if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) // 不要忽略 } - 开发时建议加超时控制和优雅关闭,避免 SIGKILL 强杀导致连接中断;生产环境别裸用
nilhandler,至少配个http.DefaultServeMux或自定义http.ServeMux -
:8080这种写法依赖系统解析,若需绑定到特定 IP(如只内网访问),显式写成127.0.0.1:8080,避免 IPv6 双栈行为差异
路由注册用 http.HandleFunc 最快上手,但路径匹配不支持通配符
Go 标准库的 http.ServeMux 是前缀匹配,不是正则或 REST 风格匹配。比如注册了 /api,它会同时响应 /api/users、/api/foo/bar,甚至 /apixxx —— 因为它只看「开头是否一致」。
- 简单场景直接用:
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) }) - 需要精确路径(如
/users/123)或动态段,别硬套http.ServeMux,改用第三方路由器(如gorilla/mux、chi),或者手动解析r.URL.Path - 注意 trailing slash:注册
/api/和/api是两个不同路由,后者无法匹配/api/xxx
处理请求体必须手动调用 r.ParseForm 或 r.Body,否则拿不到 POST 数据
Go 不会自动解析表单或 JSON。你看到 r.FormValue("key") 返回空,大概率是因为忘了调用 r.ParseForm()(适用于 application/x-www-form-urlencoded 和 multipart/form-data),或没读 r.Body(适用于 application/json)。
- 表单数据:
err := r.ParseForm() if err != nil { http.Error(w, "bad request", http.StatusBadRequest) return } name := r.FormValue("name") - JSON 数据:
var data struct{ Name string } err := json.NewDecoder(r.Body).Decode(&data) if err != nil { http.Error(w, "invalid json", http.StatusBadRequest) return } -
r.Body只能读一次,且不会自动关闭;如果中间件或日志想看 body 内容,得用io.TeeReader或提前复制,否则后续 handler 读不到
并发安全不是默认选项:全局变量、缓存、日志都得自己加锁或换结构
Go HTTP server 天然并发,每个请求在独立 goroutine 执行,但标准库没帮你管共享状态。往一个全局 map 里塞数据、用 log.Printf 写文件、甚至用 time.Now() 做唯一 ID,都可能在高并发下出问题。
- 计数器或缓存用
sync.Map替代原生map,或用sync.RWMutex包一层 - 日志别直接写文件,用
log.SetOutput接os.Stderr或结构化日志库(如zap),避免多 goroutine 写同一文件句柄冲突 - 别在 handler 里启 goroutine 后就不管——如果父请求结束,子 goroutine 可能还在跑,造成资源泄漏;用
context.WithTimeout控制生命周期
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











