
本文详解 go 的 net/http 包中 `req.body` 为空的原因,并提供针对 get/post 请求分别获取参数的规范做法:get 应使用 `req.url.query()` 或 `req.parseform()` + `req.form`,而 post 的 json 数据才应通过 `json.decoder(req.body)` 解析。
在 Go 的 net/http 中,*http.Request.Body 仅承载 HTTP 请求体(request body)中的原始字节流,例如 POST/PUT 请求中以 Content-Type: application/json 发送的 JSON 负载。它完全不包含 URL 查询参数(query string)——这些参数位于请求行中(如 /step?steps=1&direction=1),由 req.URL.Query() 直接解析,与 Body 无关。
你遇到的 req.Body 为 nil 或解码失败,根本原因在于:
✅ 你的 AJAX 请求使用的是 method: "get",浏览器将所有 data 自动序列化为 URL 查询参数(见示例 URL),此时 HTTP 请求体为空,req.Body 自然不可读或已关闭;
❌ 调用 json.NewDecoder(req.Body).Decode(&v) 对空 Body 解码必然失败(io.EOF 或 invalid character),且 log.Println(v) 输出
✅ 正确处理方式(按请求方法区分)
1. 对于 GET 请求(推荐:直接解析 URL 查询参数)
func stepHandler(res http.ResponseWriter, req *http.Request) {
// 方法一:直接获取 URL 查询参数(最轻量、无需 ParseForm)
steps := req.URL.Query().Get("steps") // "1"
direction := req.URL.Query().Get("direction") // "1"
cellsJSON := req.URL.Query().Get("cells") // "[{\"row\":11,...}]"
// 方法二:ParseForm 后统一访问(对 GET/POST 均有效,但 GET 下等价于 Query)
err := req.ParseForm()
if err != nil {
http.Error(res, "Invalid form data", http.StatusBadRequest)
return
}
steps = req.FormValue("steps") // 等价于 req.URL.Query().Get("steps")
cellsJSON = req.FormValue("cells")
// 解析 JSON 字符串字段(注意:cells 是 URL 编码后的字符串,需二次 JSON 解码)
var cells []map[string]int
if err := json.Unmarshal([]byte(cellsJSON), &cells); err != nil {
http.Error(res, "Invalid cells JSON", http.StatusBadRequest)
return
}
log.Printf("Steps: %s, Direction: %s, Cells: %+v", steps, direction, cells)
}2. 对于 POST 请求(发送 JSON Body)
若需真正使用 req.Body,客户端应改为 POST 并设置 Content-Type: application/json:
// 客户端 AJAX(POST + JSON Body)
$.ajax({
url: "/step",
method: "POST",
contentType: "application/json",
data: JSON.stringify({
steps: parseInt($("#step-size").val()),
direction: $("#step-forward").prop("checked") ? 1 : -1,
cells: painted // 直接传数组,无需 stringify
}),
success: function(data) { /* ... */ }
});服务端则可安全读取 Body:
func stepHandler(res http.ResponseWriter, req *http.Request) {
if req.Method != "POST" {
http.Error(res, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var payload struct {
Steps int `json:"steps"`
Direction int `json:"direction"`
Cells []struct { Row, Column int } `json:"cells"`
}
// Body 可被 json.Decoder 读取(自动处理 Content-Length 和关闭)
if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
http.Error(res, "Invalid JSON", http.StatusBadRequest)
return
}
defer req.Body.Close() // 显式关闭(虽 Decoder 通常已关闭,但属最佳实践)
log.Printf("Payload: %+v", payload)
}⚠️ 关键注意事项
- req.Body 只能读取一次:读取后会被耗尽,后续再调用 req.ParseForm() 或 json.Decoder 将失败(除非提前 ioutil.ReadAll 并重置 req.Body)。
- req.ParseForm() 对 GET 是冗余的:它内部会检查 req.Method,若为 GET 则直接解析 req.URL.RawQuery,无需额外开销。
- 永远校验 err:json.Decode 失败时 v 不会被修改,直接打印 v 会输出零值(如 nil, 0, ""),易造成误判。
- 生产环境务必设置超时与限长:避免恶意大请求体阻塞服务,建议配合 http.MaxBytesReader 使用。
总结:理解 HTTP 协议分层是关键——URL 参数属于请求行,Body 属于消息体。Go 的 net/http 严格遵循此规范,选择正确的 API(URL.Query() vs Body)才能高效、健壮地处理各类请求。










