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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM