http.Post不支持multipart/form-data,无法上传文件;需用mime/multipart手动构造请求体,设置正确Content-Type和boundary,用CreateFormFile写入文件字段,必调writer.Close()。

go用http.Post上传文件会失败,为什么?
因为http.Post只支持发送纯文本或简单表单(application/x-www-form-urlencoded),不支持构建带文件的multipart/form-data请求体。直接传os.File或字节切片进去,服务端根本收不到文件字段。
正确做法是用mime/multipart手动构造请求体,再通过http.DefaultClient.Do发送。
- 必须设置
Content-Type: multipart/form-data; boundary=xxx,且boundary值要和实际body中的一致 - 文件字段需用
writer.CreateFormFile写入,不能用writer.WriteField - 非文件字段(如
user_id)可用writer.WriteField追加 - 务必调用
writer.Close(),否则结尾boundary不会写入,服务端解析失败
Go上传文件的最小可行代码示例
以下代码实现向https://httpbin.org/post上传一个本地文件,字段名为file:
package main import ( "bytes" "io" "mime/multipart" "net/http" "os" ) func main() { file, _ := os.Open("example.txt") defer file.Close() body := &bytes.Buffer{} writer := multipart.NewWriter(body) // 创建文件字段,注意字段名必须和服务端约定一致 part, _ := writer.CreateFormFile("file", "example.txt") io.copy(part, file) // 可选:添加其他表单字段 writer.WriteField("upload_type", "manual") writer.Close() // 关键:不调用则boundary缺失 req, _ := http.NewRequest("POST", "https://httpbin.org/post", body) req.Header.Set("Content-Type", writer.FormDataContentType()) client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() }
上传大文件时如何避免内存爆满?
bytes.Buffer会把整个请求体加载进内存,上传100MB文件就占100MB RAM。生产环境必须流式上传。
- 改用
io.Pipe创建管道,一边写入multipart.Writer,一边由HTTP client读取发送 - 打开文件后直接
io.Copy到part,不经过内存缓冲 - 注意
io.Pipe的写端出错时,读端会收到io.ErrClosedPipe,需合理处理错误传播 - 超时控制必须设在
http.Client上(Timeout或Transport级配置),不能只靠context.WithTimeout包住Do
常见错误信息与排查点
上传失败时,先看响应状态码和Body内容,而不是猜逻辑。典型线索包括:
-
400 Bad Request+"missing required field 'file'"→ 字段名拼错,或CreateFormFile第一个参数不对 -
413 Payload Too Large→ 服务端nginx/Cloudflare限制了请求体大小,需调大client_max_body_size或maxRequestBodySize -
500 internal Server Error+ 空Body →writer.Close()没调用,boundary缺失导致服务端解析panic - 响应里
files字段为空但form有数据 → 文件字段被当成普通表单字段写入(用了WriteField而非CreateFormFile)
边界值容易被忽略:空文件、文件名含中文或特殊字符、字段名和服务端文档不一致——这些都可能让multipart解析静默失败。