nethttp

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Jun 11, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package nethttp adapts api/rest route handles to net/http handlers.

Each [RouteHandle] from api/rest becomes an http.Handler via Handler. Register wires it directly onto an http.ServeMux using the Go 1.22+ method-prefixed pattern ("POST /users", "GET /users/{id}", etc.).

Typical usage:

b := rest.NewBuilder(rest.Info{Title: "User API", Version: "1.0.0"})
createUser, _ := rest.NewRoute[CreateReq, User]("POST", "/users", ...).Register(b)

mux := http.NewServeMux()
nethttp.Register(mux, createUser, func(ctx context.Context, req CreateReq) (User, error) {
    // Access path params via the embedded request:
    r, _ := nethttp.RequestFromContext(ctx)
    id := r.PathValue("id")
    return svc.CreateUser(ctx, req)
}, nethttp.Options{})
http.ListenAndServe(":8080", mux)

Error responses use the JSON body {"error":"<message>"} by default: 400 for decode/validation failures, 500 for handler or encode errors. Override via Options.ErrorHandler.

For body-less methods (GET, HEAD, DELETE) the handler function is called with the zero value of Req. Access path and query parameters through RequestFromContext.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Handler

func Handler[Req, Resp any](handle *rest.RouteHandle[Req, Resp], fn HandlerFunc[Req, Resp], opts Options) http.Handler

Handler wraps a rest.RouteHandle and a HandlerFunc into an http.Handler.

For body-bearing methods (POST, PUT, PATCH) the request body is read, decoded, and validated using the route's codec before fn is called. For other methods (GET, HEAD, DELETE) fn is called with the zero value of Req.

On success the response is JSON-encoded and written with the HTTP status from the route descriptor's primary response (the first entry in Responses).

Pass a zero-value Options{} for default behaviour (JSON error envelope, 1 MiB body limit, application/json Content-Type check, no-op observer).

func Register

func Register[Req, Resp any](mux *http.ServeMux, handle *rest.RouteHandle[Req, Resp], fn HandlerFunc[Req, Resp], opts Options)

Register registers the route on mux using its method and path from the route descriptor. It uses the Go 1.22+ enhanced ServeMux pattern "METHOD /path" so each registration is scoped to a single method.

Pass a zero-value Options{} for default behaviour.

func RegisterSSE added in v0.8.0

func RegisterSSE[Req, Event any](mux *http.ServeMux, handle *rest.SSERouteHandle[Req, Event], fn SSEHandlerFunc[Req, Event], opts Options)

RegisterSSE wires an rest.SSERouteHandle onto mux as a GET SSE endpoint.

func RequestFromContext

func RequestFromContext(ctx context.Context) (*http.Request, bool)

RequestFromContext retrieves the *http.Request stored in ctx by Handler. Returns false if the context was not created by this package.

func ResponseHeadersFromContext added in v0.8.0

func ResponseHeadersFromContext(ctx context.Context) (http.Header, bool)

ResponseHeadersFromContext retrieves response headers previously stored by WithResponseHeaders. Returns false if no headers were set.

func SSEHandler added in v0.8.0

func SSEHandler[Req, Event any](handle *rest.SSERouteHandle[Req, Event], fn SSEHandlerFunc[Req, Event], opts Options) http.Handler

SSEHandler wraps an rest.SSERouteHandle and a user-supplied SSEHandlerFunc into an http.Handler that streams Server-Sent Events.

The handler sets Content-Type: text/event-stream, Cache-Control: no-cache, and Connection: keep-alive, then calls fn. The send func provided to fn validates the event via the codec, encodes it as JSON, writes "data: <json>\n\n" to the response, and flushes. If the event fails validation, send returns an error without writing anything.

fn should honour ctx.Done() for clean client-disconnect handling.

func SetCookie added in v0.8.0

func SetCookie(w http.ResponseWriter, name, value string, opts CookieOptions) error

SetCookie writes a Set-Cookie header on w with secure defaults: Secure, HttpOnly, SameSite=Strict, Path="/".

If CookieOptions.Codec is non-nil, value is validated first using that codec. A validation failure returns a rest.CookieParamError without writing any header — the same error type returned by rest.RouteHandle.ValidateCookies on the read side.

Example — symmetric read/write validation from a single codec:

sessionCodec := codex.String().Refine(validate.MinLen(32))

// Read (adapter validates automatically via CookieParam):
rest.CookieParam{Name: "session_token"}.WithCodec(sessionCodec)

// Write (handler sets the cookie with the same codec):
if err := nethttp.SetCookie(w, "session_token", token,
    nethttp.CookieOptions{MaxAge: 3600}.WithCodec(sessionCodec),
); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
}

func WithResponseCookies added in v0.8.0

func WithResponseCookies(ctx context.Context, cookies ...PendingCookie)

WithResponseCookies deposits one or more PendingCookie values into ctx. Handler validates their values against the route's [ResponseCookieParam] codecs and then writes Set-Cookie headers on success. Call this inside a HandlerFunc to emit response cookies.

func WithResponseHeaders added in v0.8.0

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

WithResponseHeaders copies the key-value pairs from h into the response header map stored in ctx (pre-allocated by Handler before calling the HandlerFunc). Call this inside a HandlerFunc to emit response headers such as Location, ETag, or custom headers without direct access to http.ResponseWriter.

resp, _ := svc.Create(ctx, req)
h := make(http.Header)
h.Set("Location", "/users/"+resp.ID)
nethttp.WithResponseHeaders(ctx, h) // mutates the header map in ctx

Types

type CookieOptions added in v0.8.0

