Skip to content

freerware/negotiator

Repository files navigation

negotiator

A compact library for handling HTTP content negotiation for RESTful APIs.

GoDoc Build Status Coverage Status Release License

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.