acceptable

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 9, 2021 License: MIT Imports: 7 Imported by: 0

README

Acceptable

GoDoc Build Status Issues

This is a library that handles Accept headers, which form the basis of content negotiation in HTTP server applications written in Go. It provides an implementation of the content negotiation algorithm specified in RFC-7231.

Accept parsing

The Accept header is parsed using ParseMediaRanges(hdr), which returns the slice of media ranges, e.g.

    // handle Accept-Language
    mediaRanges := header.ParseMediaRanges("application/json;q=0.8, application/xml, application/*;q=0.1")

The resulting slice is sorted according to precedence and quality rules, so in this example the order is {"application/xml", "application/json", "application/*"} because the middle item has an implied quality of 1, whereas the first item has a lower quality.

Accept-Language and Accept-Charset parsing

The other important content-negotiation headers, Accept-Language and Accept-Charset, are handled by the header.Parse method, e.g.

    // handle Accept-Language
    acceptLanguages := header.Parse("en-GB,fr;q=0.5,en;q=0.8")

This will contain {"en-GB", "en", "fr"} in a header.PrecedenceValues slice, sorted according to precedence rules with the most preferred first.

The acceptable.Parse function can be used for Accept-Encoding as well as Accept-Language and Accept-Charset. However, the Go standard library deals with Accept-Encoding, so you won't need to.

Putting it together - simple content negotiation

Finding the best match depends on you listing your available response representations. For example

    offer1 := acceptable.OfferOf("application/json")
    offer2 := acceptable.OfferOf("application/xml")
    best := acceptable.BestRequestMatch(request, offer1, offer2)

The best result will be the one that best matches the request headers. If none match, it will be nil and the response should be 406-Not Acceptable. If you need to have a catch-all case, include acceptable.OfferOf("*/*") last in the list.

The offers can also be restricted by language matching using their Language field. This matches Accept-Language using the basic prefix algorithm, which means for example that if you specify "en" it will match "en", "en-GB" and everything else beginning with "en-".

The offers can hold a suitable rendering function in their Processor field if required. If the best offer matched is not nil, you can easily call its Processor function in order to render the response.

Documentation

Overview

Package acceptable is a library that handles headers for 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).

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

Examples

Constants

View Source
const (
	Accept         = "Accept"
	AcceptLanguage = "Accept-Language"
	AcceptCharset  = "Accept-Charset"

	// The header strings used for XHR.
	XRequestedWith = "X-Requested-With"
	XMLHttpRequest = "xmlhttprequest"
)

Variables

View Source
var Debug = func(string, ...interface{}) {}

Debug can be used for observing decisions made by the negotiator. By default it is no-op.

Functions

func IsAjax

func IsAjax(req *http.Request) bool

IsAjax tests whether a request has the Ajax header sent by browsers for XHR requests.

Types

type Match added in v0.3.0

type Match struct {
	Type     string
	Subtype  string
	Language string
	Charset  string
	Data     interface{}
	Render   Processor
}

Match holds the best-matched offer after content negotiation and is used for response rendering.

func BestRequestMatch

func BestRequestMatch(req *http.Request, available ...Offer) *Match

BestRequestMatch finds the content type and language that best matches the accepted media ranges and languages contained in request headers. The result contains the best match, based on the rules of RFC-7231. On exit, the result will contain the preferred language and charset, if these are known.

Whenever the result is nil, the response should be 406-Not Acceptable.

For all Ajax requests, the available offers are filtered so that only those capable of providing an Ajax response are considered by the content negotiation algorithm. The other offers are discarded.

If no available offers are provided, the response will always be nil. Note too that Ajax requests will result in nil being returned if no offer is capable of handling them, even if other offers are provided.

Example
package main

import (
	"net/http"
	"net/http/httptest"

	"github.com/rickb777/acceptable/processor"

	"github.com/rickb777/acceptable"
)

func main() {
	// In this example, the same content is available in three languages. We're using functions
	// for the sake of illustration, but simple values (often structs) will also work and will
	// sometimes be more appropriate.

	en := func() (interface{}, error) {
		return "Hello!", nil // get English content - eg from database
	}

	fr := func() (interface{}, error) {
		return "Bonjour!", nil // get French content - eg from database
	}

	es := func() (interface{}, error) {
		return "Hola!", nil // get Spanish content - eg from database
	}

	// We're implementing an HTTP handler, so we are given a request and a response.

	req := &http.Request{}                               // some incoming request
	var res http.ResponseWriter = httptest.NewRecorder() // replace with the server's response writer

	// Now do the content negotiation. This example has three supported content types, all of them
	// able to serve any of the three example languages.
	//
	// The first offer is for JSON - this is often the most widely used because it also supports
	// Ajax requests.

	best := acceptable.BestRequestMatch(req,
		acceptable.OfferOf("application/json", "en").Using(processor.JSON("  ")).
			With("en", en).With("fr", fr).With("es", es),

		acceptable.OfferOf("application/xml").Using(processor.XML("  ")).
			With("en", en).With("fr", fr).With("es", es),

		acceptable.OfferOf("text/plain").Using(processor.TXT()).
			With("en", en).With("fr", fr).With("es", es))

	if best == nil {
		// The user agent asked for some content type that isn't available.

		res.WriteHeader(http.StatusNotAcceptable)
		res.Write([]byte(http.StatusText(http.StatusNotAcceptable)))
		return
	}

	// Happy case - we found a good match for the requested content so we serve it as the response.

	best.Render(res, *best, "")
}

func (Match) ApplyHeaders added in v0.3.0

func (m Match) ApplyHeaders(rw http.ResponseWriter) (w io.Writer)

ApplyHeaders sets response headers so that the user agent is notified of the content negotiation decisons made. Four headers may be set, depending on context.

  • Content-Type is always set.
  • Content-Language is set when a language was selected.
  • Content-Encoding is set when the character set is being transcoded
  • Vary is set to list the accept headers that led to the three decisions above.

type Offer

type Offer struct {
	// ContentType is the content type that is to be matched.
	// Wildcard values may be used.
	header.ContentType
	// contains filtered or unexported fields
}

Offer holds information about one particular resource representation that can potentially provide an acceptable response.

func OfferOf

func OfferOf(contentType string, language ...string) Offer

OfferOf constructs an Offer easily. If the language is absent, it is assumed to be the wildcard "*". If the content type is blank, it is assumed to be the full wildcard "*/*". Also, contentType can be a partial wildcard "type/*".

func (Offer) String

func (o Offer) String() string

func (Offer) Using added in v0.5.0

func (o Offer) Using(processor Processor) Offer

Using attaches a processor function to an offer and returns the modified offer. The original offer is unchanged.

func (Offer) With

func (o Offer) With(language string, data interface{}) Offer

With attaches response data to an offer. The original offer is unchanged.

type Offers

type Offers []Offer

Offers holds a slice of Offer.

func (Offers) Filter

func (offers Offers) Filter(typ, subtype string) Offers

Filter returns only the offers that match specified type and subtype.

type Processor added in v0.3.0

type Processor func(w http.ResponseWriter, match Match, template string) error

Processor is a function that renders content according to the matched result. The data can be a struct, slice etc or a Supplier.

type Supplier added in v0.5.0

type Supplier func() (interface{}, error)

Supplier supplies data in the form of a struct, a slice, etc. This allows for evaluation on demand ('lazy'), e.g. fetching from a database.

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 contains flexible implementations for rendering JSON, XML, CSV etc.
Package processor contains flexible implementations for rendering JSON, XML, CSV etc.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL