关于golang:Go的Pipe应用场景往服务器提交multipart请求

9次阅读

共计 3405 个字符,预计需要花费 9 分钟才能阅读完成。

multipart 申请是多部件申请体,个别来多用于上传文件等场景,因为文件上传,申请领会比拟大,就不适宜在内存中构建残缺的申请体(例如应用bytes.Buffer)。

这种状况就能够思考应用 Pipe,它会返回一个Writer 和一个Reader,管道流,顾名思义,一头读,一头写。读取磁盘文件,写入网络,并不会缓存在内存中。非常适合这种场景。

func Pipe() (*PipeReader, *PipeWriter) {
    p := &pipe{wrCh: make(chan []byte),
        rdCh: make(chan int),
        done: make(chan struct{}),
    }
    return &PipeReader{p}, &PipeWriter{p}
}

Demo

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "github.com/gin-gonic/gin"
    "io"
    "io/ioutil"
    "log"
    "mime/multipart"
    "net/http"
    "net/textproto"
    "strings"
    "time"
)

func main(){

    // Http 服务
    ctx, cancel := context.WithCancel(context.Background())
    ch := make(chan struct{})
    go server(ctx, ch)

    // 管道流
    r, w := io.Pipe()
    defer r.Close()

    // 创立 multipart,指定 writer
    formWriter := multipart.NewWriter(w)
    go func() {defer w.Close()
        var writer io.Writer

        // 疾速构建一般表单项,key/value 都是字符串
        formWriter.WriteField("lang", "PHP 是宇宙最好的语言")

        // 构建一般的表单项,通过 Writer 写入数据
        writer, _ = formWriter.CreateFormField("lang")
        writer.Write([]byte("Java 是世界上最好的语言"))

        // 构建文件表单项,指定表单名称,以及文件名称,通过 Writer 写入数据,默认的 ContentType 是 application/octet-stream
        writer, _ = formWriter.CreateFormFile("file", "app.json")
        jsonVal, _ := json.Marshal(map[string] string {"name": "KevinBlandy"})
        writer.Write(jsonVal)

        // 自定义 part 表单项,能够增加自定义的 header
        header := textproto.MIMEHeader{}
        header.Set("Content-Disposition", `form-data; name="file"; filename="app1.json"`)        // 自定表单字段名称,文件名称,这是必须的
        header.Set("Content-Type", `application/octet-stream`)                                    // 指定 ContentType,这是必须的
        writer, _ = formWriter.CreatePart(header)
        writer.Write([]byte("foo"))

        // 实现写入,须要调用 close 办法
        formWriter.Close()}()

    // 创立 http 客户端
    client := http.Client{}
    // 创立 request 申请,指定 body reader
    req, _ := http.NewRequest(http.MethodPost, "http://127.0.0.1/upload", r)
    req.Header.Set("Content-Type", formWriter.FormDataContentType()) // 须要正确的设置 ContentType

    // 执行申请获取响应
    resp, _ := client.Do(req)
    defer resp.Body.Close()

    // 获取响应
    data, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(data))

    // 进行服务器
    cancel()

    // 期待退出
    <- ch
}
func server(ctx context.Context, ch chan <- struct{}){router := gin.Default()
    router.POST("/upload", func(ctx *gin.Context) {form, _ := ctx.MultipartForm()
        fmt.Println("一般表单项 -------------------")
        for key, value := range form.Value {fmt.Printf("name=%s, value=%s\n", key, strings.Join(value, ","))
        }
        fmt.Println("文件表单项 -------------------")
        for key, value := range form.File {
            for _, file := range value {fmt.Printf("name=%s, size=%d, fileName=%s, headers=%v\n", key, file.Size, file.Filename, file.Header)
            }
        }
        ctx.Writer.Header().Set("Content-Type", "text/plan")
        ctx.Writer.WriteString("success")
    })
    server := http.Server{
        Addr: ":80",
        Handler: router,
    }

    // 启动服务
    go func() {if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {log.Panic(err)
        }
    }()

    for {
        select {case <- ctx.Done():{ctx, _ := context.WithTimeout(context.Background(), time.Second * 2)
                server.Shutdown(ctx)
                log.Println("服务器进行...")
                ch <- struct{}{}
                return
            }
        }
    }
}

日志输入

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:    export GIN_MODE=release
 - using code:    gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /upload                   --> main.server.func1 (3 handlers)
一般表单项 -------------------
name=lang, value=PHP 是宇宙最好的语言,Java 是世界上最好的语言
文件表单项 -------------------
name=file, size=22, fileName=app.json, headers=map[Content-Disposition:[form-data; name="file"; filename="app.json"] Content-Type:[application/octet-stream]]
name=file, size=3, fileName=app1.json, headers=map[Content-Disposition:[form-data; name="file"; filename="app1.json"] Content-Type:[application/octet-stream]]
[GIN] 2021/01/17 - 14:00:33 | 200 |            0s |       127.0.0.1 | POST     "/upload"
success
2021/01/17 14:00:34 服务器进行...
正文完
 0