rack

package module
v0.0.0-...-0eeee2b Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 8 Imported by: 0

README

go-ruby-rack/rack

rack — go-ruby-rack

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of the deterministic core of Ruby's Rack — the SPEC value types and the pure-compute utilities of Rack::Utils, Rack::Request, Rack::Response, Rack::MediaType and the header machinery — matching the MRI rack gem (Rack 3.x) byte-for-byte. It shapes a Rack environment, parses and builds query strings and cookies, escapes and unescapes URI/HTML, maps HTTP status codes, and produces the [status, headers, body] SPEC tuple — without any Ruby runtime.

It is the Rack backend for go-embedded-ruby, but is a standalone, reusable module — a sibling of go-ruby-regexp (the Onigmo engine), go-ruby-erb (the ERB compiler) and go-ruby-yaml (the Psych port).

What it is — and isn't. Shaping the env hash, parsing parameters and cookies, escaping, status-code mapping and assembling the response tuple are all fully deterministic and need no interpreter, so they live here as pure Go. The HTTP server — Rack::Handler, the socket accept loop, TLS — is the host's job and is out of scope. Reading the request body is a single, explicit seam: the host supplies an Input backed by whatever IO it has, so this library never touches the network.

Features

A faithful port of Rack 3.x's pure-compute surface, validated against the rack gem on every supported platform:

  • Utils query parsingParseQuery / ParseNestedQuery expand foo[bar] / foo[] / x[][a] bracket nesting exactly like the gem (including the array-of-hash vs nested-array subtleties and the ParameterTypeError / ParamsTooDeepError conflicts), and BuildQuery / BuildNestedQuery invert them.
  • EscapingEscape / Unescape (encode/decode_www_form_component, space ↔ +), EscapePath / UnescapePath (RFC2396), and EscapeHTML / UnescapeHTML, byte-identical to MRI down to the unreserved set and upper-case hex.
  • Status codes — the full HTTPStatusCodes table, the symbol → code reverse map (with the deprecated aliases), and StatusWithNoEntityBody.
  • HeadersHeaders, the insertion-ordered, key-down-casing map mirroring Rack::Headers.
  • Content negotiationQValues, BestQMatch, SelectBestEncoding (Accept-Encoding), and GetByteRanges / byte-range parsing.
  • Path & security helpersCleanPathInfo (traversal-safe path normalisation), ValidPath, SecureCompare (constant-time), and ForwardedValues (RFC 7239 Forwarded parsing).
  • CookiesParseCookiesHeader / ParseCookies (from an Env), MakeCookieHeader / MakeDeleteCookieHeader, and the …Into(*Headers, …) mutators.
  • Request over an Env — method predicates, PathInfo / QueryString / GET / POST / Params / Cookies, ContentType / MediaType, Host / Port / Scheme / SSL / BaseURL / URL / Fullpath, XHR, IP (with the trusted-proxy filter) and the …Header accessors.
  • ResponseNewResponse(body, status, headers), Write, Finish / ToA (the [status, headers, body] tuple), SetStatus, header get/set, SetCookie / DeleteCookie, Redirect, and the status-class predicates.
  • MediaTypeMediaTypeOf and MediaTypeParams.

CGO-free, dependency-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x) and three OSes (Linux, macOS, Windows).

Install

go get github.com/go-ruby-rack/rack

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-rack/rack"
)

func main() {
	// Parse a nested query string into structural types.
	p, _ := rack.ParseNestedQuery("user[name]=ada&user[langs][]=go", "&",
		rack.DefaultParamDepthLimit)
	fmt.Println(p.Get("user")) // *rack.Params {"name"=>"ada", "langs"=>["go"]}

	// Shape a request over a Rack env.
	req := rack.NewRequest(rack.Env{
		rack.RequestMethod: "GET",
		rack.PathInfo:      "/search",
		rack.QueryString:   "q=hello+world",
		rack.HTTPHost:      "example.com",
		rack.RackURLScheme: "https",
	})
	fmt.Println(req.URL()) // https://example.com/search?q=hello+world
	get, _ := req.GET()
	fmt.Println(get.Get("q")) // "hello world"

	// Build a response and emit the SPEC tuple.
	res := rack.NewResponseString("Hello", 200, nil)
	res.SetContentType("text/plain")
	status, headers, body := res.Finish()
	fmt.Println(status, headers.Get("content-length"), body)
	// 200 5 [Hello]
}

The body/input seam

Reading the request body is the one place this library defers to the host. A Request reads env["rack.input"] through the small Input interface:

type Input interface {
	// Read returns up to n bytes, or all remaining bytes when n < 0, and
	// nil at EOF — the subset of Ruby's IO contract Rack relies on.
	Read(n int) ([]byte, error)
}

Request.POST / Request.Params read it only for form-data content types, parse the body with ParseNestedQuery, and memoise into the env exactly like the gem. The host (e.g. rbgo) supplies the Input over whatever socket or buffer it has, so the package stays free of any network or runtime dependency.

