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

相关文章

类型转换 1、int转string 2、string转int 3、string转float ...
package main import s "strings" import...
类使用:实现一个people中有一个sayhi的方法调用功能,代码如...
html代码: beego代码:
1、读取文件信息: 2、读取文件夹下的所有文件: 3、写入文件...
配置环境:Windows7+推荐IDE:LiteIDEGO下载地址:http:...