httpsemantics

package
v0.30.4 Latest Latest
Warning

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

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package httpsemantics provides stdlib-only helpers for HTTP standards including OAuth token-response cache headers, RFC 6750 Bearer challenges, RFC 9110 conditional requests, RFC 8288 Link headers, and RFC 9110 Retry-After parsing/formatting.

These helpers are opt-in and framework-neutral. They do not change default behavior of any requestCore handler or responder.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyBearerChallenge

func ApplyBearerChallenge(h http.Header, c BearerChallenge)

ApplyBearerChallenge sets the WWW-Authenticate header on an http.Header with the given Bearer challenge. The status code should be 401 (Unauthorized). The caller is responsible for determining that the response is a Bearer-protected resource-server 401; this helper does not automatically attach challenges to unrelated 401 responses.

func ApplyBearerChallengeToParser

func ApplyBearerChallengeToParser(setHeader func(name, value string), c BearerChallenge)

ApplyBearerChallengeToParser sets the WWW-Authenticate header through a SetHeader function (e.g. webFramework.RequestParser.SetRespHeader). This is the v1 integration point for framework-neutral header setting.

func ApplyTokenResponseNoStore

func ApplyTokenResponseNoStore(h http.Header)

ApplyTokenResponseNoStore sets the headers required by RFC 6749 §5.1 for OAuth 2.0 access-token responses: Cache-Control: no-store and Pragma: no-cache. The caller is responsible for determining that the response is a token response; this helper only applies the headers.

RFC 6749 §5.1: "The authorization server MUST include the HTTP "Cache-Control" response header field [RFC2616] with a value of "no-store" in any successful response to the token endpoint. The authorization server MUST include the "Pragma" response header field [RFC2616] with a value of "no-cache" in any successful response to the token endpoint."

func ApplyTokenResponseNoStoreToParser

func ApplyTokenResponseNoStoreToParser(setHeader func(name, value string))

ApplyTokenResponseNoStoreToParser sets the no-store headers through a SetHeader function (e.g. webFramework.RequestParser.SetRespHeader). This is the v1 integration point for framework-neutral header setting.

func ClampRetryAfter

func ClampRetryAfter(ra RetryAfter, now time.Time, maxDelay time.Duration) int

ClampRetryAfter caps a parsed Retry-After duration to a maximum delay. This prevents denial-of-service via excessively large Retry-After values. Returns the clamped duration in seconds.

func ExtractW3CTraceContext

func ExtractW3CTraceContext(parent context.Context, h http.Header) context.Context

ExtractW3CTraceContext extracts W3C Trace Context headers (traceparent, tracestate) from the given HTTP request and returns a new context with the trace context embedded. This is the inbound extraction point for W3C trace propagation.

This helper uses the globally configured OpenTelemetry propagator (otel.GetTextMapPropagator()), which is typically a composite of propagation.TraceContext{} and propagation.Baggage{} as configured by libTracing.

Usage in framework adapters:

ctx := httpsemantics.ExtractW3CTraceContext(r.Context(), r.Header)
// Pass ctx to request.NewContext or use it directly

func ExtractW3CTraceContextFromMap

func ExtractW3CTraceContextFromMap(parent context.Context, headers map[string]string) context.Context

ExtractW3CTraceContextFromMap extracts W3C Trace Context headers from a map[string]string (e.g. framework parser header map) and returns a new context with the trace context embedded.

func FormatBearerChallenge

func FormatBearerChallenge(c BearerChallenge) string

FormatBearerChallenge formats an RFC 6750 WWW-Authenticate Bearer challenge string. All parameter values are validated and escaped to prevent header injection. The realm is always included; other parameters are included only when non-empty.

The returned string is suitable for setting as the WWW-Authenticate response header value on a 401 response from a Bearer-protected resource server.

func FormatETagList

func FormatETagList(tags []ETag) string

FormatETagList formats a slice of ETags as a comma-separated header value.

func FormatLinkHeader

func FormatLinkHeader(links []Link) string

FormatLinkHeader serializes a slice of Links as a single Link header value, with links separated by commas.

func FormatRetryAfter

func FormatRetryAfter(ra RetryAfter, now time.Time, maxDelay time.Duration) string

FormatRetryAfter formats a parsed RetryAfter as a header string, clamped to maxDelay seconds. If maxDelay is 0, no clamping is applied.

func FormatRetryAfterDate

func FormatRetryAfterDate(t time.Time) string

FormatRetryAfterDate formats a time.Time as an RFC 9110 Retry-After HTTP-date header string (RFC 7231 IMF-fixdate format).

func FormatRetryAfterDelta

func FormatRetryAfterDelta(seconds int) string

FormatRetryAfterDelta formats a delta-seconds value as an RFC 9110 Retry-After header string.

func InjectW3CTraceContext

func InjectW3CTraceContext(ctx context.Context, h http.Header)

