golang 上传文件(包括 gin 实现)

golang web服务有时候需要提供上传文件的接口,以下就是具体示例。为了示例简单(吐槽下 golang 的错误处理), 忽略了所有的错误处理。本文会用两种方式(标准库和gin)详细讲解 golang 实现文件上传的实现。

gin是一个用 golang 实现的优秀 web 服务框架

上传文件

标准包实现

package main

import (
	"io"
	"log"
	"net/http"
	"os"
)

var (
  // 文件 key
	uploadFileKey = "upload-key"
)

func main() {
	http.HandleFunc("/upload", uploadHandler)
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatalf("error to start http server:%s", err.Error())
	}
}

func uploadHandler(w http.ResponseWriter, r *http.Request) {
	// 接受文件
	file, header, err := r.FormFile(uploadFileKey)
	if err != nil {
		// ignore the error handler
	}
	log.Printf("selected file name is %s", header.Filename)
	// 将文件拷贝到指定路径下,或者其他文件操作
	dst, err := os.Create(header.Filename)
	if err != nil {
		// ignore
	}
	_, err = io.Copy(dst, file)
	if err != nil {
		// ignore
	}
}

Gin 实现

package main

import (
	"github.com/gin-gonic/gin"
)

var (
	uploadFileKey = "upload-key"
)

func main() {
	r := gin.Default()
	r.POST("/upload", uploadHandler)
	r.Run()
}

func uploadHandler(c *gin.Context) {
	header, err := c.FormFile(uploadFileKey)
	if err != nil {
		//ignore
	}
	dst := header.Filename
  // gin 简单做了封装,拷贝了文件流
	if err := c.SaveUploadedFile(header, dst); err != nil {
		// ignore
	}
}

SaveUploadedFile 实现如下:

// SaveUploadedFile uploads the form file to specific dst.
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error {
	src, err := file.Open()
	if err != nil {
		return err
	}
	defer src.Close()
	//创建 dst 文件
	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()
// 拷贝文件
	_, err = io.Copy(out, src)
	return err
}

上传文件和参数

有时候除了选中文件外,我们还需要向服务端传递一些参数。在 http multipart 请求格式中。值是以键值对的形式传递的。

标准包实现

可以使用 Request下的 MultipartForm

文件参数

files :=r.MultipartForm.File

files 是 map[string][]*FileHeader 类型, 可以传递多个文件

值参数

values := r.MultipartForm.Value

values 是 map[string][]string 类型, 可以允许有多个同名的变量,每个同名的变量值在一个切片中

Gin 实现

ginContext中包含了*http.Request,因此完全可以用与标准库相同的方式处理。同时 gin对参数的获取也做了一层分装。
假设需要传递 name, age 以及 key 为 upload-key 的文件。首先定义结构体:

type newForm struct {
	UploadKey *multipart.FileHeader `form:"upload-key"`
	Name      string                `form:"name"`
	Age       int                   `form:"age"`
}

在获取 form 的时候直接使用 gin分装的方法ShouldBind获取到所有参数

	var form newForm
	if err := c.ShouldBind(&form); err != nil{
		//ignore
	}

同时newForm中可以添加binding tag 进行参数校验。具体可以参考 gin 的官方文档 gin 请求参数校验

Multipart client实现

multipart form 的 client 写法示例

package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
)

var (
	uploadFileKey = "upload-key"
)

func main() {
	url := "http://127.0.0.1:8080/upload"
	path := "/tmp/test.txt"
	params := map[string]string{
		"key1": "val1",
	}
	req, err := NewFileUploadRequest(url, path, params)
	if err != nil {
		fmt.Printf("error to new upload file request:%s\n", err.Error())
		return
	}
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("error to request to the server:%s\n", err.Error())
		return
	}
	body := &bytes.Buffer{}
	_, err = body.ReadFrom(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	fmt.Println(body)
}

// NewFileUploadRequest ...
func NewFileUploadRequest(url, path string, params map[string]string) (*http.Request, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	body := &bytes.Buffer{}
	// 文件写入 body
	writer := multipart.NewWriter(body)

	part, err := writer.CreateFormFile(uploadFileKey, filepath.Base(path))
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(part, file)
	// 其他参数列表写入 body
	for k, v := range params {
		if err := writer.WriteField(k, v); err != nil {
			return nil, err
		}
	}
	if err := writer.Close(); err != nil {
		return nil, err
	}

	req, err := http.NewRequest(http.MethodPost, url, body)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Content-Type", writer.FormDataContentType())
	return req, err
}

总结

Gin 的实现方式更加简单高效,框架为我们封装了很多细节。使用起来更加方便。标准库实现相对而言也算简单。但是需要我们自己组织和校验请求参数。

参考

gin
https://gist.github.com/mattetti/5914158

posted @ 2019-08-29 11:23  jssyjam  阅读(16844)  评论(0编辑  收藏  举报