17370845950

如何在Golang中上传文件到服务器_Golang net/http multipart方法实践
必须用 multipart.Writer 构造请求体:创建 bytes.Buffer,用 multipart.NewWriter 写字段和文件,调用 w.Close(),设置 req.Header.Set("Content-Type", w.FormDataContentType())。

http.Post 上传文件会失败,必须用 multipart.Writer

直接调用 http.Post 并传入 bytes.NewReader 是行不通的——它只会把原始字节当纯文本发过去,服务器端收不到 multipart/form-data 的边界(boundary)和字段结构。正确做法是手动构造 multipart 请求体。

关键点在于:先创建 bytes.Buffer,再用 multipart.NewWriter 写入字段和文件数据,最后把 Buffer.Bytes() 作为请求体发送,并设置正确的 Content-Type 头(含 boundary)。

  • multipart.Writer 会自动生成并管理 boundary,不能硬编码
  • 写完所有字段后必须调用 w.Close(),否则 boundary 结尾缺失,服务端解析失败
  • req.Header.Set("Content-Type", w.FormDataContentType()) 是必须的,不能手写 multipart/form-data; boundary=xxx

上传单个文件时如何设置文件名和字段名

服务端(比如 Gin 或标准 http.Request.ParseMultipartForm)依赖 multipart.Writer.WriteFieldmultipart.Writer.CreateFormFile 的参数来识别字段名和文件元信息。字段名(file)、文件名(report.pdf)都由你控制,不是自动推断的。

见错误是把文件内容当成字符串传给 WriteField,结果服务端收到的是明文内容而非文件流。

  • 普通表单字段用 w.WriteField("username", "alice")
  • 文件字段必须用 w.CreateFormFile("file", "report.pdf") 获取 io.Writer,再把文件内容写进去
  • 字段名(如 "file")要和服务端 r.FormFile("file") 中的键完全一致

上传多个文件或混合字段的完整示例

package main

import (
	"bytes"
	"io"
	"mime/multipart"
	"net/http"
)

func uploadFiles() error {
	buf := &bytes.Buffer{}
	w := multipart.NewWriter(buf)

	// 写普通字段
	if err := w.WriteField("project_id", "123"); err != nil {
		return err
	}

	// 写第一个文件
	fw1, err := w.CreateFormFile("files", "a.txt")
	if err != nil {
		return err
	}
	io.WriteString(fw1, "content of a.txt")

	// 写第二个文件
	fw2, err := w.CreateFormFile("files", "b.json")
	if err != nil {
		return err
	}
	io.WriteString(fw2, `{"id": 42}`)

	// 关键:关闭 writer,生成结尾 boundary
	w.Close()

	req, err := http.NewRequest("POST", "http://localhost:8080/upload", buf)
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", w.FormDataContentType())

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return nil
}

注意:CreateFormFile 的第一个参数是字段名(可复用,如都用 "files" 实现多文件),第二个是客户端看到的文件名(影响 Header.Get("Filename"))。

服务端解析时容易忽略的边界条件

Go 标准库要求在读取 multipart 前调用 r.ParseMultipartForm,否则 r.MultipartFormnil。而且这个方法会一次性把全部文件读进内存或临时磁盘,不设限可能被恶意大文件打爆内存。

  • 务必设置合理上限:r.ParseMultipartForm(32 (32MB)
  • 如果只处理小文件且想避免磁盘临时文件,可设 MaxMemory,超过才落盘
  • r.MultipartForm.File["files"] 返回的是 []*multipart.FileHeader,不是单个值
  • 读取文件内容前需用 header.Open() 获得 io.ReadCloser,别漏掉 Close()

boundary 不匹配、没调 w.Close()、服务端没调 ParseMultipartForm——这三个问题占了调试失败的八成以上。