Package rfc9110

import "funnyhaha.technology/rfc/rfc9110"
View the source code

Overview

Package rfc9110 builds HTTP error statuses that carry the header fields RFC 9110 (HTTP Semantics) requires of them.

A Status can only be built by its constructors, and a status whose definition requires a header field takes that field's value as an argument. A 401 can't be built without a Challenge for its WWW-Authenticate field, nor a 405 without its Allow field:

rfc9110.Unauthorized(challenge)
rfc9110.MethodNotAllowed(rfc9110.MethodGet, rfc9110.MethodHead)

Optional fields are added with With methods, such as Status.WithRetryAfter. Values with a grammar, such as a Token, are checked by a Parse function, so a status built from them is always well-formed.

For content negotiation, ParseMediaType parses a Content-Type and ParseAccept parses an Accept field, whose Accept.Negotiate picks the representation the client prefers.

The package covers the 4xx and 5xx status codes of Section 15.5 and 15.6, tokens and quoted strings (Section 5.6), media types (Section 8.3.1), the authentication challenge framework (Section 11), Retry-After (Section 10.2.3) and Accept (Section 12.5.1). It doesn't cover successful or redirection responses, or the other negotiation fields.

The RFC text is in rfc9110.txt in this directory, and the doc comments cite its sections.

Index

Variables

var (
	// ErrInvalidToken is returned for a string that isn't a token
	// (Section 5.6.2).
	ErrInvalidToken = errors.New("invalid token")

	// ErrInvalidQuotedString is returned for a string that can't be sent
	// as a quoted-string, because it contains a control character
	// (Section 5.6.4).
	ErrInvalidQuotedString = errors.New("invalid quoted-string")

	// ErrInvalidProtocol is returned for a string that isn't an Upgrade
	// protocol (Section 7.8).
	ErrInvalidProtocol = errors.New("invalid protocol")
)
source
var ErrDuplicateAuthParam = errors.New("duplicate auth-param")
source

ErrDuplicateAuthParam is returned for a challenge that has two parameters with the same name. Section 11.2 says each name "MUST only occur once per challenge".

var ErrInvalidAccept = errors.New("invalid Accept")
source

ErrInvalidAccept is returned for an Accept field value that doesn't match the grammar of Section 12.5.1.

var ErrInvalidMediaType = errors.New("invalid media type")
source

ErrInvalidMediaType is returned for a string that isn't a media type or a media range.

var ErrInvalidRetryAfter = errors.New("invalid Retry-After")
source

ErrInvalidRetryAfter is returned by ParseRetryAfter for a value that is neither delay-seconds nor an HTTP-date.

var ErrInvalidStatus = errors.New("invalid status")
source

ErrInvalidStatus is returned by NewStatus for a code it can't build.

Functions

ParseRetryAfter source

func ParseRetryAfter(s string, now time.Time) (time.Time, error)

ParseRetryAfter returns the time that a Retry-After value of s, received at now, asks the client to wait until. It accepts delay-seconds and all three HTTP-date formats (Section 5.6.7), and returns ErrInvalidRetryAfter for anything else.

Types

Accept source

type Accept struct {
	// contains filtered or unexported fields
}

Accept is a parsed Accept request header field: the media ranges a client accepts, each with a weight. The zero Accept is an absent field, which accepts every media type (Section 12.4.1).

See RFC 9110, Section 12.5.1.

ParseAccept source

func ParseAccept(lines ...string) (Accept, error)

ParseAccept parses the field lines of an Accept header field, as returned by http.Header.Values. A field with no media ranges is treated as absent. Any parameter named "q" is the weight, wherever it appears, as Section 12.5.1 asks recipients to do. It returns ErrInvalidAccept if a line doesn't match the grammar.

(Accept) Negotiate source

func (a Accept) Negotiate(offers ...MediaType) (MediaType, bool)

Negotiate returns the offer the client prefers, or false if it accepts none of them. Offers with equal weights are preferred in the order given, so list the server's preferred representation first.

See RFC 9110, Section 12.1.

(Accept) Quality source

func (a Accept) Quality(m MediaType) int

Quality returns the weight a client gives m, in thousandths: 1000 is most preferred and 0 is not acceptable. It's the weight of the most specific range that matches m. A range matches if its type and subtype are equal to m's or "*", and m has each of its parameters (other than "q") with an equal value, compared case-insensitively.

See RFC 9110, Section 12.5.1 and Table 5.

AuthParam source

type AuthParam struct {
	// contains filtered or unexported fields
}

