Multipart HTTP responses in Go
source link: https://peter.bourgon.org/blog/2019/02/12/multipart-http-responses.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Multipart HTTP responses in Go
2019 02 12
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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK