Golang使用net/http处理HTTP请求

最常见原因是没启动服务器或 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() 才能读 POSTapplication/x-www-form-urlencodedmultipart/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、计数器)是共享的,没有自动同步机制。

  • 避免在 handler 中直接读写全局可变变量;如需共享状态,用 sync.Mutexsync.RWMutex 保护
  • 更推荐把状态封装进结构体,用 http.Handle 注册带状态的 handler 实例,而非 HandleFunc
  • 不要在 handler 里启 goroutine 后不等它结束就返回——响应已写出,但后台 goroutine 还在跑,可能访问已释放的局部变量
  • 日志打印也要注意:log.Printf 是线程安全的,但自定义 logger 若含缓存或写文件逻辑,仍需确认并发安全性
HTTP 处理器看似简单,但请求生命周期、状态共享、I/O 边界这些地方一不留神就埋坑。尤其是 req.Body 的一次性读取、ParseForm 的隐式副作用、以及没加锁的共享变量,最容易在线上突然暴露。