AuthParam is an authentication parameter, a name and value such as realm="api". It's always sent with its value as a quoted-string.

See RFC 9110, Section 11.2.

NewAuthParam source

func NewAuthParam(name Token, value string) (AuthParam, error)

NewAuthParam returns a parameter with the given name and value. It returns ErrInvalidToken if name isn't a token, and ErrInvalidQuotedString if value contains a control character.

(AuthParam) Name source

func (p AuthParam) Name() Token

Name returns the parameter's name.

(AuthParam) String source

func (p AuthParam) String() string

String returns the parameter as sent: name="value".

(AuthParam) Value source

func (p AuthParam) Value() string

Value returns the parameter's unquoted value.

Challenge source

type Challenge struct {
	// contains filtered or unexported fields
}

Challenge is an authentication challenge, sent in a WWW-Authenticate or Proxy-Authenticate header field: a scheme and its parameters.

Packages for specific schemes, such as rfc6750 for Bearer and rfc7617 for Basic, build challenges with the parameters their scheme requires. Use NewChallenge for other schemes. The zero Challenge isn't valid.

See RFC 9110, Section 11.3.

NewChallenge source

func NewChallenge(scheme Token, params ...AuthParam) (Challenge, error)

NewChallenge returns a challenge for the scheme with the given parameters. It returns ErrInvalidToken if scheme isn't a token or a parameter is the zero AuthParam, and ErrDuplicateAuthParam if two parameters share a name, compared case-insensitively.

(Challenge) Params source

func (c Challenge) Params() []AuthParam

Params returns a copy of the challenge's parameters.

(Challenge) Scheme source

func (c Challenge) Scheme() Token

Scheme returns the challenge's authentication scheme.

(Challenge) String source

func (c Challenge) String() string

String returns the challenge as sent, such as: Bearer realm="api".

MediaType source

type MediaType struct {
	// contains filtered or unexported fields
}

MediaType is a media type, such as "application/json" or "text/plain;charset=utf-8". Its type, subtype and parameter names are lowercase, because they're case-insensitive. Use ParseMediaType to build one.

See RFC 9110, Section 8.3.1.

MustMediaType source

func MustMediaType(s string) MediaType

MustMediaType is like ParseMediaType but panics if s isn't a media type. It's for media types fixed at compile time.

ParseMediaType source

func ParseMediaType(s string) (MediaType, error)

ParseMediaType parses a media type, as sent in Content-Type. It returns ErrInvalidMediaType if s doesn't match the grammar of Section 8.3.1, or repeats a parameter. Wildcards aren't media types, so "*/*" is rejected.

(MediaType) Essence source

func (m MediaType) Essence() string

Essence returns the type and subtype without parameters, such as "application/json".

(MediaType) IsJSON source

func (m MediaType) IsJSON() bool

IsJSON reports whether m is application/json or has the structured syntax suffix +json (RFC 6839, Section 3.1), such as application/problem+json.

(MediaType) Param source

func (m MediaType) Param(name string) (string, bool)

Param returns the value of the named parameter, which is matched case-insensitively.

(MediaType) String source

func (m MediaType) String() string

String returns the media type in its preferred form: lowercase, no whitespace, and parameter values quoted only when they aren't tokens.

(MediaType) Subtype source

func (m MediaType) Subtype() string

Subtype returns the subtype, such as "json" or "problem+json".

(MediaType) Type source

func (m MediaType) Type() string

Type returns the top-level type, such as "application".

Method source

type Method string

Method is a request method, such as MethodGet. Use ParseMethod for other methods.

See RFC 9110, Section 9.

const (
	MethodGet     Method = "GET"
	MethodHead    Method = "HEAD"
	MethodPost    Method = "POST"
	MethodPut     Method = "PUT"
	MethodDelete  Method = "DELETE"
	MethodConnect Method = "CONNECT"
	MethodOptions Method = "OPTIONS"
	MethodTrace   Method = "TRACE"
	MethodPatch   Method = "PATCH"
)
source

The methods defined in Section 9.3, and PATCH from RFC 5789.

ParseMethod source

func ParseMethod(s string) (Method, error)

ParseMethod returns s as a Method, or ErrInvalidToken if it isn't a token. Methods are case-sensitive.

Protocol source

type Protocol string

Protocol is a protocol named in an Upgrade header field: a name with an optional version, such as "websocket" or "HTTP/3.0". Use ParseProtocol to check a string's syntax.

See RFC 9110, Section 7.8.

