Documentation ¶
Overview ¶
Package negotiator is a library that handles content negotiation in web applications written in Go. Content negotiation is specified by RFC (http://tools.ietf.org/html/rfc7231) and, less formally, by Ajax (https://en.wikipedia.org/wiki/XMLHttpRequest).
A Negotiator contains a list of ResponseProcessor. For each call to Negotiate, one or more offers of possibly-matching data is compared with the headers in the request. The best matching response processor is chosen and given the task of sending the response.
For more information visit http://github.com/rickb777/negotiator
import "github.com/rickb777/negotiator" ... func getUser(w http.ResponseWriter, req *http.Request) { user := &User{"Joe", "Bloggs"} negotiator.NegotiateWithJSONAndXML(w, req, negotiator.Offer{Data: user}) }
Accept - from https://tools.ietf.org/html/rfc7231#section-5.3.2:
The "Accept" header field can be used by user agents to specify response media types that are acceptable. Accept header fields can be used to indicate that the request is specifically limited to a small set of desired types, as in the case of a request for an in-line image.
A request without any Accept header field implies that the user agent will accept any media type in response.
If the header field is present in a request and none of the available representations for the response have a media type that is listed as acceptable, the origin server can either honor the header field by sending a 406 (Not Acceptable) response, or disregard the header field by treating the response as if it is not subject to content negotiation.
Accept-Language - from https://tools.ietf.org/html/rfc7231#section-5.3.5:
The "Accept-Language" header field can be used by user agents to indicate the set of natural languages that are preferred in the response.
A request without any Accept-Language header field implies that the user agent will accept any language in response.
If the header field is present in a request and none of the available representations for the response have a matching language tag, the origin server can either disregard the header field by treating the response as if it is not subject to content negotiation or honor the header field by sending a 406 (Not Acceptable) response. However, the latter is not encouraged, as doing so can prevent users from accessing content that they might be able to use (with translation software, for example).
Index ¶
- Constants
- Variables
- func IsAjax(req *http.Request) bool
- type CodedRender
- type ErrorHandler
- type Negotiator
- func (n *Negotiator) Append(responseProcessors ...processor.ResponseProcessor) *Negotiator
- func (n *Negotiator) N() int
- func (n *Negotiator) Negotiate(w http.ResponseWriter, req *http.Request, offers ...Offer) error
- func (n *Negotiator) Processor(i int) processor.ResponseProcessor
- func (n *Negotiator) Render(req *http.Request, offers ...Offer) CodedRender
- func (n *Negotiator) WithDefaults() *Negotiator
- func (n *Negotiator) WithErrorHandler(eh ErrorHandler) *Negotiator
- type Offer
- type Offers
- type Render
Examples ¶
Constants ¶
const ( Accept = "Accept" AcceptLanguage = "Accept-Language" AcceptCharset = "Accept-Charset" XRequestedWith = "X-Requested-With" XMLHttpRequest = "XMLHttpRequest" )
Variables ¶
var Printer = func(level byte, message string, data map[string]interface{}) {}
Printer is something that allows printing log entries. This is only used for diagnostics.
var StdLogger = func(level byte, message string, data map[string]interface{}) { buf := &strings.Builder{} fmt.Fprintf(buf, "%c: %s", level, message) for k, v := range data { fmt.Fprintf(buf, ", %q: %v", k, v) } log.Printf(buf.String()) }
StdLogger adapts the standard Go logger to be usable for the negotiator.
Functions ¶
Types ¶
type CodedRender ¶ added in v0.7.0
CodedRender extends Render with a status code. This provides compatibility with the Gin Context.Render method.
type ErrorHandler ¶
type ErrorHandler func(w http.ResponseWriter, error string, code int)
ErrorHandler is called for NotAcceptable and InternalServerError situations.
type Negotiator ¶
type Negotiator struct {
// contains filtered or unexported fields
}
Negotiator is responsible for content negotiation when using custom response processors.
func New ¶
func New(responseProcessors ...processor.ResponseProcessor) *Negotiator
New creates a Negotiator with a list of custom response processors. The error handler invokes http.Error and the diagnostic printer is no-op; change these if required.
func (*Negotiator) Append ¶ added in v0.6.0
func (n *Negotiator) Append(responseProcessors ...processor.ResponseProcessor) *Negotiator
Append more response processors. A new Negotiator is returned with the original processors plus the extra processors. The extra processors are appended last. Because the processors are checked in order, any overlap of matching media range goes to the first such matching processor.
func (*Negotiator) N ¶ added in v0.6.0
func (n *Negotiator) N() int
N returns the number of processors.
func (*Negotiator) Negotiate ¶
func (n *Negotiator) Negotiate(w http.ResponseWriter, req *http.Request, offers ...Offer) error
Negotiate negotiates your model based on the HTTP Accept and Accept-... headers. Any error arising will result in a panic.
Example (SingleOffer) ¶
Negotiate applies the negotiation algorithm, choosing the response based on the Accept header in the request, if present. It returns either a successful response or a 406-Not Acceptable, or possibly a 500-Internal server error.
In this example, there is only one offer and it will be used by whichever response processor matches the request.
package main import ( "net/http" "github.com/rickb777/negotiator" ) type User struct { Name string } func main() { // getUser is a 'standard' handler function getUser := func(w http.ResponseWriter, req *http.Request) { // some data; this will be wrapped in an Offer{} user := &User{Name: "Joe Bloggs"} // the negotiator determines the response format based on the request headers negotiator.New().WithDefaults().Negotiate(w, req, negotiator.Offer{Data: user}) } // normal handling http.Handle("/user", http.HandlerFunc(getUser)) }
Output:
func (*Negotiator) Processor ¶ added in v0.6.0
func (n *Negotiator) Processor(i int) processor.ResponseProcessor
Processor gets the ith processor.
func (*Negotiator) Render ¶ added in v0.7.0
func (n *Negotiator) Render(req *http.Request, offers ...Offer) CodedRender
Render computes the best matching response, if there is one, and returns a suitable renderer that is compatible with Gin (github.com/gin-gonic/gin).
Example (SingleOffer) ¶
Negotiate applies the negotiation algorithm, choosing the response based on the Accept header in the request, if present. It returns either a successful response or a 406-Not Acceptable.
In this example, there is only one offer and it will be used by whichever response processor matches the request. The example integrates the negotiator seamlessly with Gin using the Context.Render method.
package main import ( "github.com/gin-gonic/gin" "github.com/rickb777/negotiator" ) type User struct { Name string } func main() { // create and configure Gin engine, e.g. engine := gin.Default() // getUser is a 'standard' handler function getUser := func(c *gin.Context) { // some data; this will be wrapped in an Offer{} user := &User{Name: "Joe Bloggs"} // the negotiator determines the response format based on the request headers // returning a CodedRender value cr := negotiator.New().WithDefaults().Render(c.Request, negotiator.Offer{Data: user}) // pass the negotiation result to Gin; the status code will be one of // 200-OK, 204-No content, or 406-Not acceptable c.Render(cr.StatusCode(), cr) } // normal handling engine.GET("/user", getUser) }
Output:
func (*Negotiator) WithDefaults ¶ added in v0.8.0
func (n *Negotiator) WithDefaults() *Negotiator
WithDefaults adds the default processors JSON, XML, CSV and TXT.
func (*Negotiator) WithErrorHandler ¶ added in v0.6.0
func (n *Negotiator) WithErrorHandler(eh ErrorHandler) *Negotiator
WithErrorHandler adds a custom error handler. This is used for 406-Not Acceptable cases and dealing with 500-Internal Server Error in Negotiate.
type Offer ¶ added in v0.6.0
type Offer struct { MediaType string // e.g. "text/html" or blank not relevant Language string // blank if not relevant Template string // blank if not relevant Data interface{} }
Offer holds the set of parameters that are offered to the content negotiation. Note that Data will be passed to a ResponseProcessor, having first checked
* if it is a func(language string) interface{}, that function will have been called with the chosen language as its parameter.
* if it is a func() interface{}, that function will have been called
The above checks are repeated until the data is neither kind of function.
If the (resulting) data is nil, the response will have 204-Not Content status instead of 200-OK.
type Offers ¶ added in v0.6.0
type Offers []Offer
Offers is a slice of Offer.
func (Offers) MediaTypes ¶ added in v0.6.0
MediaTypes gets the media types from the offers, keeping the same order.
type Render ¶ added in v0.7.0
type Render interface { // Render writes data with custom ContentType. Render(http.ResponseWriter) error // WriteContentType writes custom ContentType. WriteContentType(w http.ResponseWriter) }
Render defines the interface for content renderers. Note that it happens to match render.Render in github.com/gin-gonic/gin/render. This means that this negotiator package can be used with Gin directly.
Directories ¶
Path | Synopsis |
---|---|
package header provides parsing rules for content negotiation headers according to RFC-7231.
|
package header provides parsing rules for content negotiation headers according to RFC-7231. |
package processor defines what a ResponseProcessor is, and provides four standard implementations: JSON, XML, CSV and plain text.
|
package processor defines what a ResponseProcessor is, and provides four standard implementations: JSON, XML, CSV and plain text. |