InjectW3CTraceContext injects the W3C Trace Context headers from the given context into the provided HTTP headers. This is the outbound injection point for W3C trace propagation when making downstream API calls.

Usage:

httpsemantics.InjectW3CTraceContext(ctx, req.Header)
// Then send req via http.Client

func InjectW3CTraceContextToMap

func InjectW3CTraceContextToMap(ctx context.Context, headers map[string]string)

InjectW3CTraceContextToMap injects W3C Trace Context headers from the given context into a map[string]string.

func IsNoBodyStatus

func IsNoBodyStatus(status int) bool

IsNoBodyStatus reports whether the given HTTP status code should not have a response body per RFC 9110.

func ValidateErrorURI

func ValidateErrorURI(uri string) error

ValidateErrorURI validates that the error_uri is a valid URI reference (absolute or relative). This prevents injection of malformed URIs into the WWW-Authenticate header.

Types

type BearerChallenge

type BearerChallenge struct {
	// Realm is a description of the protected resource. If empty,
	// "Protected" is used as a default.
	Realm string

	// Error is the RFC 6750 error code. If empty, no error parameter is
	// included (used for initial 401 challenges without a specific error).
	Error BearerError

	// ErrorDescription is a human-readable description of the error.
	// Must not contain characters that require escaping beyond standard
	// quoted-string rules. If empty, the parameter is omitted.
	ErrorDescription string

	// ErrorURI is a URI identifying a human-readable page with
	// information about the error. Must be a valid absolute or relative
	// URI. If empty, the parameter is omitted.
	ErrorURI string

	// Scope is a space-delimited list of scopes that would suffice for
	// the request. If empty, the parameter is omitted.
	Scope string
}

BearerChallenge holds the parameters for an RFC 6750 WWW-Authenticate Bearer challenge. All fields are validated and escaped by FormatBearerChallenge before being included in the header value.

type BearerError

type BearerError string

BearerError is a standard RFC 6750 Bearer challenge error code.

const (
	// BearerErrorInvalidToken indicates the access token provided is
	// expired, revoked, malformed, or invalid for other reasons.
	BearerErrorInvalidToken BearerError = "invalid_token"

	// BearerErrorInvalidRequest indicates the request is missing a
	// required parameter, includes an unsupported parameter or
	// parameter value, repeats the same parameter, uses more than one
	// method for including an access token, or is otherwise malformed.
	BearerErrorInvalidRequest BearerError = "invalid_request"

	// BearerErrorInsufficientScope indicates that the access token
	// provided has insufficient scope for the resource being accessed.
	BearerErrorInsufficientScope BearerError = "insufficient_scope"

	// BearerErrorMissingToken indicates that no access token was
	// provided in the request. This is not a standard RFC 6750 error
	// code but is commonly used in WWW-Authenticate challenges to
	// distinguish "no token" from "invalid token".
	BearerErrorMissingToken BearerError = "missing_token"
)

type ETag

type ETag struct {
	// Weak indicates whether this is a weak entity tag (W/ prefix).
	Weak bool

	// Value is the opaque entity-tag value without the surrounding
	// double quotes or W/ prefix.
	Value string
}

ETag represents an RFC 9110 entity tag with optional weak indicator.

func ParseETag

func ParseETag(s string) (ETag, error)

ParseETag parses a single RFC 9110 entity-tag value. Supports both strong ("value") and weak (W/"value") forms. Returns an error for malformed input.

func ParseETagList

func ParseETagList(s string) ([]ETag, error)

ParseETagList parses a comma-separated list of entity tags from a header value (e.g. If-Match, If-None-Match). Returns the parsed ETags. The wildcard "*" is represented as ETag{Value: "*"}.

func (ETag) String

func (e ETag) String() string

String formats the ETag as an RFC 9110 entity-tag value: `W/"value"` for weak, `"value"` for strong.

func (ETag) StrongEqual

func (e ETag) StrongEqual(other ETag) bool

StrongEqual reports whether two ETags are strongly equal per RFC 9110 §8.8.3.2: both must be strong and their values must match.

func (ETag) WeakEqual

func (e ETag) WeakEqual(other ETag) bool

WeakEqual reports whether two ETags are weakly equal per RFC 9110 §8.8.3.2: their values must match, regardless of weakness.

type Link struct {
	// URI is the link target. May be relative or absolute.
	URI string

	// Rel is the link relation type (e.g. "next", "prev", "self").
	Rel string

	// Title is an optional human-readable link title.
	Title string

	// Type is an optional media type hint for the target.
	Type string

	// HrefLang is an optional language hint.
	HrefLang string

	// ExtraParams holds additional parameters not covered by the
	// fields above. Keys are parameter names, values are parameter
	// values.
	ExtraParams map[string]string
}

Link represents a single RFC 8288 Web Link with a target URI and optional parameters.

