A multipart request is a multipart request body, which is usually used for uploading files. Because the request body is large, it is not suitable to construct a complete request body (such as bytes.buffer) in memory.

In this case, consider using a Pipe, which returns a Writer and a Reader. The Pipe stream, as the name suggests, reads at one end and writes at the other. Disk files are read and written to the network without being cached in memory. Perfect for this scenario.

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

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(a){

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

	/ / pipe flow
	r, w := io.Pipe()
	defer r.Close()

	// Create multipart and specify writer
	formWriter := multipart.NewWriter(w)
	go func(a) {
		defer w.Close()
		var writer io.Writer

		// Quickly build normal form items, key/value are strings
		formWriter.WriteField("lang"."PHP is the best language in the universe")

		// Build a normal form entry to write data to Writer
		writer, _ = formWriter.CreateFormField("lang")
		writer.Write([]byte("Java is the best language in the world"))

		// Construct the file form item, specify the form name and file name, write data to Writer, default ContentType is application/octet-stream
		writer, _ = formWriter.CreateFormFile("file"."app.json")
		jsonVal, _ := json.Marshal(map[string] string {"name": "KevinBlandy"})
		writer.Write(jsonVal)

		// Custom part form item, you can add custom header
		header := textproto.MIMEHeader{}
		header.Set("Content-Disposition".`form-data; name="file"; filename="app1.json"`)		// Customize the form field name and file name, which are required
		header.Set("Content-Type".`application/octet-stream`)									// Specify ContentType, which is required
		writer, _ = formWriter.CreatePart(header)
		writer.Write([]byte("foo"))

		// To finish writing, call the close method
		formWriter.Close()
	}()

	// Create an HTTP client
	client := http.Client{}
	// Create a request specifying the body reader
	req, _ := http.NewRequest(http.MethodPost, "http://127.0.0.1/upload", r)
	req.Header.Set("Content-Type", formWriter.FormDataContentType()) // ContentType needs to be set correctly

	// Execute the request to get the response
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	// Get the response
	data, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(data))

	// Stop the server
	cancel()

	// Wait for exit
	<- ch
}
func server(ctx context.Context, ch chan <- struct{}){
	router := gin.Default()
	router.POST("/upload".func(ctx *gin.Context) {
		form, _ := ctx.MultipartForm()
		fmt.Println("Common form entry -------------------")
		for key, value := range form.Value {
			fmt.Printf("name=%s, value=%s\n", key, strings.Join(value, ","))
		}
		fmt.Println("File form entry -------------------")
		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,
	}

	// Start the service
	go func(a) {
		iferr := 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("Server stopped...")
				ch <- struct{} {}return}}}}Copy the code

Log output

[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) Common form entry ------------------- ------------------- 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 = ". Json "] the content-type: application/octet stream] - [GIN] 2021/01/17-14:00:33 | | 200 | 0 s 127.0.0.1 | POST "/upload" success 2021/01/17 14:00:34 Server stopped...Copy the code