-
Notifications
You must be signed in to change notification settings - Fork 18.3k
Description
Content negotiation is, roughly, the process of figuring out what content type the response should take, based on an Accept header present in the request.
An example might be an image server that figures out which image format to send to the client, or an API that wants to return HTML to browsers but JSON to command line clients.
It's tricky to get right because the client may accept multiple content types, and the server may have multiple types available, and it can be difficult to match these correctly. I think this is a good fit for Go standard (or adjacent) libraries because:
- there's a formal specification for how it should behave: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
- it's annoying to implement yourself, correctly; you have to write a mini-parser.
- it would take one function to implement, which makes it annoying to import an entire third party library for (assuming you find the right one)
I've seen people hack around this in various ways:
- checking whether the Accept header contains a certain string
- checking for the first matching value,
- returning different content types based on the User-Agent
- requiring different URI's to get different content.
There's a sample implementation here with a pretty good API: https://godoc.org/github.com/golang/gddo/httputil#NegotiateContentType
// NegotiateContentType returns the best offered content type for the request's
// Accept header. If two offers match with equal weight, then the more specific
// offer is preferred. For example, text/* trumps */*. If two offers match
// with equal weight and specificity, then the offer earlier in the list is
// preferred. If no offers match, then defaultOffer is returned.
func NegotiateContentType(r *http.Request, offers []string, defaultOffer string) string
offers
are content-types that the server can respond with, and can include wildcards like text/*
or */*
. defaultOffer
is the default content type, if nothing in offers
matches. The returned value never has wildcards.
So you'd call it with something like
availableTypes := []string{"application/json", "text/plain", "text/html", "text/*"}
ctype: = NegotiateContentType(req, availableTypes, "application/json")
If the first value in offers
is text/*
and the client requests text/plain
, NegotiateContentType will return text/plain
. This is why you have to have a default - you can't just return the first value in offers
because it might include a wildcard.
In terms of where it could live, I'm guessing that net/http
is frozen at this point. Maybe one of the packages in x/net
would be a good fit? There is also a similar function for parsing Accept-Language headers in x/text/language. Open to ideas.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status