一、表单上传
一种写法,(更多的内存分配):
func upload(ctx *gin.Context) {
//标准库会将文件内容读入buffer
file, err := ctx.FormFile("file")
if err != nil {
fmt.Println("获取数据失败")
ctx.JSON(http.StatusOK, gin.H{
"code": 1,
"message": "获取数据失败",
})
} else {
fmt.Println("接收的数据", file.Filename, file.Size)
//保存上传文件
filePath := filepath.Join("/sata", file.Filename)
ctx.SaveUploadedFile(file, filePath)
ctx.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
})
}
}
另一种写法,(更少的内存分配):
func uploadBig(ctx *gin.Context) {
mr, err := ctx.Request.MultipartReader()
if err != nil {
fmt.Sprintln(err)
fmt.Fprintln(ctx.Writer, err)
return
}
for {
part, err := mr.NextPart()
//必须判断
if err == io.EOF {
break
}
name := part.FormName()
if name == "" {
continue
}
filename := part.FileName()
filePath := filepath.Join("/sata", filename)
out, err := os.Create(filePath)
if err != nil {
fmt.Sprintln(err)
fmt.Fprintln(ctx.Writer, err)
return
}
defer out.Close()
_, err = io.Copy(out, part)
ctx.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
})
}
}
go 客户端代码:
package main
import (
"bytes"
"io"
"mime/multipart"
"net/http"
"path/filepath"
"github.com/cheggaaa/pb"
)
type zero struct{}
func (z zero) Read(d []byte) (int, error) {
return len(d), nil
}
var Zero io.Reader = &zero{}
// 分片大小设置为8M
const chunkSize = 8 * 1024 * 1024
var reader io.Reader
func init() {
bar := pb.StartNew(0)
bar.ShowSpeed = true
bar.SetUnits(pb.U_BYTES)
barReader := bar.NewProxyReader(Zero)
reader = barReader
}
func upload1() {
//准备一个buffer
b := &bytes.Buffer{}
//构建一个multipart writer
multipartWriter := multipart.NewWriter(b)
_, err := multipartWriter.CreateFormFile("file", filepath.Base("file.bin"))
if err != nil {
panic(err)
}
_, err = io.CopyN(b, reader, chunkSize)
if err != nil {
panic(err)
}
err = multipartWriter.Close()
if err != nil {
panic(err)
}
//构建一个multipart writer end
req, err := http.NewRequest("POST", "http://192.168.4.161:8080/upload", b)
if err != nil {
panic(err)
}
//必须设置请求头
req.Header.Add("Content-Type", multipartWriter.FormDataContentType())
//fmt.Println(multipartWriter.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
}
func upload2() {
b := &bytes.Buffer{}
_, err := io.CopyN(b, reader, chunkSize)
if err != nil {
panic(err)
}
req, err := http.NewRequest("POST", "http://192.168.4.161:8080/upload", b)
if err != nil {
panic(err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
}
func main() {
for {
upload1()
//upload2()
}
}
表单上传的性能损耗:
二、文件直接放入body
服务端代码:不必担心什么边界问题,因为标准库已经做好了limitreader 限制,就和json传输一样。
经过测试:二进制文件、图片、视频均可以通过此方式传输。
如果需要分片传输,则将控制参数(chunk、chunksize)放入header、或放入body,
可以使用4字节+控制数据+文件数据的格式,其中4字节表示控制数据的长度。
func uploadx(ctx *gin.Context) {
f, _ := os.Create("/sata/file.bin")
defer f.Close()
io.Copy(f, ctx.Request.Body)
ctx.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
})
}