A compact library for handling HTTP content negotiation for RESTful APIs.
negotiator
enhances the interoperability of your HTTP API by equipping it
with capabilities to facilitate proactive,
reactive, and transparent content negotiation.
This is accomplished by providing customizable and extendable functionality
that adheres to RFC specifications, as well as industry adopted algorithms.
With negotiator
, your API no longer needs to take on the burden of
implementing content negotiation, allowing you to focus on simply defining
your representations and letting us do the rest.
There are many reasons why you should leave your HTTP content negotiation to us:
- content negotiation algorithms are not trivial, with some algorithms detailed in lengthy RFC documentation while others lacking any standardization at all.
- allows you to focus purely on defining your representations.
- maximizes your APIs interoperability, lowering friction for client adoption.
- unlocks all forms of content negotiation, allowing your API leverage different kinds of negotiation algorithms to support all of your flows.
- customization allowing you to disable or enable particular features.
- extensibility allowing you to define your own algorithms.
http.HandleFunc("/foo", func(rw http.ResponseWriter, r *http.Request) {
// gather representations.
representations := []representation.Representation { Foo { ID: 1 } }
// choose a negotiator.
p := proactive.Default
// negotiate.
ctx := negotiator.NegotiationContext { Request: r, ResponseWriter: rw }
if err := p.Negotiate(ctx, representations...); err != nil {
http.Error(rw, "oops!", 500)
}
})
If you are looking for hands-on examples, we've created a sample RESTful API,
called tutor
, that leverages this library.
For out of the box proactive negotiation support, use
proactive.Default
, which is the default proactive
negotiator.
// retrieves the default proactive negotiator.
p := proactive.Default
In situations where more customization is required, use the
proactive.New
constructor function and specify options
as arguments.
// constructs a proactive negotiator with the provided options.
p := proactive.New(
proactive.DisableStrictMode(),
proactive.DisableNotAcceptableRepresentation(),
)
According to RFC7231, when none of the representations match the
values provided for a particular proactive content negotiation header, the
origin server can honor that header and return 406 Not Acceptable
, or
disregard the header field by treating the resource as if it is not subject
to content negotiation.
The behavior of honoring the header in these scenarios is what we refer to as strict mode. It is possible to configure strict mode for each individual proactive negotiation header, or disable strict mode for all. Strict mode is enabled for all headers by default.
For out of the box reactive negotiation support, use
reactive.Default
, which is the default reactive
negotiator.
// retrieves the default reactive negotiator.
p := reactive.Default
In situations where more customization is required, use the
reactive.New
constructor function and specify options
as arguments.
// constructs a reactive negotiator with the provided options.
p := reactive.New(
reactive.Logger(l),
)
For out of the box transparent negotiation support, use
transparent.Default
, which is the default transparent
negotiator.
// retrieves the default transparent negotiator.
p := transparent.Default
In situations where more customization is required, use the
transparent.New
constructor function and specify options
as arguments.
// constructs a transparent negotiator with the provided options.
p := transparent.New(
transparent.MaximumVariantListSize(5),
)
We use zap
as our logging library of choice. To leverage the logs
emitted from the negotiator, utilize the proactive.Logger
,
reactive.Logger
, or transparent.Logger
option with a *zap.Logger
upon creation.
l, _ := zap.NewDevelopment()
// create a proactive negotiator with logging.
pn := proactive.New(
proactive.Logger(l),
)
// create a reactive negotiator with logging.
rn := reactive.New(
reactive.Logger(l),
)
// create a transparent negotiator with logging.
tn := transparent.New(
transparent.Logger(l),
)
For emitting metrics, we use tally
. To utilize the metrics emitted
from the negotiator, leverage the proactive.Scope
,
reactive.Scope
, or transparent.Scope
option with a tally.Scope
upon creation.
s := tally.NewTestScope("example", map[string]string{})
// create a reactive negotiator with metrics.
rn := reactive.New(
reactive.Scope(s),
)
// create a proactive negotiator with metrics.
pn := proactive.New(
proactive.Scope(s),
)
// create a transparent negotiator with metrics.
tn := transparent.New(
transparent.Scope(s),
)
Name | Tag | Type | Description |
---|---|---|---|
[PREFIX.]negotiate | negotiator: proactive | timer | The time spent during proactive negotiation. |
[PREFIX.]negotiate | negotiator: reactive | timer | The time spent during reactive negotiation. |
[PREFIX.]negotiate | negotiator: transparent | timer | The time spent during transparent negotiation. |
[PREFIX.]negotiate.no_content | negotiator: proactive | counter | The count of proactive negotiation resulting in HTTP 204. |
[PREFIX.]negotiate.no_content | negotiator: reactive | counter | The count of reactive negotiation resulting in HTTP 204. |
[PREFIX.]negotiate.error | negotiator: proactive | counter | The count of proactive negotiation resulting in an error. |
[PREFIX.]negotiate.error | negotiator: reactive | counter | The count of reactive negotiation resulting in an error. |
[PREFIX.]negotiate.error | negotiator: transparent | counter | The count of transparent negotiation resulting in an error. |
[PREFIX.]negotiate.multiple_choices | negotiator: reactive | counter | The count of reactive negotiation resulting in HTTP 302. |
[PREFIX.]negotiate.acceptable | negotiator: proactive | counter | The count of reactive negotiation resulting in HTTP 200. |
[PREFIX.]negotiate.not_acceptable | negotiator: proactive | counter | The count of reactive negotiation resulting in HTTP 406. |
Want to lend us a hand? Check out our guidelines for contributing.
We are rocking an Apache 2.0 license for this project.
Please check out our code of conduct to get up to speed how we do things.