ParseProtocol source

func ParseProtocol(s string) (Protocol, error)

ParseProtocol returns s as a Protocol, or ErrInvalidProtocol if it isn't a token, optionally followed by "/" and a version token.

RetryAfter source

type RetryAfter struct {
	// contains filtered or unexported fields
}

RetryAfter is the value of a Retry-After header field: how long a client ought to wait before it tries again. The zero RetryAfter is a delay of zero seconds.

See RFC 9110, Section 10.2.3.

RetryAfterDate source

func RetryAfterDate(t time.Time) RetryAfter

RetryAfterDate returns t as an HTTP-date, in UTC with whole seconds.

RetryAfterDelay source

func RetryAfterDelay(d time.Duration) RetryAfter

RetryAfterDelay returns a delay of d, rounded up to whole seconds. A negative d is treated as zero.

(RetryAfter) String source

func (r RetryAfter) String() string

String returns the field value: delay-seconds or an HTTP-date.

Status source

type Status struct {
	// contains filtered or unexported fields
}

Status is a 4xx or 5xx status code together with the header fields that go with it. Build one with its constructor, such as NotFound or Unauthorized, or with NewStatus. The zero Status isn't valid.

Status is immutable: its With methods return a modified copy.

See RFC 9110, Section 15.

BadGateway source

func BadGateway() Status

BadGateway returns 502 Bad Gateway (Section 15.6.3).

BadRequest source

func BadRequest() Status

BadRequest returns 400 Bad Request (Section 15.5.1).

Conflict source

func Conflict() Status

Conflict returns 409 Conflict (Section 15.5.10).

ContentTooLarge source

func ContentTooLarge() Status

ContentTooLarge returns 413 Content Too Large (Section 15.5.14).

ExpectationFailed source

func ExpectationFailed() Status

ExpectationFailed returns 417 Expectation Failed (Section 15.5.18).

Forbidden source

func Forbidden() Status

Forbidden returns 403 Forbidden (Section 15.5.4).

GatewayTimeout source

func GatewayTimeout() Status

GatewayTimeout returns 504 Gateway Timeout (Section 15.6.5).

Gone source

func Gone() Status

Gone returns 410 Gone (Section 15.5.11). A 410 is heuristically cacheable.

HTTPVersionNotSupported source

func HTTPVersionNotSupported() Status

HTTPVersionNotSupported returns 505 HTTP Version Not Supported (Section 15.6.6).

InternalServerError source

func InternalServerError() Status

InternalServerError returns 500 Internal Server Error (Section 15.6.1).

LengthRequired source

func LengthRequired() Status

LengthRequired returns 411 Length Required (Section 15.5.12).

MethodNotAllowed source

func MethodNotAllowed(allow ...Method) Status

MethodNotAllowed returns 405 Method Not Allowed, with an Allow field listing the methods the resource supports. Section 10.2.1 says a 405 "MUST generate an Allow header field", and that it may be empty if the resource allows no methods.

Example

ExampleMethodNotAllowed shows a 405 with the Allow field RFC 9110, Section 15.5.6, requires.

package main

import (
	"fmt"

	"funnyhaha.technology/rfc/rfc9110"
)

func main() {
	s := rfc9110.MethodNotAllowed(rfc9110.MethodGet, rfc9110.MethodHead)

	fmt.Println(s.Code(), s.Header().Get("Allow"))
}

Output:

405 GET, HEAD

MisdirectedRequest source

func MisdirectedRequest() Status

MisdirectedRequest returns 421 Misdirected Request (Section 15.5.20).

NewStatus source

func NewStatus(code int) (Status, error)

NewStatus returns the status for any 4xx or 5xx code that doesn't need a header field, such as 402 or a code defined by another RFC. It returns ErrInvalidStatus for a code outside 400 to 599, and for 401, 405, 407, 416 and 426, which must be built with their own constructors.

NotAcceptable source

func NotAcceptable() Status

NotAcceptable returns 406 Not Acceptable (Section 15.5.7).

NotFound source

func NotFound() Status

NotFound returns 404 Not Found (Section 15.5.5). A 404 is heuristically cacheable, so consider adding rfc9111.NoStore.

NotImplemented source

func NotImplemented() Status

NotImplemented returns 501 Not Implemented (Section 15.6.2). A 501 is heuristically cacheable.

PreconditionFailed source

func PreconditionFailed() Status

PreconditionFailed returns 412 Precondition Failed (Section 15.5.13).

ProxyAuthenticationRequired source

