http.post不可靠,易致400/415错误:它不自动设content-type,不序列化json,不url编码表单,且不复用连接;应改用postform(表单)、newrequest+json.marshal(json)或multipart.writer(文件上传)。

别用 http.Post 发真实业务请求——它连 Content-Type 都不能可靠设置,400/415 错误基本是它干的。
http.Post 为什么总返回 415 或 400
它只做三件事:发 POST、传 body、设一个固定 Content-Type(仅当显式传入时才设,否则为空),其余全不管。常见翻车点:
- 你传
"application/json",但没序列化结构体,body 是原始字符串,服务端解析 JSON 失败 → 400 - 你传
"application/x-www-form-urlencoded",但 body 是手拼的"name=张三&city=北京",没 URL 编码 → 中文变乱码,服务端收不到字段 → 400 - 你根本没传 Content-Type 字符串,
http.Post就不设 header → 服务端看到空 Content-Type,直接拒收 → 415 - 它每次新建
http.Client,不复用连接,压测时 TCP 端口耗尽、DNS 重查、TLS 握手全重复 → 延迟飙升
提交表单数据必须用 url.Values + PostForm
这是唯一既安全又省心的方式,等价于 curl -d "a=1&b=2",自动编码、自动设头、无脑可用:
-
url.Values是map[string][]string,.Set()和.Add()都会做标准 URL 编码(空格→+,中文→%E4%BD%A0) -
http.PostForm内部调用url.Values.Encode()并强制设Content-Type: application/x-www-form-urlencoded - 不支持加自定义 header(如
Authorization),真有这需求就别用它,切到http.NewRequest - 示例:
data := url.Values{} data.Set("username", "admin") data.Set("token", "x y+z") // 自动编码成 "x+y%2Bz" resp, err := http.PostForm("https://api.example.com/login", data)
发 JSON 必须手动构造 http.Request
几乎所有 REST API 都要求 Content-Type: application/json + 合法 JSON 字节流,http.Post 做不到这点:
- 必须用
json.Marshal序列化,别用fmt.Sprintf拼字符串——少个引号或错个转义就 400 -
req.Header.Set("Content-Type", "application/json")必须在client.Do(req)之前调用 - body 要传
io.Reader,用bytes.NewBuffer(jsonBytes)或strings.NewReader(string(jsonBytes)),别传[]byte直接过去 - 记得设超时:
&http.Client{Timeout: 8 * time.Second},否则默认无超时,卡死不报错 - 示例:
payload := map[string]string{"id": "abc", "note": "ok"} jsonBytes, _ := json.Marshal(payload) req, _ := http.NewRequest("POST", "https://api.example.com/webhook", bytes.NewBuffer(jsonBytes)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Api-Key", "secret-123") client := &http.Client{Timeout: 8 * time.Second} resp, err := client.Do(req)
文件上传必须用 multipart.Writer
别手写 boundary、别用 strings.NewReader 拼 multipart body——换行符、编码、boundary 位置错一丁点,服务端就解析失败:
- 用
mime/multipart包的Writer,它自动管理 boundary、header、分隔符和结尾 - 必须调
w.Close(),否则结尾 boundary 缺失,服务端收不到完整体 - 文件内容用
part.Write(fileBytes),别整个文件读进内存再塞进去(大文件会 OOM) - 如果要传额外字段(如
user_id),用w.WriteField("user_id", "123"),不是塞进 form 表单里
真正难的不是写对一次请求,而是保证所有边界场景(超时、重试、连接复用、错误传播、body 关闭)不出错。越早放弃 http.Post,越早少 debug 半天。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











