http.Post不支持multipart/form-data,无法上传文件;需用mime/multipart手动构造请求体,设置正确Content-Type和boundary,用CreateFormFile写入文件字段,必调writer.Close()。
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不会写入,服务端解析失败以下代码实现向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缺失导致服务端解析panicfiles字段为空但form有数据 → 文件字段被当成普通表单字段写入(用了WriteField而非CreateFormFile)边界值容易被忽略:空文件、文件名含中文或特殊字符、字段名和服务端文档不一致——这些都可能让multipart解析静默失败。