Best practices for writing nested MIME multipart bodies

372 views
Skip to first unread message

Jens Alfke

unread,
Jul 19, 2013, 7:58:06 PM7/19/13
to golang-nuts
I need to write a nested MIME multipart body — i.e. a multipart that contains parts that are themselves multipart. I can’t find a way to do this without creating a secondary data buffer, which increases the memory usage.

Let’s say I already have a multipart.Writer for the outer body, and I now want to write a new Part that’s itself multipart. The two things I need to call are
* writer.CreatePart, to start a new part in the outer body;
* multipart.NewWriter, to create the nested multipart body.
But there’s a chicken-and-egg problem with these: I need to give CreatePart the headers to include, including the all-important Content-Type, which contains a “separator” parameter derived from the new Writer. But to create the Writer I need an io.Writer for it to write into, which is what CreatePart returns. So each one of them needs a value provided by the other, and I can’t call either one first!

The workaround I’m using is to create a bytes.Buffer and tell NewWriter to write into that. Then after I’m done writing the nested body I get the byte-array from the Buffer, create the Part, and write the bytes into it.

This works, but ends up doing a lot of in-memory copying. I’m worried about that since this code is going to typically be running on file attachments that could be arbitrarily huge. Ideally I’d stream them straight from their origin to the HTTP response without creating in-memory copies. Is there any way around this?

—Jens

Tamás Gulácsi

unread,
Jul 20, 2013, 5:25:09 AM7/20/13
to golan...@googlegroups.com
You need to work leaf-first, and wrap the parts as you walk upwards in the tree.

Kyle Lemons

unread,
Jul 20, 2013, 6:19:03 PM7/20/13
to Jens Alfke, golang-nuts
I suspect you can create a proxy writer that has the behavior you want by just buffering at the beginning.

(yes, I know I didn't check errors, you should:) http://play.golang.org/p/E9RFUqmRFl

David DENG

unread,
Jul 20, 2013, 7:41:17 PM7/20/13
to golan...@googlegroups.com
You can generate the boundary by your own, then you get the header for CreatePart, call NewWriter, and set the boundary by calling multipart.Writer.SetBoundary.

This is the boundary generation code in the built-in lib:

func randomBoundary() string {
        var buf [30]byte
        _, err := io.ReadFull(rand.Reader, buf[:])
        if err != nil {
                panic(err)
        }
        return fmt.Sprintf("%x", buf[:])
}

David
Reply all
Reply to author
Forward
0 new messages