Value model

Parsed parameters and cookies are built from a small, fixed set of Go types — the analogue of the Ruby Hash/Array/String/nil graph the gem returns:

Ruby Go
Hash (ordered) *rack.Params
Array []any
String string
nil nil
response headers *rack.Headers (ordered, down-cased keys)

*Params preserves insertion order (like Ruby's Hash), so key order round-trips through BuildQuery / BuildNestedQuery.

Tests & coverage

The suite pairs deterministic, ruby-free tests (which alone hold coverage at 100%, so the qemu cross-arch and Windows lanes pass the gate) with a differential MRI oracle: every escaping, query-parse, status, cookie, media-type, byte-range and Response#finish case is also run through the system ruby with the rack gem and compared byte-for-byte. The oracle scripts $stdout.binmode so Windows text-mode never pollutes the bytes, and skip themselves where ruby or the gem is absent.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-rack/rack authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package rack is a pure-Go (no cgo) reimplementation of the deterministic, interpreter-independent core of Ruby's Rack — the SPEC value types and the pure-compute utilities — matching MRI's `rack` gem (Rack 3.x) byte-for-byte.

It models a Rack environment as a Env (a string-keyed map), and exposes Request and Response over it, plus the [Utils], [MediaType] and header helpers. The HTTP server itself (the socket accept loop, Rack::Handler) is NOT part of this package: it is the host's job. The body-reading seam — the `rack.input` IO — is supplied by the host through the Input interface, so this package stays free of any Ruby runtime or network dependency.

The package is the Rack backend for go-embedded-ruby, but is a standalone, reusable module — a sibling of go-ruby-regexp, go-ruby-erb and go-ruby-yaml.

Index

Constants

View Source
const (
	HTTPHost       = "HTTP_HOST"
	HTTPPort       = "HTTP_PORT"
	HTTPS          = "HTTPS"
	PathInfo       = "PATH_INFO"
	RequestMethod  = "REQUEST_METHOD"
	RequestPath    = "REQUEST_PATH"
	ScriptName     = "SCRIPT_NAME"
	QueryString    = "QUERY_STRING"
	ServerProtocol = "SERVER_PROTOCOL"
	ServerName     = "SERVER_NAME"
	ServerPort     = "SERVER_PORT"
	HTTPCookie     = "HTTP_COOKIE"

	// Response header keys (already lower-case, per the Rack 3 SPEC).
	CacheControl     = "cache-control"
	ContentLengthKey = "content-length"
	ContentTypeKey   = "content-type"
	ETagKey          = "etag"
	Expires          = "expires"
	SetCookie        = "set-cookie"
	TransferEncoding = "transfer-encoding"

	// HTTP method verbs.
	MethodGet     = "GET"
	MethodPost    = "POST"
	MethodPut     = "PUT"
	MethodPatch   = "PATCH"
	MethodDelete  = "DELETE"
	MethodHead    = "HEAD"
	MethodOptions = "OPTIONS"
	MethodConnect = "CONNECT"
	MethodLink    = "LINK"
	MethodUnlink  = "UNLINK"
	MethodTrace   = "TRACE"

	// Rack environment variables.
	RackURLScheme                = "rack.url_scheme"
	RackInput                    = "rack.input"
	RackErrors                   = "rack.errors"
	RackSession                  = "rack.session"
	RackRequestQueryHash         = "rack.request.query_hash"
	RackRequestQueryString       = "rack.request.query_string"
	RackRequestCookieHash        = "rack.request.cookie_hash"
	RackRequestCookieString      = "rack.request.cookie_string"
	RackRequestFormHash          = "rack.request.form_hash"
	RackRequestFormInput         = "rack.request.form_input"
	RackRequestFormVars          = "rack.request.form_vars"
	RackMethodOverrideOrigMethod = "rack.methodoverride.original_method"
)

Request env keys (Rack::* constants from constants.rb).

View Source
const DefaultParamDepthLimit = 32

DefaultParamDepthLimit is the default nesting depth allowed by parse_nested_query, matching Rack::Utils.default_query_parser (32). It guards against a rogue client triggering a stack overflow.

View Source
const DeleteCookieHeaderValue = "; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"

DeleteCookieHeaderValue is the standard expired-cookie payload Rack uses to instruct a client to drop a cookie: max-age 0 and an epoch expiry.

Variables

View Source
var HTTPStatusCodes = map[int]string{
	100: "Continue",
	101: "Switching Protocols",
	102: "Processing",
	103: "Early Hints",
	200: "OK",
	201: "Created",
	202: "Accepted",
	203: "Non-Authoritative Information",
	204: "No Content",
	205: "Reset Content",
	206: "Partial Content",
	207: "Multi-Status",
	208: "Already Reported",
	226: "IM Used",
	300: "Multiple Choices",
	301: "Moved Permanently",
	302: "Found",
	303: "See Other",
	304: "Not Modified",
	305: "Use Proxy",
	307: "Temporary Redirect",
	308: "Permanent Redirect",
	400: "Bad Request",
	401: "Unauthorized",
	402: "Payment Required",
	403: "Forbidden",
	404: "Not Found",
	405: "Method Not Allowed",
	406: "Not Acceptable",
	407: "Proxy Authentication Required",
	408: "Request Timeout",
	409: "Conflict",
	410: "Gone",
	411: "Length Required",
	412: "Precondition Failed",
	413: "Content Too Large",
	414: "URI Too Long",
	415: "Unsupported Media Type",
	416: "Range Not Satisfiable",
	417: "Expectation Failed",
	421: "Misdirected Request",
	422: "Unprocessable Content",
	423: "Locked",
	424: "Failed Dependency",
	425: "Too Early",
	426: "Upgrade Required",
	428: "Precondition Required",
	429: "Too Many Requests",
	431: "Request Header Fields Too Large",
	451: "Unavailable For Legal Reasons",
	500: "Internal Server Error",
	501: "Not Implemented",
	502: "Bad Gateway",
	503: "Service Unavailable",
	504: "Gateway Timeout",
	505: "HTTP Version Not Supported",
	506: "Variant Also Negotiates",
	507: "Insufficient Storage",
	508: "Loop Detected",
	511: "Network Authentication Required",
}

HTTPStatusCodes maps every standard HTTP status code to its reason phrase, matching Rack::Utils::HTTP_STATUS_CODES.

Functions

func BestQMatch

func BestQMatch(header string, available []string) string

BestQMatch returns the best available mime type for a quality-value Accept header, matching Rack::Utils.best_q_match. available is the server's set of concrete types; it returns "" when nothing matches.

func BuildNestedQuery

func BuildNestedQuery(value any, prefix string) (string, error)

BuildNestedQuery builds a query string from nested structural types, matching Rack::Utils.build_nested_query. value may be a *Params (Hash), []any (Array), a string scalar, or nil. prefix is the key prefix ("" at the top level).

func BuildQuery

func BuildQuery(p *Params) string

BuildQuery builds a flat query string from an ordered map of values, matching Rack::Utils.build_query: a []any value repeats the key, a nil value emits a bare key, and keys/values are escaped with Escape. Keys are emitted in p's insertion order.

func CleanPathInfo

func CleanPathInfo(pathInfo string) string

CleanPathInfo canonicalises a PATH_INFO the way Rack::Utils.clean_path_info does: it splits on '/', drops empty and "." segments, pops the last kept segment for each "..", and rejoins with '/'. A leading '/' is restored when the input was empty or began with a separator. This is the traversal-safe normalisation Rack::Files and the static middleware rely on.

func DeleteCookieHeaderInto

func DeleteCookieHeaderInto(h *Headers, key string, c CookieValue) error

DeleteCookieHeaderInto sets an expired cookie in the Headers, matching Rack::Utils.delete_cookie_header!.

func Escape

func Escape(s string) string

Escape escapes a string the way Rack::Utils.escape does — i.e. URI.encode_www_form_component: CGI form encoding where space becomes '+', the unreserved set (alphanumerics and *-._) is preserved, and every other byte is percent-encoded with upper-case hex.

func EscapeHTML

func EscapeHTML(s string) string

EscapeHTML escapes ampersands, angle brackets and quotes to their HTML/XML entities, matching Rack::Utils.escape_html (CGI.escapeHTML). The single quote becomes the numeric reference "&#39;", exactly as MRI emits.

It mirrors the table-driven native path of MRI's cgi/escape C core: a single pass over the bytes bulk-copies each verbatim run and splices in the entity for each escapable byte, straight into one output buffer. Inputs with nothing to escape return the original string with no allocation at all.

func EscapePath

func EscapePath(s string) string

EscapePath escapes a string like Rack::Utils.escape_path — RFC2396 URI path escaping, where (unlike Escape) space becomes "%20" rather than '+' and the path-safe punctuation set is preserved.

func ForwardedValues

func ForwardedValues(forwardedHeader string, hasHeader bool) (map[string][]string, bool)

ForwardedValues parses an RFC 7239 Forwarded header into its parameter lists, matching Rack::Utils.forwarded_values. It returns (nil, false) when the header is absent, contains a disallowed parameter, or exceeds the DoS guards (more than 1024 parameters or 1024 quoted escapes); otherwise it returns a map from the lower-cased parameter name ("by", "for", "host", "proto") to the ordered list of its values. present is false exactly when the Ruby method returns nil.

func MakeCookieHeader

func MakeCookieHeader(key string, c CookieValue) (string, error)

MakeCookieHeader builds the encoded set-cookie string for the given key and cookie, matching Rack::Utils.set_cookie_header. The cookie value(s) are escaped with Escape and joined with '&'. An invalid key yields an error.

func MakeDeleteCookieHeader

func MakeDeleteCookieHeader(key string, c CookieValue) (string, error)

MakeDeleteCookieHeader builds an encoded set-cookie string that deletes the named cookie, matching Rack::Utils.delete_set_cookie_header: an empty value, max-age 0 and an epoch expiry, plus any supplied attributes.

func MediaTypeOf

func MediaTypeOf(contentType string) string

MediaTypeOf returns the media type (type/subtype) portion of a CONTENT_TYPE string without parameters — e.g. "text/plain;charset=utf-8" → "text/plain" — matching Rack::MediaType.type. An empty content type yields "".

Ruby returns nil here; in Go we use the empty string to mean "no media type".

func SecureCompare

func SecureCompare(a, b string) bool

SecureCompare reports whether a and b are equal using a constant-time comparison, matching Rack::Utils.secure_compare. It returns false immediately when the lengths differ (as MRI does before the fixed-length compare), so it leaks length but not content, and is safe for comparing secrets such as CSRF tokens.

func SelectBestEncoding

func SelectBestEncoding(available []string, accept []QValue) (string, bool)

SelectBestEncoding picks the best available content-coding for an Accept-Encoding preference list, matching Rack::Utils.select_best_encoding. available is the server's ordered coding list (its order breaks quality ties); accept is the parsed Accept-Encoding as value/quality pairs (typically from QValues). Only the first 16 accept entries are considered. A "*" expands to every available coding not named explicitly; "identity" is always an implicit candidate unless disqualified by a q=0. It returns ("", false) when nothing is acceptable (the Ruby nil).

func SetCookieHeaderInto

func SetCookieHeaderInto(h *Headers, key string, c CookieValue) error

SetCookieHeaderInto appends a set-cookie value to a Headers, converting an existing single value to a []any list, matching Rack::Utils.set_cookie_header!.

func StatusWithNoEntityBody

func StatusWithNoEntityBody(status int) bool

StatusWithNoEntityBody reports whether a response with the given status code must not carry an entity body — the 1xx range plus 204 and 304 — matching Rack::Utils::STATUS_WITH_NO_ENTITY_BODY. This is the predicate form the `status_with_no_entity_body?` helper exposes.

func SymbolToStatusCode

func SymbolToStatusCode(sym string) (code int, ok bool)

SymbolToStatusCode returns the HTTP code for a status symbol (e.g. "not_found" → 404), resolving the deprecated aliases too. ok is false for an unrecognised symbol, mirroring how Rack::Utils.status_code raises ArgumentError in that case.

func TrustedProxy

func TrustedProxy(ip string) bool

TrustedProxy reports whether ip is a trusted proxy / loopback / private address, matching Rack::Request.ip_filter. Hosts that terminate TLS behind a reverse proxy can rely on this when deciding which forwarded address to trust.

func Unescape

func Unescape(s string) (string, error)

Unescape reverses Rack::Utils.escape (URI.decode_www_form_component): '+' becomes a space and %XX is decoded. An invalid percent-escape (a '%' not followed by two hex digits) makes the whole operation fail the way MRI raises ArgumentError, which this surfaces as a non-nil error.

func UnescapeHTML

func UnescapeHTML(s string) string

UnescapeHTML reverses EscapeHTML, decoding the named and numeric character references CGI.unescapeHTML understands for the bytes EscapeHTML emits, plus the common decimal/hex numeric forms.

func UnescapePath

func UnescapePath(s string) string

UnescapePath reverses EscapePath, decoding %XX escapes. Unlike Unescape it does not turn '+' into a space (mirroring URI::RFC2396_PARSER.unescape, which only undoes percent-encoding). An invalid escape is left verbatim, matching Ruby's lenient path unescaper.

func ValidPath

func ValidPath(path string) bool

ValidPath reports whether path is a valid request path, matching Rack::Utils.valid_path?: the bytes must be valid UTF-8 and contain no NUL.

Types

type ByteRange

type ByteRange struct {
	Start int
	End   int
}

ByteRange is a satisfiable byte range [Start, End] (inclusive), the Go analogue of the Ruby Range objects get_byte_ranges returns.

func GetByteRanges

func GetByteRanges(httpRange string, size, maxRanges int) ([]ByteRange, bool)

GetByteRanges parses a Range header value against a resource of the given size, matching Rack::Utils.get_byte_ranges. It returns (nil, false) when the header is missing or syntactically invalid, and an empty slice when no range is satisfiable. maxRanges caps the number of comma-separated ranges (use 100 for the Rack default).

func (ByteRange) Size

func (r ByteRange) Size() int

Size returns the number of bytes the range covers.

type CookieValue

type CookieValue struct {
	Value       string
	Values      []string // multiple values, joined with '&' (Ruby Array form)
	HasValues   bool     // use Values instead of Value
	Domain      string
	Path        string
	MaxAge      string
	Expires     string // pre-formatted httpdate (host supplies it)
	Secure      bool
	HTTPOnly    bool
	Partitioned bool
	SameSite    string // "", "none", "lax" or "strict"
}

CookieValue holds the cookie payload and attributes for SetCookieHeader, mirroring the Hash form Rack::Utils.set_cookie_header accepts. A zero CookieValue with only Value set produces a bare "key=value" cookie. The HTTPOnlySet/SecureSet/PartitionedSet booleans gate emission of those valueless attributes.

type Env

type Env map[string]any

Env is a Rack environment: a string-keyed map of CGI-style variables and rack.* entries. It is the substrate Request reads from and writes to. Values are usually strings; rack.input is an Input.

type ErrInvalidCookieKey

type ErrInvalidCookieKey struct{ Key string }

ErrInvalidCookieKey is returned by SetCookieHeader for a key that violates RFC6265, matching the ArgumentError Ruby raises.

func (*ErrInvalidCookieKey) Error

func (e *ErrInvalidCookieKey) Error() string

type ErrInvalidSameSite

type ErrInvalidSameSite struct{ Value string }

ErrInvalidSameSite is returned for an unrecognised SameSite value.

func (*ErrInvalidSameSite) Error

func (e *ErrInvalidSameSite) Error() string

type Headers

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

Headers is the Go analogue of Rack::Headers — an insertion-ordered map that downcases every string key on the way in, so a Rack-2-style "Content-Type" and a Rack-3-style "content-type" address the same slot. A value is typically a string or a []any (a header with multiple values, e.g. several set-cookie lines). Non-string keys are not used by Rack response headers, so Headers keys are always strings.

func HeadersOf

func HeadersOf(m map[string]any) *Headers

HeadersOf builds a Headers from a plain map, downcasing keys. It mirrors constructing Rack::Headers and assigning each pair. Iteration order of a Go map is unspecified, so callers needing deterministic order should Set keys individually.

func NewHeaders

func NewHeaders() *Headers

NewHeaders returns an empty Headers.

func (*Headers) Delete

func (h *Headers) Delete(key string) bool

Delete removes the (case-insensitive) key, returning whether it was present.

func (*Headers) Each

func (h *Headers) Each(fn func(key string, val any) bool)

Each iterates key/value pairs in insertion order. Returning false from fn stops iteration.

func (*Headers) Get

func (h *Headers) Get(key string) any

Get returns the value for key, or nil if absent.

func (*Headers) GetOK

func (h *Headers) GetOK(key string) (any, bool)

GetOK returns the value for key and whether it was present.

func (*Headers) Has

func (h *Headers) Has(key string) bool

Has reports whether the (case-insensitive) key is present.

func (*Headers) Keys

func (h *Headers) Keys() []string

Keys returns the (already down-cased) keys in insertion order.

func (*Headers) Len

func (h *Headers) Len() int

Len reports the number of header keys.

func (*Headers) Set

func (h *Headers) Set(key string, val any)

Set assigns the down-cased key to val.

func (*Headers) ToMap

func (h *Headers) ToMap() map[string]any

ToMap returns a plain map snapshot of the headers (losing order).

type Input

type Input interface {
	// Read returns up to n bytes, or all remaining bytes when n < 0. It returns
	// nil at EOF with no bytes read, matching IO#read's nil-at-EOF behaviour.
	Read(n int) ([]byte, error)
}

Input is the body-reading seam (env["rack.input"]). The host supplies an implementation backed by whatever IO it has; this package never reads the network itself. It mirrors the subset of the Ruby IO contract Rack relies on.

type InvalidParameterError

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

InvalidParameterError is raised when a parameter has an invalid byte sequence or %-encoding, corresponding to Rack::QueryParser::InvalidParameterError.

func (*InvalidParameterError) Error

func (e *InvalidParameterError) Error() string

type ParameterTypeError

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

ParameterTypeError is raised when nested parameters parsed by ParseNestedQuery contain conflicting structural types (e.g. a key used both as a scalar and as an array/hash). It corresponds to Rack::QueryParser::ParameterTypeError.

func (*ParameterTypeError) Error

func (e *ParameterTypeError) Error() string

type Params

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

Params is an insertion-ordered string-keyed map, the Go analogue of Ruby's Hash (which preserves insertion order). parse_query / parse_nested_query and the Request param accessors return values built from Params, []any, string and nil — the same small value model the rack gem produces. A nested value is one of:

  • nil (a bare key with no '=')
  • string (a scalar value)
  • []any (an array, from foo[] nesting or repeated parse_query keys)
  • *Params (a sub-hash, from foo[bar] nesting)

func MediaTypeParams

func MediaTypeParams(contentType string) *Params

MediaTypeParams parses the parameters of a CONTENT_TYPE string into an ordered map — e.g. "text/plain;charset=utf-8" → {"charset" => "utf-8"} — matching Rack::MediaType.params. Parameter names are down-cased; a parameter with no value (or an empty value) maps to "". Surrounding double quotes on a value are stripped.

func NewParams

func NewParams() *Params

NewParams returns an empty ordered map.

func ParseCookies

func ParseCookies(env Env) *Params

ParseCookies extracts and parses the Cookie header (env["HTTP_COOKIE"]) from a Rack environment, matching Rack::Utils.parse_cookies. A missing or non-string HTTP_COOKIE parses as an empty header.

func ParseCookiesHeader

func ParseCookiesHeader(value string) *Params

ParseCookiesHeader parses a Cookie header value into an ordered map of cookie key to value, matching Rack::Utils.parse_cookies_header (RFC6265, splitting on ';'). The first occurrence of a key wins. A cookie with no '=' maps to a nil value; a value is unescaped, falling back to the raw bytes if it cannot be.

func ParseNestedQuery

func ParseNestedQuery(qs, sep string, depthLimit int) (*Params, error)

ParseNestedQuery parses a query string into structural types — *Params, []any and string — expanding foo[bar] and foo[] bracket nesting exactly like Rack::Utils.parse_nested_query. depthLimit caps nesting (use DefaultParamDepthLimit). Conflicting types raise ParameterTypeError and over-deep nesting raises ParamsTooDeepError.

func ParseQuery

func ParseQuery(qs, sep string) (*Params, error)

ParseQuery parses a query string into a *Params, collapsing repeated keys into a []any (in order) the way Rack::Utils.parse_query does. A bare key (no '=') maps to nil; an empty value maps to "". The sep argument is the delimiter set, defaulting to "&" when empty.

func (*Params) Delete

func (p *Params) Delete(key string) (any, bool)

Delete removes key, returning its value (or nil) and whether it was present.

func (*Params) Each

func (p *Params) Each(fn func(key string, val any) bool)

Each iterates key/value pairs in insertion order. If fn returns false the iteration stops.

func (*Params) Get

func (p *Params) Get(key string) (any, bool)

Get returns the value for key and whether it was present.

func (*Params) Has

func (p *Params) Has(key string) bool

Has reports whether key is present.

func (*Params) Keys

func (p *Params) Keys() []string

Keys returns the keys in insertion order. The slice is a copy and safe to mutate.

func (*Params) Len

func (p *Params) Len() int

Len reports the number of keys.

func (*Params) Merge

func (p *Params) Merge(other *Params) *Params

Merge returns a new *Params containing this map's pairs overlaid with other's (other wins on a key collision), mirroring Hash#merge used by Request#params.

func (*Params) Set

func (p *Params) Set(key string, val any)

Set assigns key to val, appending it to the key order if new.

func (*Params) ToMap

func (p *Params) ToMap() map[string]any

ToMap returns a plain Go map snapshot (losing key order), convenient for callers that do not care about ordering.

type ParamsTooDeepError

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

ParamsTooDeepError is raised when nested parameters exceed the configured depth limit, corresponding to Rack::QueryParser::ParamsTooDeepError.

func (*ParamsTooDeepError) Error

func (e *ParamsTooDeepError) Error() string

type QValue

type QValue struct {
	Value   string
	Quality float64
}

QValue is one entry of an Accept-style header: a media/encoding value and its quality weight.

func QValues

func QValues(header string) []QValue

QValues parses a comma-separated quality-value header (e.g. "text/html;q=0.8, application/json") into value/quality pairs, matching Rack::Utils.q_values. A missing q parameter defaults to quality 1.0.

type Request

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

Request provides a convenient, stateless interface over a Rack Env. It is a faithful port of the pure-compute parts of Rack::Request; the env passed in is referenced directly and may be mutated by the setters and the GET/POST memoisation, exactly like the Ruby class.

func NewRequest

func NewRequest(env Env) *Request

NewRequest wraps env in a Request. env must be non-nil.

func (*Request) Authority

func (r *Request) Authority() string

Authority returns the request authority (host[:port]), preferring HTTP_HOST then SERVER_NAME/SERVER_PORT, matching Request#authority (without the Forwarded header, which the host can layer on).

func (*Request) BaseURL

func (r *Request) BaseURL() string

BaseURL returns scheme://host[:port], matching Request#base_url.

func (*Request) Body

func (r *Request) Body() Input

Body returns the rack.input Input, or nil if unset.

func (*Request) ContentCharset

func (r *Request) ContentCharset() string

ContentCharset returns the charset media-type parameter, or "" if none.

func (*Request) ContentType

func (r *Request) ContentType() string

ContentType returns CONTENT_TYPE, or "" if absent or empty (Request#content_type returns nil there; we use "").

func (*Request) Cookies

func (r *Request) Cookies() *Params

Cookies returns the parsed Cookie header, memoised into rack.request.cookie_hash, matching Request#cookies.

func (*Request) DeleteHeader

func (r *Request) DeleteHeader(name string) any

DeleteHeader removes name from the env, returning its prior value.

func (*Request) Env

func (r *Request) Env() Env

Env returns the underlying environment.

func (*Request) Fullpath

func (r *Request) Fullpath() string

Fullpath returns the path plus the query string when present, matching Request#fullpath.

func (*Request) GET

func (r *Request) GET() (*Params, error)

GET returns the parsed query-string parameters, memoised into rack.request.query_hash, matching Request#GET.

func (*Request) GetHeader

func (r *Request) GetHeader(name string) string

GetHeader returns the env value for name (string-typed), or "" if absent or non-string. Use GetHeaderRaw for the untyped value.

func (*Request) GetHeaderRaw

func (r *Request) GetHeaderRaw(name string) (any, bool)

GetHeaderRaw returns the raw env value for name and whether it was present.

func (*Request) HasHeader

func (r *Request) HasHeader(name string) bool

HasHeader reports whether name is set in the env.

func (*Request) Host

func (r *Request) Host() string

Host returns the host portion of the authority, matching Request#host.

func (*Request) HostWithPort

func (r *Request) HostWithPort() string

HostWithPort returns the host, including the port only when it differs from the scheme's default, matching Request#host_with_port.

func (*Request) Hostname

func (r *Request) Hostname() string

Hostname returns the address portion (IPv6 brackets removed), matching Request#hostname.

func (*Request) IP

func (r *Request) IP() string

IP returns the originating client IP, walking REMOTE_ADDR and the X-Forwarded-For chain past trusted proxies, matching Request#ip.

func (*Request) IsDelete

func (r *Request) IsDelete() bool

func (*Request) IsGet

func (r *Request) IsGet() bool

Method predicates.

func (*Request) IsHead

func (r *Request) IsHead() bool
func (r *Request) IsLink() bool

func (*Request) IsOptions

func (r *Request) IsOptions() bool

func (*Request) IsPatch

func (r *Request) IsPatch() bool

func (*Request) IsPost

func (r *Request) IsPost() bool

func (*Request) IsPut

func (r *Request) IsPut() bool

func (*Request) IsTrace

func (r *Request) IsTrace() bool
func (r *Request) IsUnlink() bool

func (*Request) MediaType

func (r *Request) MediaType() string

MediaType returns the media type of CONTENT_TYPE, matching Request#media_type.

func (*Request) MediaTypeParams

func (r *Request) MediaTypeParams() *Params

MediaTypeParams returns the CONTENT_TYPE parameters, matching Request#media_type_params.

func (*Request) POST

func (r *Request) POST() (*Params, error)

POST parses the request body as form data when the content type is a form-data media type, memoising into rack.request.form_hash. It returns an empty Params when there is no rack.input or the body is not form data, matching Request#POST. The body is read through the Input seam.

func (*Request) Params

func (r *Request) Params() (*Params, error)

Params returns the union of GET and POST, with POST winning on collisions, matching Request#params.

func (*Request) Path

func (r *Request) Path() string

Path returns SCRIPT_NAME + PATH_INFO, matching Request#path.

func (*Request) PathInfo

func (r *Request) PathInfo() string

PathInfo returns PATH_INFO, defaulting to "".

func (*Request) Port

func (r *Request) Port() int

Port returns the request port, falling back through the authority, the scheme's default port and SERVER_PORT, matching Request#port.

func (*Request) QueryString

func (r *Request) QueryString() string

QueryString returns QUERY_STRING, defaulting to "".

func (*Request) RequestMethod

func (r *Request) RequestMethod() string

RequestMethod returns the REQUEST_METHOD (e.g. "GET").

func (*Request) SSL

func (r *Request) SSL() bool

SSL reports whether the scheme is https or wss, matching Request#ssl?.

func (*Request) Scheme

func (r *Request) Scheme() string

Scheme returns the request scheme, honouring HTTPS, X-Forwarded-SSL and rack.url_scheme, matching Request#scheme (the forwarded-Proto header path is covered by ForwardedScheme below).

func (*Request) ScriptName

func (r *Request) ScriptName() string

ScriptName returns SCRIPT_NAME, defaulting to "".

func (*Request) ServerName

func (r *Request) ServerName() string

ServerName returns SERVER_NAME.

func (*Request) ServerPort

func (r *Request) ServerPort() string

ServerPort returns SERVER_PORT.

func (*Request) SetHeader

func (r *Request) SetHeader(name string, v any)

SetHeader sets name to v in the env.

func (*Request) URL

func (r *Request) URL() string

URL returns the reconstructed request URL, matching Request#url.

func (*Request) XHR

func (r *Request) XHR() bool

XHR reports whether HTTP_X_REQUESTED_WITH is "XMLHttpRequest", matching Request#xhr?.

type Response

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

Response is a convenient builder for a Rack response — a faithful port of the pure-compute parts of Rack::Response. It accumulates a status, a Headers and a buffered body, and produces the SPEC `[status, headers, body]` tuple via Response.Finish / Response.ToA.

func NewResponse

func NewResponse(body []string, status int, headers *Headers) *Response

NewResponse builds a Response with the given body, status and headers, like Rack::Response.new(body, status, headers). A nil body yields an empty, length-unknown buffered response; a single string body is buffered with its byte length recorded. status defaults are the caller's responsibility (pass 200 for the Ruby default). headers may be nil.

func NewResponseString

func NewResponseString(body string, status int, headers *Headers) *Response

NewResponseString is the common case of a single string body.

func ResponseTuple

func ResponseTuple(status int, headers *Headers, body []string) *Response

ResponseTuple builds a Response from the SPEC `[status, headers, body]` argument order, matching Rack::Response.[](status, headers, body).

func (*Response) Accepted

func (r *Response) Accepted() bool

func (*Response) AddHeader

func (r *Response) AddHeader(key string, value any) any

AddHeader adds value to key, promoting an existing single value to a list when a second value is added, matching Response#add_header. A nil value is a no-op (returns the current value).

func (*Response) BadRequest

func (r *Response) BadRequest() bool

func (*Response) Body

func (r *Response) Body() []string

Body returns the buffered body parts.

func (*Response) ClientError

func (r *Response) ClientError() bool

func (*Response) ContentLength

func (r *Response) ContentLength() int

ContentLength returns the content-length header as an int, or -1 if unset.

func (*Response) ContentType

func (r *Response) ContentType() string

ContentType returns the content-type header value, or "" if unset.

func (*Response) Created

func (r *Response) Created() bool

func (*Response) DeleteCookie

func (r *Response) DeleteCookie(key string, cookie CookieValue) error

DeleteCookie sets an expiring set-cookie header for key, matching Response#delete_cookie.

func (*Response) DeleteHeader

func (r *Response) DeleteHeader(key string)

DeleteHeader removes key.

func (*Response) Empty

func (r *Response) Empty() bool

Empty reports whether the buffered body has no parts, matching Response#empty?.

func (*Response) Finish

func (r *Response) Finish() (status int, headers *Headers, body []string)

Finish produces the SPEC `[status, headers, body]` tuple, matching Response#finish. For a no-entity-body status it strips content-type and content-length and returns an empty body; otherwise it sets content-length from the buffered length (unless chunked).

func (*Response) Forbidden

func (r *Response) Forbidden() bool

func (*Response) GetHeader

func (r *Response) GetHeader(key string) any

GetHeader returns the value for key.

func (*Response) HasHeader

func (r *Response) HasHeader(key string) bool

HasHeader reports whether key is set.

func (*Response) Headers

func (r *Response) Headers() *Headers

Headers returns the response Headers.

func (*Response) Informational

func (r *Response) Informational() bool

func (*Response) Invalid

func (r *Response) Invalid() bool

Status-class predicates (Response::Helpers).

func (*Response) IsRedirect

func (r *Response) IsRedirect() bool

IsRedirect reports whether the status is a redirect code, matching Response#redirect?.

func (*Response) Location

func (r *Response) Location() string

Location returns the location header value, or "" if unset.

func (*Response) MediaType

func (r *Response) MediaType() string

MediaType returns the media type of the content-type header.

func (*Response) MediaTypeParams

func (r *Response) MediaTypeParams() *Params

MediaTypeParams returns the content-type parameters.

func (*Response) MethodNotAllowed

func (r *Response) MethodNotAllowed() bool

func (*Response) MovedPermanently

func (r *Response) MovedPermanently() bool

func (*Response) NoContent

func (r *Response) NoContent() bool

func (*Response) NotAcceptable

func (r *Response) NotAcceptable() bool

func (*Response) NotFound

func (r *Response) NotFound() bool

func (*Response) OK

func (r *Response) OK() bool

func (*Response) PreconditionFailed

func (r *Response) PreconditionFailed() bool

func (*Response) Redirect

func (r *Response) Redirect(target string, status int)

Redirect sets the status (default 302) and location header, matching Response#redirect.

func (*Response) Redirection

func (r *Response) Redirection() bool

func (*Response) RequestTimeout

func (r *Response) RequestTimeout() bool

func (*Response) ServerError

func (r *Response) ServerError() bool

func (*Response) SetContentType

func (r *Response) SetContentType(ct string)

SetContentType sets the content-type header (Response#content_type=).

func (*Response) SetCookie

func (r *Response) SetCookie(key string, cookie CookieValue) error

SetCookie appends a set-cookie header for key/cookie, matching Response#set_cookie.

func (*Response) SetHeader

func (r *Response) SetHeader(key string, value any)

SetHeader sets key to value.

func (*Response) SetLocation

func (r *Response) SetLocation(loc string)

SetLocation sets the location header.

func (*Response) SetStatus

func (r *Response) SetStatus(status int)

SetStatus sets the response status code (Response#status=).

func (*Response) Status

func (r *Response) Status() int

Status returns the response status code.

func (*Response) Successful

func (r *Response) Successful() bool

func (*Response) ToA

func (r *Response) ToA() (int, *Headers, []string)

ToA is an alias for Finish (Response#to_a).

func (*Response) Unauthorized

func (r *Response) Unauthorized() bool

func (*Response) Unprocessable

func (r *Response) Unprocessable() bool

func (*Response) Write

func (r *Response) Write(chunk string)

Write appends a chunk to the buffered body, updating the byte length, matching Response#write.

Jump to

Keyboard shortcuts

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