func ProxyAuthenticationRequired(c Challenge, more ...Challenge) Status

ProxyAuthenticationRequired returns 407 Proxy Authentication Required, with a Proxy-Authenticate field holding the challenges (Section 15.5.8).

RangeNotSatisfiable source

func RangeNotSatisfiable(completeLength uint64) Status

RangeNotSatisfiable returns 416 Range Not Satisfiable, with a Content-Range field giving the representation's current length, as Section 15.5.17 says a server generating a 416 "SHOULD" send.

RequestTimeout source

func RequestTimeout() Status

RequestTimeout returns 408 Request Timeout (Section 15.5.9).

ServiceUnavailable source

func ServiceUnavailable() Status

ServiceUnavailable returns 503 Service Unavailable (Section 15.6.4). Add Status.WithRetryAfter if you know when the service will be back.

URITooLong source

func URITooLong() Status

URITooLong returns 414 URI Too Long (Section 15.5.15). A 414 is heuristically cacheable.

Unauthorized source

func Unauthorized(c Challenge, more ...Challenge) Status

Unauthorized returns 401 Unauthorized, with a WWW-Authenticate field holding the challenges. Section 15.5.2 says a 401 "MUST send a WWW-Authenticate header field".

Example

ExampleUnauthorized shows a 401 written with the WWW-Authenticate challenge RFC 9110, Section 15.5.2, requires.

package main

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

	"funnyhaha.technology/rfc/rfc9110"
)

func main() {
	realm, err := rfc9110.NewAuthParam("realm", "api")
	if err != nil {
		panic(err)
	}
	challenge, err := rfc9110.NewChallenge("Bearer", realm)
	if err != nil {
		panic(err)
	}

	rec := httptest.NewRecorder()
	rfc9110.Unauthorized(challenge).WriteHeader(rec)

	fmt.Println(rec.Code)
	fmt.Println(rec.Header().Get("WWW-Authenticate"))
}

Output:

401
Bearer realm="api"

UnprocessableContent source

func UnprocessableContent() Status

UnprocessableContent returns 422 Unprocessable Content (Section 15.5.21).

UnsupportedMediaType source

func UnsupportedMediaType() Status

UnsupportedMediaType returns 415 Unsupported Media Type (Section 15.5.16).

UpgradeRequired source

func UpgradeRequired(p Protocol, more ...Protocol) Status

UpgradeRequired returns 426 Upgrade Required, with an Upgrade field listing the protocols in order of preference. Section 15.5.22 says a 426 "MUST send an Upgrade header field", and Section 7.8 that the sender "MUST also send an "Upgrade" connection option in the Connection header field", so that's set too.

(Status) Code source

func (s Status) Code() int

Code returns the status code.

(Status) Header source

func (s Status) Header() http.Header

Header returns a copy of the status's header fields.

(Status) WithAuthenticate source

func (s Status) WithAuthenticate(c Challenge, more ...Challenge) Status

WithAuthenticate returns a copy of s with a WWW-Authenticate field holding the challenges, replacing any already set. Section 11.6.1 allows it on any response "to indicate that supplying credentials (or different credentials) might affect the response", such as a 403.

(Status) WithCacheControl source

func (s Status) WithCacheControl(dirs ...rfc9111.Directive) Status

WithCacheControl returns a copy of s with a Cache-Control field holding the directives, replacing any already set.

(Status) WithRetryAfter source

func (s Status) WithRetryAfter(r RetryAfter) Status

WithRetryAfter returns a copy of s with a Retry-After field (Section 10.2.3).

Example

ExampleStatus_WithRetryAfter shows a 503 telling the client to retry in two minutes.

package main

import (
	"fmt"
	"time"

	"funnyhaha.technology/rfc/rfc9110"
)

func main() {
	s := rfc9110.ServiceUnavailable().WithRetryAfter(rfc9110.RetryAfterDelay(2 * time.Minute))

	fmt.Println(s.Code(), s.Header().Get("Retry-After"))
}

Output:

503 120

(Status) WriteHeader source

func (s Status) WriteHeader(w http.ResponseWriter)

WriteHeader copies the status's header fields to w and writes the status code. Set any other header fields first.

Token source

type Token string

Token is a short textual identifier with no whitespace or delimiters, such as an authentication scheme or a parameter name. Use ParseToken to check a string's syntax.

See RFC 9110, Section 5.6.2.

ParseToken source

func ParseToken(s string) (Token, error)

ParseToken returns s as a Token, or ErrInvalidToken if it isn't one.