Peter Bourgon Home About Talks Articles Blog

Multipart HTTP responses in Go

Sometimes I write HTTP servers that need to serve multiple values in response to a single request. If the values are small, one common way is to define an e.g. JSON object to wrap them.

type myResponse struct { Values []string `json:"values"` } func handle(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(myResponse{ Values: getValues(), }) }

But sometimes that’s not a great solution; for example, if the values are raw binary data (in Go, []byte) and you don’t want to go through a base64 conversion. In those cases, it may make sense to use a multipart response. For the record, this approach is adapted (reverse engineered, I guess) from the Riak KV API.

Here’s one way to set things up in the handler.

func handle(w http.ResponseWriter, r *http.Request) { mediatype, _, err := mime.ParseMediaType(r.Header.Get("Accept")) if err != nil { http.Error(w, err.Error(), http.StatusNotAcceptable) return } if mediatype != "multipart/form-data" { http.Error(w, "set Accept: multipart/form-data", http.StatusMultipleChoices) return } mw := multipart.NewWriter(w) w.Header().Set("Content-Type", mw.FormDataContentType()) for _, value := range getValues() { fw, err := mw.CreateFormField("value") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if _, err := fw.Write(value); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } if err := mw.Close(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } }

And here’s how to use it as a consumer, with error handling elided.

func main() { req, _ := http.NewRequest("GET", "http://localhost:8080/foo", nil) req.Header.Set("Accept", "multipart/form-data; charset=utf-8") resp, _ := http.DefaultClient.Do(req) _, params, _ := mime.ParseMediaType(resp.Header.Get("Content-Type")) mr := multipart.NewReader(resp.Body, params["boundary"]) for part, err := mr.NextPart(); err == nil; part, err = mr.NextPart() { value, _ := ioutil.ReadAll(part) log.Printf("Value: %s", value) } }

Hopefully that helps someone. Is there a better way to do it? Tweet at me and I’ll update the code.

Related work: if you’re interested in streaming potentially unlimited data from an HTTP server to a client, and don’t want to deal with Websockets (I don’t blame you) consider using eventsourcing, also known as server-sent events.