最常见原因是没启动服务器或 ListenAndServe 后程序退出;注册路由仅存入 DefaultServeMux,需调用 ListenAndServe 才生效,且须确保其为 main goroutine 最后调用。

为什么 http.HandleFunc 注册的路由不生效?
最常见原因是没启动服务器,或 http.ListenAndServe 调用后程序直接退出。注册路由只是往默认的 http.DefaultServeMux 里塞函数,真正监听和分发要靠 ListenAndServe 启动。
- 确保调用
http.ListenAndServe(":8080", nil),第二个参数为nil表示使用默认多路复用器 - 如果用了自定义
http.ServeMux,必须显式传入ListenAndServe第二个参数,不能传nil - 检查端口是否被占用,错误日志会输出
listen tcp :8080: bind: address already in use - Go 程序在
ListenAndServe后不会自动阻塞——如果它不是 main goroutine 的最后调用,后续代码执行完进程就退出了
如何读取 URL 查询参数和表单数据?
req.URL.Query() 拿查询参数(?name=alice&age=30),req.ParseForm() 才能读 POST 的 application/x-www-form-urlencoded 或 multipart/form-data 数据。两者互不影响,但顺序有讲究。
- 对 GET 请求,直接用
req.URL.Query().Get("key") - 对 POST 表单,先调
req.ParseForm()(否则req.Form是空的),再用req.FormValue("key") -
ParseForm()会自动识别Content-Type并解析;若请求体是 JSON,则不应调用它,而该用json.Decoder - 重复键(如
?tag=a&tag=b)用req.URL.Query()["tag"]获取切片,Get只返回第一个
怎么正确处理 JSON 请求和响应?
别手动拼接字符串或用 fmt.Fprintf 输出 JSON——容易出格式错误、缺少 Content-Type、不处理编码问题。
- 写响应:设头
w.Header().Set("Content-Type", "application/json; charset=utf-8"),再用json.NewEncoder(w).Encode(v) - 读请求:用
json.NewDecoder(req.Body).Decode(&v),别用io.ReadAll+json.Unmarshal,除非你明确需要原始字节 - 注意
req.Body只能读一次;如果之前调过ParseForm()或ParseMultipartForm(),Body可能已被消费,需提前保存或重置 - 错误时也应返回 JSON 响应(如
{"error": "invalid json"}),并设http.StatusBadRequest
func handleUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if r.Method != "POST" {
http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed)
return
}
var u struct{ Name string `json:"name"` }
if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
http.Error(w, `{"error":"invalid json"}`, http.StatusBadRequest)
return
}
json.NewEncoder(w).Encode(map[string]string{"msg": "ok", "name": u.Name})
}
为什么并发请求下状态变量会错乱?
Go 的 HTTP handler 函数每次请求都在独立 goroutine 中执行,但闭包捕获的外部变量(比如全局 map、计数器)是共享的,没有自动同步机制。
立即学习“go语言免费学习笔记(深入)”;
- 避免在 handler 中直接读写全局可变变量;如需共享状态,用
sync.Mutex或sync.RWMutex保护 - 更推荐把状态封装进结构体,用
http.Handle注册带状态的 handler 实例,而非HandleFunc - 不要在 handler 里启 goroutine 后不等它结束就返回——响应已写出,但后台 goroutine 还在跑,可能访问已释放的局部变量
- 日志打印也要注意:
log.Printf是线程安全的,但自定义 logger 若含缓存或写文件逻辑,仍需确认并发安全性
req.Body 的一次性读取、ParseForm 的隐式副作用、以及没加锁的共享变量,最容易在线上突然暴露。










