Itermultipart simplifies reading of multipart messages by providing an iterator interface to multipart.Reader and creating multipart messages from iterators.
- Functions to convert
multipart.Readerorhttp.Requestto an iterator suitable forfor rangeloop - Generate multipart messages from iterators. Message generator implements
io.Readerinterface, so it's suitable for http request body directly. - Convenient parts constructor with fluent interface
- Zero-dependency
Reading parts via wrapped multipart.Reader is an efficient way to work with multipart messages.
This also allows to be more flexible with limitation of message/part size, parts count and memory usage.
func SimpleFileSaveHandler(w http.ResponseWriter, *r http.Request) {
for part, err := range itermultipart.PartsFromRequest(r) {
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Save part to file
if part.FileName() == "" {
http.Error(w, "File name is required", http.StatusBadRequest)
return
}
file, err := os.Create(part.FileName())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, err = io.Copy(file, part.Content)
file.Close()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}Also, you can feed standard multipart.Reader to itermultipart.PartsFromReader function.
Traditional way with multipart.Writer:
func CreateMultipartRequest() (*http.Request, error) {
pr, pw := io.Pipe()
mw := multipart.NewWriter(pw)
go func() {
// Write parts
part, err := mw.CreateFormFile("file", "file.txt")
if err != nil {
pw.CloseWithError(err)
return
}
_, err = part.Write([]byte("Hello, world!"))
if err != nil {
pw.CloseWithError(err)
return
}
pw.CloseWithError(mw.Close())
}()
req, err := http.NewRequest("POST", "http://example.com/upload", pr)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", mw.FormDataContentType())
return req, nil
}As you may notice it requires extra goroutine and io.Pipe to work with multipart.Writer.
This makes impossible to use some optimizations provided by io.ReaderFrom or io.WriterTo interfaces i.e. direct file-socket or socket-socket transfer.
However, with you can use itermultipart.Source to create multipart message from iterator:
func CreateMultipartRequest() (*http.Request, error) {
src := itermultipart.NewSource(itermultipart.PartSeq(
itermultipart.NewPart().
SetFormName("file").
SetFileName("file.txt").
SetContentString("Hello, world!"),
))
req, err := http.NewRequest("POST", "http://example.com/upload", src)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", src.FormDataContentType())
return req, nil
}As you can see its much simpler and doesn't require extra goroutine and io.Pipe.
PartSeq here is a simple helper that transforms list of parts to iterator.
itermultipart.NewPart provides a fluent interface to create parts:
part := itermultipart.NewPart().
SetFormName("file").
SetFileName("file.txt").
SetContentString("Hello, world!")Content may be set via methods:
SetContent- set content directly fromio.ReaderSetContentString- use provided string as contentSetContentBytes- use provided byte slice as content
To define a content-type you can use:
SetContentType- set content type directlySetContentTypeByExtension- set content type by file extension ifSetFileNamecalled beforeDetectContentType- peeks first 512 bytes from content and tries to recognize content type
Even if you don't want to use itermultipart.Source to create a multipart message, itermultipart.NewPart still may be useful.
It's method AddToWriter allows to add part to standard multipart.Writer:
func WritePartToMultipartWriter(w *multipart.Writer) error {
return itermultipart.NewPart().
SetFormName("file").
SetFileName("file.txt").
SetContentString("Hello, world!").
AddToWriter(w)
}