Go语言中如何不落地磁盘从multipart.Part获取multipart.File?
问题描述
之前在Go API中,通过r.FormFile("file")从请求体提取multipart.File,再传给MinIO客户端上传到S3,功能正常。现在添加额外表单数据后,再调用r.FormFile会触发“error getting form file”错误。改用MultipartReader读取表单数据后,拿到的multipart.Part不是multipart.File类型,希望在不写入磁盘的前提下,获取可直接传给MinIO的文件对象。
原正常代码
提取文件:
file, handler, err := r.FormFile("file")
上传至MinIO:
err = uploadToMinio(rs, file, fileSize, fileName, guid.String(), userId)
当前出错的代码
var err error start := time.Now() const maxUploadSize = 500 * 1024 * 1024 // 500 Mb var requiredByDate FileRequiredDateData mr, err := r.MultipartReader() if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } for { part, err := mr.NextPart() // 读取完毕,退出循环 if err == io.EOF { break } // 读取出错 if err != nil { log.Println("multipart reader other error") http.Error(w, err.Error(), http.StatusInternalServerError) return } log.Println(part.FormName()) if part.FormName() == "data" { log.Println("multipart reader found multipart form name data") decoder := json.NewDecoder(part) err = decoder.Decode(&requiredByDate) if err != nil { log.Println("error in decoding request body data") log.Println(err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } } if part.FormName() == "file" { // 此处报错:error getting form file file, handler, err := r.FormFile("file") if err != nil { log.Println("error getting form file") log.Println(err.Error()) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusInternalServerError) return } defer file.Close() // ... 省略其他代码 err = uploadToMinio(rs, file, fileSize, fileName, guid.String(), userId) if err != nil { log.Println(err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } } }
解决方案
核心问题:请求体只能被读取一次,调用MultipartReader后已经消费了整个请求流,再调用r.FormFile会因为没有剩余数据报错。
直接使用multipart.Part上传
multipart.Part实现了io.Reader和io.Closer接口,完全可以直接替代multipart.File传给MinIO上传函数(只要uploadToMinio的第二个参数接受io.Reader或io.ReadCloser类型)。修改后的文件处理逻辑如下:
if part.FormName() == "file" { fileName := part.FileName() // 可选:从请求头获取文件大小,若Header中无此字段则需其他方式处理 // fileSize, _ := strconv.ParseInt(part.Header.Get("Content-Length"), 10, 64) // 直接将part作为数据源传入上传函数 err = uploadToMinio(rs, part, fileSize, fileName, guid.String(), userId) if err != nil { log.Println(err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } defer part.Close() }
若需严格匹配multipart.File类型
如果uploadToMinio强制要求multipart.File类型(该类型额外实现了Seek方法),可以封装multipart.Part:
// 封装multipart.Part为multipart.File type partFile struct { *multipart.Part } // 流式读取的Part不支持Seek,若上传函数不需要此方法可直接返回错误 func (pf partFile) Seek(offset int64, whence int) (int64, error) { return 0, errors.New("seek not supported") } // 使用示例: file := partFile{part} err = uploadToMinio(rs, file, fileSize, fileName, guid.String(), userId)
内容的提问来源于stack exchange,提问作者markhorrocks
相关产品推荐
相关产品推荐

