-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
x/net/http: support content negotiation #19307
Comments
Funny you should mention this, I was just looking for this for use in x/perf. Your proposed API is reasonable, but you should explicitly document what happens in exceptional circumstances:
|
That sounds reasonable to me.
The spec includes a grammar but doesn't really say what happens if you can't match it, other than you should return a 406 if you can't match anything. I think we should just return the default? |
@bradfitz suggested (if we want to go forward with this proposal) it could live in |
I also mentioned that it seems like |
Sorry, I tried to edit the description to cover that case.
I think the problem with this would be, if you had If we return the first value in |
@kevinburke What's wrong with returning text/plain if the first offer is text/*? I don't see why it has to return the exact string passed in—don't you need to know the negotiated type to set the header in the response? Also, if negotiation fails does it return the empty string? I'd rather have an error to distinguish between expected failure, negotiation failed, and unexpected failure, bad request. |
@kevinburke, any response to @jimmyfrasche? This seems fine if the simplified signature works. You want to prepare a CL? |
Also, we like to make sure we append |
I don't think wildcards should be allowed in the I agree with @jimmyfrasche that returning an error seems better so that the caller can know the reason if a default (first offered) is returned. maybe even not return a default at all. If there is a default the caller can handle that rather than build it as part of this method. For example there is a difference between not finding a match in an existing accept header and when the accept header is missing IMO. Since the RFC for Accept supports multiple parameters (such as
|
@kevinburke, what's the status here? It can also live in x/net if you want. It's probably too late for Go 1.10, though. |
It's too late and I'm buried in work stuff unfortunately, I probably won't have time to try to offer an implementation. I remember when I tried to implement it I ran into non-trivial problems with the design and some kinds of inputs. |
Go for it. No need to ask for permission. :) |
Permit me to note that Accept is not the only header that needs this sort of parsing. Accept-Encoding and Accept-Language have nearly the same syntax and need virtually the same parsing. A generic implementation would be welcome. |
Notice that there is already such a negotiation in the x/text/language package, in the |
Has anybody made any progress on this? I'd be interested in using some of this functionality. A suggestion for an incremental improvement that could be merged first: Make a variant of |
I've just ported goautoneg which is used by Kubernetes and Prometheus to parse accept headers into a Github repository at https://github.com/markusthoemmes/goautoneg. Would it be a valuable first step to add a |
I'm interested in working on this, but I wonder if it makes sense to follow x/text/language's example, and provide a Matcher type, so that the provided CTs needn't be parsed for every HTTP request. Any objections to this approach? If so, what should we call it? |
@markusthoemmes I see in your port of goautoneg that you're proposing in its readme be internalized into the stdlib you dropped the As an example we use the goautoneg |
Since Go standard library (and no library I could find) does not support content negotiation that follows RFC 7231, I wrote my own: https://github.com/elnormous/contenttype |
I've also written a library for this - which is BSD-3 licensed (like Go) and handles a lot of the edge cases I've hit before when writing these (more info on the blog) gitlab.com/jamietanna/content-negotiation-go I'd be happy to look at what we'd need to do to make this work within Go's codebase and follow the standard library's style? |
Just for the record, I have written (in Go) a content negotiation plugin for the caddy webserver: https://github.com/mpilhlt/caddy-conneg It relies on @elnormous's library mentioned above, adding stuff from go's own x/text/language libraries. (Will have a look at @jamietanna's library, too - when I find the time to do so, that is.) Thanks to you all for this issue and discussion, and for all your efforts, obviously. I am posting this here so that it may eventually provide some ideas about use cases and parameters that one would maybe like to throw at the library/libraries. |
FWIW, I just noticed Gin provides another implementation for this, with the https://pkg.go.dev/github.com/gin-gonic/gin#Context.NegotiateFormat It does not handle quality factors, AFAICS, though. |
Many people have been writing the same or similar code for their projects. Me also is not an exception. I remember writing a library for this functionality. Unfortunately, it was done for a closed-source project which is not controlled by me, so all the code is lost there. But this is not the case. I am surprised how long does it take for developers of this language to accept offers made by people across the Internet. We see that there are a lot of examples of already written code which is already working, but this issue is still opened and we do not see this fuctionality in the standard Go language. |
I ended up here trying to find this in the standard library. Echoing what others have said, I expected it to be part of the standard library because:
Many of the 3rd party packages linked here have a complicated API or don't implement the spec as-per the RFC, e.g. the quality scores. For my own use-cases, the API I'd most like to see would be a single func along the lines of: func ResponseType(accept string, offers ...string) string
// or
var r *http.Request
r.ResponseType(offers ...string) string Rationale:
A common use case might look like: switch r.ResponseType("text/html", "application/json") {
case "text/html":
writeHtmlResponse(w, ...)
case "application/json":
writeJsonResponse(w, ...)
default:
w.WriteHeader(http.StatusNotAcceptable)
} In case it helps anyone, I use a shim for this for my own projects as follows, using import "github.com/golang/gddo/httputil"
func ResponseType(r *http.Request, offers ...string) string {
return httputil.NegotiateContentType(r, offers, "")
} |
Hi there. Original author of the goautoneg library here. My apologies @markusthoemmes, I never saw your bug reports and only just today learned that it got used in things like Kubernetes and Prometheus (because I was wondering if Caddy could do autoneg). As Go developed more sophisticated and somewhat opinionated packaging infrastructure, now long ago, I probably got tired of keeping up with it. I also no longer had the original use case that made me write it to begin with. Anyways, though it's a bit late now, but just to say that I am perfectly happy for it to get used wherever you like under some sort of free license. Sorry for stuffing license information into the README, that was silly! It would make me happy if some small piece of it made it into Go's system libraries. |
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:
I've seen people hack around this in various ways:
There's a sample implementation here with a pretty good API: https://godoc.org/github.com/golang/gddo/httputil#NegotiateContentType
offers
are content-types that the server can respond with, and can include wildcards liketext/*
or*/*
.defaultOffer
is the default content type, if nothing inoffers
matches. The returned value never has wildcards.So you'd call it with something like
If the first value in
offers
istext/*
and the client requeststext/plain
, NegotiateContentType will returntext/plain
. This is why you have to have a default - you can't just return the first value inoffers
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 inx/net
would be a good fit? There is also a similar function for parsing Accept-Language headers in x/text/language. Open to ideas.The text was updated successfully, but these errors were encountered: