直接读取大文件会导致内存爆炸,因 http.Request.Body 默认不流式处理;应避免 io.ReadAll 等全量加载函数,改用 io.Copy 到磁盘或按 chunk 读取。

用 http.Request.Body 直接读取大文件会内存爆炸
Go 的 http.Request.Body 是一个 io.ReadCloser,但默认不会流式处理上传内容。如果直接用 io.ReadAll(r.Body) 或 bytes.Buffer.ReadFrom(r.Body),整个文件会一次性加载进内存——上传 2GB 文件,进程 RSS 就涨 2GB,OOM 杀死是常态。
正确做法是边读边处理,不缓存全部内容。关键点在于:别调用任何会把整个 body 吃进去的函数。
- 禁用:
io.ReadAll、io.Copy(ioutil.Discard, ……)(旧版)、json.NewDecoder(r.Body).Decode(……)(除非确认 body 很小) - 允许:
io.Copy到磁盘文件、multipart.Reader解析表单、按 chunk 调用Read() - 注意:
r.ParseMultipartForm(32 会把文件头和小文件放进内存,MaxMemory参数只是阈值,不是上限
用 multipart.Reader 安全解析含文件的表单
浏览器上传通常走 multipart/form-data,必须用 Go 标准库的 multipart 包解析,不能手动字符串切割或正则匹配 boundary。
核心逻辑是:先调用 r.MultipartReader() 获取 *multipart.Reader,再循环 NextPart(),对每个 part 检查 Header.Get("Content-Disposition") 是否含 filename=,再流式保存。
立即学习 “go 语言免费学习笔记(深入)”;
func uploadHandler(w http.ResponseWriter, r *http.Request) {if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } mr, err := r.MultipartReader() if err != nil { http.Error(w, "Invalid multipart request", http.StatusBadRequest) return } for {part, err := mr.NextPart() if err == io.EOF {break} if err != nil {http.Error(w, "Parse part failed", http.StatusBadRequest) return } filename := part.FileName() if filename != ""{ out, err := os.Create("/tmp/upload/"+ filename) if err != nil {http.Error(w,"Create file failed", http.StatusInternalServerError) return } _, err = io.Copy(out, part) // 流式写入,无内存压力 out.Close() if err != nil { http.Error(w,"Save file failed", http.StatusInternalServerError) return } } } w.WriteHeader(http.StatusOK)
}
io.Copy 的底层行为和 buffer 大小影响
io.Copy 默认使用 io.DefaultCopyBuffer(目前是 32KB),它在内部分配一个临时 buffer,反复 Read → Write,不会把整个源读进内存。这个大小足够平衡系统调用开销和内存占用,一般无需修改。
但如果你要写入网络 socket 或低延迟设备,可能需要调小;写入机械硬盘且文件极大,可略增大(如 1MB)减少 write() 次数——不过实测提升有限,优先保证稳定性。
- 自定义 buffer 示例:
io.CopyBuffer(dst, src, make([]byte, 1 - 不要用
bufio.Reader包裹r.Body再传给io.Copy:多余且可能破坏 multipart boundary 对齐 - 上传中断时,
io.Copy返回的n, err中err可能是net.ErrClosed或io.ErrUnexpectedEOF,需区分处理
客户端上传超时与服务端 http.Server.ReadTimeout 配合
大文件上传慢,容易触发默认 30 秒的 ReadTimeout,导致连接被服务器强制关闭,客户端收到 408 Request Timeout 或直接断连。
必须显式延长读取超时,且建议比客户端最大预期上传时间多留 10–20 秒缓冲:
srv := &http.Server{Addr: ":8080", Handler: mux, ReadTimeout: 30 * time.Minute, WriteTimeout: 30 * time.Minute, IdleTimeout: 30 * time.Minute,} srv.ListenAndServe()
同时,Nginx / Caddy 等反代层也要同步调大 client_max_body_size、client_body_timeout,否则请求根本到不了 Go 进程。
真正难处理的是“上传中客户端断网”,此时 Go 不会立刻感知,得靠 TCP keepalive 或应用层心跳——但大多数文件上传场景下,依赖 ReadTimeout 已覆盖绝大多数异常。






























