直接读取大文件会导致内存爆炸,因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,不会把整个源读进内存。这个大小足够平衡系统调用开销和内存占用,一般无需修改。
赣极购物商城网店建站软件系统
大小仅1兆左右 ,足够轻便的商城系统; 易部署,上传空间即可用,安全,稳定; 容易操作,登陆后台就可设置装饰网站; 并且使用异步技术处理网站数据,表现更具美感。 前台呈现页面,兼容主流浏览器,DIV+CSS页面设计; 如果您有一定的网页设计基础,还可以进行简易的样式修改,二次开发, 发布新样式,调整网站结构,只需修改css目录中的css.css文件即可。 商城网站完全独立,网站源码随时可供您下载
下载
但如果你要写入网络 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 已覆盖绝大多数异常。









