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

Go服务器如何渐进式接收大文件而非全量加载至内存?

Go 实现流式大文件上传至S3方案

核心思路

要实现不加载全量文件到内存的上传,关键是利用流式IO直接将HTTP请求体转发到S3,同时通过请求头获取文件总大小做前置校验。

1. 获取并校验文件总大小

客户端上传时必须携带Content-Length请求头,服务端先读取这个值判断是否超过限制:

const maxFileSize = 10 * 1024 * 1024 * 1024 // 10GB

func uploadHandler(w http.ResponseWriter, r *http.Request) {
    // 检查请求方法
    if r.Method != http.MethodPost {
        http.Error(w, "only POST allowed", http.StatusMethodNotAllowed)
        return
    }

    // 获取文件总大小
    fileSize := r.ContentLength
    if fileSize == -1 {
        // 无Content-Length,无法校验总大小,直接拒绝
        http.Error(w, "Content-Length header required", http.StatusBadRequest)
        return
    }
    if fileSize > maxFileSize {
        http.Error(w, "file exceeds size limit", http.StatusRequestEntityTooLarge)
        return
    }

    // 后续上传逻辑...
}

2. 流式上传至S3

使用AWS SDK for Go v2的s3.PutObject接口,直接将r.Body作为上传体传入——SDK会自动以流式方式将数据分块发送到S3,不会把整个文件加载到内存:

import (
    "context"
    "net/http"
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

func initS3Client() (*s3.Client, error) {
    cfg, err := config.LoadDefaultConfig(context.TODO())
    if err != nil {
        return nil, err
    }
    return s3.NewFromConfig(cfg), nil
}

func uploadHandler(w http.ResponseWriter, r *http.Request) {
    // 前面的校验逻辑...

    s3Client, err := initS3Client()
    if err != nil {
        http.Error(w, "failed to init S3 client", http.StatusInternalServerError)
        return
    }

    bucket := "your-bucket-name"
    key := "uploaded-files/" + r.Header.Get("X-File-Name") // 从自定义头取文件名,或生成唯一ID

    _, err = s3Client.PutObject(context.TODO(), &s3.PutObjectInput{
        Bucket:        aws.String(bucket),
        Key:           aws.String(key),
        Body:          r.Body, // 直接传入请求体,流式上传
        ContentLength: aws.Int64(fileSize),
    })

    if err != nil {
        http.Error(w, "failed to upload to S3", http.StatusInternalServerError)
        return
    }

    w.WriteHeader(http.StatusOK)
    w.Write([]byte("upload success"))
}

3. 大文件分块上传(可选)

如果文件超过S3单文件上传上限(5GB),可以用SDK的Uploader组件,它会自动拆分文件为多个分块(默认5MB),流式上传每个分块:

import "github.com/aws/aws-sdk-go-v2/feature/s3/manager"

func uploadHandler(w http.ResponseWriter, r *http.Request) {
    // 前面的校验逻辑...

    uploader := manager.NewUploader(s3Client)
    _, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{
        Bucket:        aws.String(bucket),
        Key:           aws.String(key),
        Body:          r.Body,
        ContentLength: aws.Int64(fileSize),
    })

    // 错误处理...
}

未消费字节的存储位置

当采用流式处理时,未被消费的文件字节并不会加载到应用层内存,而是存储在两个地方:

  • 内核的TCP接收缓冲区:客户端发送的字节先被内核接收,暂存在这里。
  • Go HTTP包的内部缓冲区:http包会用bufio做一层小缓冲(默认几KB),用于优化读取效率,但不会缓存整个文件。

只有当调用r.Body.Read()时,这些字节才会被读取到应用层的临时缓冲区(你自己定义的byte slice),处理完后就会被GC回收。如果上传中断或客户端断开连接,内核会自动清理TCP缓冲区里的未消费数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 16:45:16