negotiator
A compact library for handling HTTP content negotiation for RESTful APIs.
What is it?
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.
Why use it?
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.
How to use it?
Quickstart
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)
}
})
Examples
If you are looking for hands-on examples, we've created a sample RESTful API,
called tutor
, that leverages this library.
Proactive
Construction
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(),
)
Strict Mode
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.
Reactive
Construction
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),
)
Transparent
Construction
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),
)
Logging
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),
)
Metrics
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),
)
Emitted Metrics
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. |
Contribute
Want to lend us a hand? Check out our guidelines for
contributing.
License
We are rocking an Apache 2.0 license for this project.
Code of Conduct
Please check out our code of conduct to get up to speed
how we do things.