func BuildPaginationLinks(cfg PaginationConfig) []Link

BuildPaginationLinks constructs RFC 8288 Link entries for a paginated collection. It preserves unrelated query parameters, omits unavailable relations (e.g. no "prev" on page 1), and safely handles relative or absolute request URLs.

func ParseLinkHeader

func ParseLinkHeader(s string) ([]Link, error)

ParseLinkHeader parses an RFC 8288 Link header value into a slice of Link entries. This is a simple parser for testing and verification.

func (Link) Format

func (l Link) Format() string

Format serializes the Link as an RFC 8288 link value: `<uri>; rel="rel"; title="title"`.

type PaginationConfig

type PaginationConfig struct {
	// BaseURL is the request URL (absolute or relative) without query
	// parameters. May include a path.
	BaseURL string

	// Page is the current page number (1-based).
	Page int

	// PageSize is the number of items per page.
	PageSize int

	// TotalItems is the total number of items across all pages.
	// If 0, the "last" link is omitted.
	TotalItems int

	// PageParam is the query parameter name for the page number.
	// Defaults to "page".
	PageParam string

	// PageSizeParam is the query parameter name for the page size.
	// Defaults to "per_page".
	PageSizeParam string

	// Self is the URI for the "self" link relation. If empty, the
	// "self" link is omitted.
	Self string

	// ExtraParams holds additional query parameters to preserve
	// across pagination links.
	ExtraParams url.Values
}

PaginationConfig holds the parameters for building pagination links.

type PaginationLinks struct {
	First string
	Prev  string
	Next  string
	Last  string
	Self  string
}

PaginationLinks holds the rel types for a paginated collection.

type PreconditionInput

type PreconditionInput struct {
	// IfMatch is the value of the If-Match header. Empty if absent.
	IfMatch string

	// IfNoneMatch is the value of the If-None-Match header. Empty if absent.
	IfNoneMatch string

	// IfModifiedSince is the value of the If-Modified-Since header.
	// Empty if absent.
	IfModifiedSince string

	// IfUnmodifiedSince is the value of the If-Unmodified-Since header.
	// Empty if absent.
	IfUnmodifiedSince string

	// ResourceETag is the current entity tag of the resource.
	// Empty if the resource has no ETag.
	ResourceETag string

	// ResourceModified is the last modification time of the resource.
	// Zero if the resource has no Last-Modified.
	ResourceModified time.Time

	// IsSafeMethod is true for GET and HEAD (where If-None-Match can
	// produce 304 rather than 412).
	IsSafeMethod bool
}

PreconditionInput holds the request headers and resource state needed to evaluate RFC 9110 preconditions.

type PreconditionResult

type PreconditionResult int

PreconditionResult indicates the outcome of precondition evaluation.

const (
	// PreconditionProceed means the request should proceed normally.
	PreconditionProceed PreconditionResult = iota

	// PreconditionNotModified means the server should respond with 304
	// Not Modified.
	PreconditionNotModified

	// PreconditionFailed means the server should respond with 412
	// Precondition Failed.
	PreconditionFailed

	// PreconditionRequired means the server should respond with 428
	// Precondition Required (the resource requires a precondition
	// that was not provided).
	PreconditionRequired
)

func EvaluatePreconditions

func EvaluatePreconditions(in PreconditionInput) PreconditionResult

EvaluatePreconditions evaluates RFC 9110 §13.1.2 precondition headers in precedence order and returns the appropriate result.

Precedence (RFC 9110 §13.2.2):

  1. If-Match
  2. If-Unmodified-Since
  3. If-None-Match
  4. If-Modified-Since

If-Match and If-Unmodified-Since produce 412 on failure. If-None-Match and If-Modified-Since produce 304 on failure for safe methods, 412 for unsafe methods.

type RetryAfter

type RetryAfter struct {
	// Delta is the delay in seconds. Valid only when IsDate is false.
	Delta int

	// Date is the retry time. Valid only when IsDate is true.
	Date time.Time

	// IsDate indicates whether the value is an HTTP-date form.
	IsDate bool
}

RetryAfter represents a parsed RFC 9110 Retry-After header value. It is either a delta-seconds (non-negative integer) or an HTTP-date.

func ParseRetryAfter

func ParseRetryAfter(s string) (RetryAfter, error)

ParseRetryAfter parses an RFC 9110 Retry-After header value. Supports both delta-seconds (integer) and HTTP-date (RFC 7231 IMF-fixdate) forms. Returns an error for malformed input.

func (RetryAfter) Duration

func (r RetryAfter) Duration(now time.Time) time.Duration

Duration returns the effective delay duration. For delta-seconds, it returns Delta seconds. For HTTP-date, it returns the duration from now until the date (clamped to non-negative). The clock parameter allows injecting a time source for deterministic tests.

Jump to

Keyboard shortcuts

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