type CookieOptions struct {
	// Path is the cookie path. Defaults to "/" when empty.
	Path string

	// Domain scopes the cookie to a specific host. Defaults to the current host.
	Domain string

	// MaxAge is the cookie lifetime in seconds.
	// 0 means session cookie (deleted when browser closes).
	// Negative means delete the cookie immediately.
	MaxAge int

	// SameSite controls the cross-site request behaviour.
	// Defaults to [http.SameSiteStrictMode] when zero.
	SameSite http.SameSite

	// Insecure, when true, omits the Secure attribute.
	// Use only for non-TLS environments such as localhost development.
	// Default: false (Secure is always set).
	Insecure bool

	// AllowJS, when true, omits the HttpOnly attribute, making the cookie
	// accessible via document.cookie. Required for patterns such as
	// CSRF tokens that must be read by client-side JavaScript.
	// Default: false (HttpOnly is always set).
	AllowJS bool

	// Codec, when non-nil, validates value before the Set-Cookie header is
	// written. Use the same [codex.Codec] as the matching [rest.CookieParam]
	// for symmetric read/write validation from a single definition.
	// Set via [CookieOptions.WithCodec] to avoid address-of boilerplate.
	//
	// If validation fails, SetCookie returns [rest.CookieParamError] and does
	// NOT write the Set-Cookie header.
	Codec *codex.Codec[string]
}

CookieOptions configures the security attributes and optional value validation applied by SetCookie.

Safe defaults: Secure=true, HttpOnly=true, SameSite=Strict, Path="/". Use the opt-in fields to relax specific attributes for legitimate use cases.

func (CookieOptions) WithCodec added in v0.8.0

func (o CookieOptions) WithCodec(c codex.Codec[string]) CookieOptions

WithCodec sets the validation codec and returns the updated CookieOptions. Avoids the temporary-variable + address-of pattern required when setting Codec inline:

err := nethttp.SetCookie(w, "session_token", token,
    nethttp.CookieOptions{MaxAge: 3600}.WithCodec(sessionCodec))

type HandlerFunc

type HandlerFunc[Req, Resp any] func(ctx context.Context, req Req) (Resp, error)

HandlerFunc is the typed application handler called by Handler. ctx is the request context. req is the decoded request value; for body-less methods it is the zero value of Req. Use RequestFromContext to access the underlying *http.Request for path parameters, headers, or other request metadata.

type Options

type Options struct {
	// ErrorHandler, when non-nil, is called instead of the default JSON error
	// envelope when a request fails. status is the suggested HTTP status code
	// (400 or 500). Implementations must write the response header and body.
	ErrorHandler func(w http.ResponseWriter, r *http.Request, status int, err error)

	// Observer, when non-nil, receives per-request lifecycle events: request
	// counts with latency and HTTP status, and per-field validation errors.
	// Defaults to [stats.NoopObserver] when nil.
	Observer stats.Observer

	// MaxBodyBytes limits the number of bytes read from the request body for
	// body-bearing methods (POST, PUT, PATCH). Zero means the default (1 MiB).
	// Requests exceeding the limit are rejected with 400 Bad Request.
	MaxBodyBytes int64

	// ContentType is the expected Content-Type for body-bearing methods (POST,
	// PUT, PATCH). When non-empty, requests whose Content-Type does not match
	// (ignoring parameters such as "; charset=utf-8") are rejected with
	// 415 Unsupported Media Type. Defaults to "application/json".
	ContentType string

	// MultiValueQueryParams, when true, passes the raw multi-value query map
	// (map[string][]string from r.URL.Query()) to [rest.RouteHandle.ValidateQueryMulti]
	// instead of the flat single-value map. Use when your routes use repeated query
	// keys such as "?tags=a&tags=b". When false (default), the first value per key
	// is validated via [rest.RouteHandle.ValidateQuery].
	MultiValueQueryParams bool

	// SecurityFunc, when non-nil, is called for routes that declare a non-nil
	// Security field (via [rest.RouteMeta.Security] or global security), after
	// parameter validation but before the handler fn.
	//
	// Return a non-nil error to reject the request with 401 Unauthorized.
	// reqs contains the route's declared security requirements (scheme names +
	// scopes). The adapter has already extracted and codec-validated the credential
	// from the request before calling SecurityFunc.
	//
	// Example — JWT bearer verification:
	//
	//	opts.SecurityFunc = func(ctx context.Context, r *http.Request, reqs []route.SecurityRequirement) error {
	//	    token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
	//	    return jwtlib.VerifyScopes(token, reqs)
	//	}
	SecurityFunc func(ctx context.Context, r *http.Request, reqs []route.SecurityRequirement) error
}

Options configures the behaviour of Handler and Register.

type PendingCookie added in v0.8.0

type PendingCookie struct {
	Name  string
	Value string
	Opts  CookieOptions
}

PendingCookie is a cookie queued to be validated and written as a Set-Cookie response header by Handler. Create one inside a HandlerFunc and deposit it via WithResponseCookies.

func ResponseCookiesFromContext added in v0.8.0

func ResponseCookiesFromContext(ctx context.Context) ([]PendingCookie, bool)

ResponseCookiesFromContext retrieves pending cookies previously stored by WithResponseCookies. Returns false if no cookies were queued.

type SSEHandlerFunc added in v0.8.0

type SSEHandlerFunc[Req, Event any] func(ctx context.Context, req Req, send func(Event) error) error

SSEHandlerFunc is the typed application handler called by SSEHandler. ctx is the request context (cancelled when the client disconnects). req is the decoded request (zero value for body-less GET requests). send encodes, validates, and writes one SSE event; it returns an error if the event fails codec validation or if the underlying write fails.

Jump to

Keyboard shortcuts

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