Sunday, May 5, 2024

Sending a Multipart Request


Multipart request is usually used by browsers to upload files to the server. Additional parameters can be also specified as part of the body. In this post we show an example of building a multipart request.


The following is an example to build a multipart HTTP request:


const Boundary = "MyBoundary"
const fileName = "file"

func CreateFormRequest(
parameters map[string]string,
fileData string,
) string {
var stringBuilder strings.Builder
for key, value := range parameters {
stringBuilder.WriteString("--" + Boundary + "\n")
line := fmt.Sprintf(`Content-Disposition: form-data; name="%v"`, key)
stringBuilder.WriteString(line + "\n\n")
stringBuilder.WriteString(value)
stringBuilder.WriteString("\n")

}
stringBuilder.WriteString("--" + Boundary + "\n")
line := fmt.Sprintf(`Content-Disposition: form-data; name="%v"; filename="%v"`, fileName, fileName)
stringBuilder.WriteString(line + "\n")
stringBuilder.WriteString("Content-Type: text/plain\n")
stringBuilder.WriteString("\n")
stringBuilder.WriteString(fileData)
stringBuilder.WriteString("\n")
stringBuilder.WriteString("--" + Boundary + "--\n")

return stringBuilder.String()
}


Note the HTTP request should specify the multipart boundary string in the content type header:


headers := map[string]string{
"Content-Type": "multipart/form-data; boundary=" + Boundary,
}


On the server side we will use a struct to read the multipart content:

type FormContent struct {
File *multipart.FileHeader `form:"file"`
Values map[string]string
}


The server will read the multipart using a dedicated bind function:


func NewBindFile(originalBinder echo.Binder) echo.Binder {
return BindFunc(func(i interface{}, ctx echo.Context) error {
contentType := ctx.Request().Header.Get(echo.HeaderContentType)
if !strings.HasPrefix(contentType, echo.MIMEApplicationForm) && !strings.HasPrefix(contentType, echo.MIMEMultipartForm) {
return originalBinder.Bind(i, ctx)
}

formContent, ok := i.(*web.FormContent)
if !ok {
return fmt.Errorf("fail casting to form content")
}

form, err := ctx.MultipartForm()
if err != nil {
return err
}

formContent.File = form.File["file"][0]
formContent.Values = make(map[string]string)
for key, value := range form.Value {
formContent.Values[key] = value[0]
}

return nil
})
}


The related request handler can get the file data using:

fileData := web.ReadFormFile(formContent.File)
func ReadFormFile(fileHeader *multipart.FileHeader) []byte {
if fileHeader == nil {
return nil
}
file, err := fileHeader.Open()
kiterr.RaiseIfError(err)
body, err := io.ReadAll(file)
kiterr.RaiseIfError(err)
return body
}







No comments:

Post a Comment