You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言表单文件上传:如何避免大文件占用服务器资源?

Go 文件上传常见问题解答

1. 保存文件前获取文件大小

你当前的代码忽略了r.FormFile返回的第二个参数*multipart.FileHeader,这个结构体的Size字段就是文件的实际大小。修改代码如下:

func index(w http.ResponseWriter, r *http.Request) {
    // 建议设置合理的maxMemory值,而非0(0会使用Go默认的10MB)
    err := r.ParseMultipartForm(10 << 20) 
    if err != nil {
        log.Printf("解析表单失败: %v", err)
        http.Error(w, "服务器错误", http.StatusInternalServerError)
        return
    }

    file, header, err := r.FormFile("file")
    if err != nil {
        log.Printf("获取文件失败: %v", err)
        http.Error(w, "获取文件失败", http.StatusBadRequest)
        return
    }
    defer file.Close()

    // 获取文件大小
    fileSize := header.Size
    log.Printf("文件大小: %d 字节", fileSize)
}

注意:不要忽略任何错误,否则遇到异常情况无法排查问题。

2. 关于临时文件占满磁盘的理解与解决

你的理解部分正确,但有细节需要补充:

  • 调用r.ParseMultipartForm(maxMemory)时,整个请求体都会被解析,所有文件部分中,总大小不超过maxMemory的部分会存在内存里,超过的部分会写入系统临时目录的临时文件中。
  • 这些临时文件不会自动删除,需要在请求处理完成后调用r.MultipartForm.RemoveAll()手动清理,否则大量大文件上传会持续占用磁盘空间,严重时可能导致服务器崩溃。

避免磁盘被占满的方案

最可靠的方式是流式处理上传文件,不要一次性解析整个请求体,而是用r.MultipartReader()逐块读取,边读取边检查大小,一旦超过限制就中断请求,不会把完整文件写到磁盘或内存:

func index(w http.ResponseWriter, r *http.Request) {
    const maxUploadSize = 10 << 20 // 限制为10MB
    // 初步校验Content-Length(仅当客户端发送该头时有效)
    if r.ContentLength > maxUploadSize {
        http.Error(w, "文件过大", http.StatusRequestEntityTooLarge)
        return
    }

    reader, err := r.MultipartReader()
    if err != nil {
        log.Printf("获取MultipartReader失败: %v", err)
        http.Error(w, "服务器错误", http.StatusInternalServerError)
        return
    }

    var totalBytes int64
    for {
        part, err := reader.NextPart()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Printf("读取表单部分失败: %v", err)
            http.Error(w, "服务器错误", http.StatusInternalServerError)
            return
        }
        defer part.Close()

        // 跳过非文件类型的表单字段
        if part.FileName() == "" {
            continue
        }

        // 流式读取文件内容,同时累计大小
        buf := make([]byte, 4096)
        for {
            n, err := part.Read(buf)
            if err == io.EOF {
                break
            }
            if err != nil {
                log.Printf("读取文件内容失败: %v", err)
                http.Error(w, "服务器错误", http.StatusInternalServerError)
                return
            }
            totalBytes += int64(n)
            if totalBytes > maxUploadSize {
                http.Error(w, "文件超过大小限制", http.StatusRequestEntityTooLarge)
                return
            }
            // 此处可将buf[:n]写入目标存储,或做其他业务处理
        }
        log.Printf("文件大小: %d 字节", totalBytes)
    }
}

3. 不接收完整文件的前提下处理大小

  • 客户端发送Content-Length头时:可以先读取r.ContentLength做初步校验,但该值可能被客户端伪造,仅作为第一层过滤。
  • 分块上传或无Content-Length的情况:无法提前获取完整文件大小,只能通过流式读取累计字节数,一旦超过限制就立即中断请求,避免继续接收数据。

这种流式处理的方式,既可以避免大文件占满磁盘,也能在读取过程中严格控制文件大小,是处理大文件上传的最优方案。

内容的提问来源于stack exchange,提问作者DFG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 07:15:32