mime/multipart.Writer: Support closing a Part immediately

161 views
Skip to first unread message

Ohad Lutzky

unread,
May 7, 2021, 2:11:59 PM5/7/21
to golang-nuts
Hi all, I'm looking at multipart/x-mixed-replace-based image streaming (mjpeg-style, but with PNG in my case), and would like to allow code to push a new image immediately. This look roughly like this (error handling elided for brevity):

m := multipart.NewWriter()
defer m.Close()
w.Header().Set("Content-Type", "multipart/x-mixed-replace; boundary="+m.Boundary())
h := textproto.MIMEHeader{}
h.Set("Content-Type", "image/png")
for {
buf := <-c // Channel with PNG data
h.Set("Content-Length", fmt.Sprint(len(buf)))
mw, _ := m.CreatePart(h)
mw.Write(buf)
w.(http.Flusher).Flush()
}

Writing PNGs to channel c will indeed display the image to the screen, but one image later. Specifically, it's the m.CreatePart call that completes the last part by emitting the final --BOUNDARY\r\n. Unfortunately, I can't call it immediately after mw.Write, because I don't necessarily know the Content-Length for the next image. A workaround that does work is this:

mw, _ := m.CreatePart(h)
mw.Write(buf)
mw, _ = m.CreatePart(h) // Yes, again
mw.Write(buf)
w.(http.Flusher).Flush()

Notably, calling m.Close() wouldn't be appropriate here. While CreatePart write \r\n--BOUNDARY\r\n, Close would write \r\n--BOUNDARY--\r\n. Apparently that extra -- is enough to make the browser disregard everything that follows.

While this can be worked around by not using the mime/multipart library, I think it's a reasonable API change (but an API change nonetheless) to add a FlushLastPart method to Writer. WDYT?

Damien Neil

unread,
May 11, 2021, 1:06:15 PM5/11/21
to golang-nuts
A multipart.Writer exists in one of three states currently:

1. CreatePart has not been called.
2. CreatePart has been called at least once, but Close has not been called yet.
3. Close has been called.

This proposal would introduce a fourth state:

4. FlushLastPart has been called. CreatePart or Close have not been called since then.

This needs to be a distinct state, since FlushLastPart (perhaps better called ClosePart?) would write the boundary separator terminating the previous part. If we call Close in this state, we need to write "--\r\n", not "\r\n--BOUNDARY--\r\n".

Given how simple multipart.Writer is, I wonder if it isn't better to just write the multipart message manually in your case.
Reply all
Reply to author
Forward
0 new messages