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
相关产品推荐
相关产品推荐

