如何使用Golang处理大文件上传_Golang io Reader大文件上传示例

11次阅读

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

如何使用 Golang 处理大文件上传_Golang io Reader 大文件上传示例

http.Request.Body 直接读取大文件会内存爆炸

Go 的 http.Request.Body 是一个 io.ReadCloser,但默认不会流式处理上传内容。如果直接用 io.ReadAll(r.Body)bytes.Buffer.ReadFrom(r.Body),整个文件会一次性加载进内存——上传 2GB 文件,进程 RSS 就涨 2GB,OOM 杀死是常态。

正确做法是边读边处理,不缓存全部内容。关键点在于:别调用任何会把整个 body 吃进去的函数。

  • 禁用:io.ReadAllio.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,反复 ReadWrite,不会把整个源读进内存。这个大小足够平衡系统调用开销和内存占用,一般无需修改。

但如果你要写入网络 socket 或低延迟设备,可能需要调小;写入机械硬盘且文件极大,可略增大(如 1MB)减少 write() 次数——不过实测提升有限,优先保证稳定性。

  • 自定义 buffer 示例:io.CopyBuffer(dst, src, make([]byte, 1
  • 不要用 bufio.Reader 包裹 r.Body 再传给 io.Copy:多余且可能破坏 multipart boundary 对齐
  • 上传中断时,io.Copy 返回的 n, errerr 可能是 net.ErrClosedio.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_sizeclient_body_timeout,否则请求根本到不了 Go 进程。

真正难处理的是“上传中客户端断网”,此时 Go 不会立刻感知,得靠 TCP keepalive 或应用层心跳——但大多数文件上传场景下,依赖 ReadTimeout 已覆盖绝大多数异常。

text=ZqhQzanResources