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