GoのPipeはシーンを適用し、multipartリクエストをサーバに送信する

3778 ワード

multipart要求はマルチコンポーネント要求体であり、一般的にファイルのアップロードなどのシーンに多く用いられるが、ファイルのアップロードのため、要求体験が大きいため、メモリに完全な要求体を構築するのに適していない(例えばbytes.Bufferを使用する).
この場合、Pipeを使用することを考慮することができます.WriterReaderを返します.パイプフローは、名前の通り、頭で読んで、頭で書きます.ディスクファイルを読み込み、ネットワークに書き込み、メモリにキャッシュしません.このシーンにぴったりです.
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()

    //     
    

ログ出力
[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      ...