http

package module
v0.0.0-...-e85ce36 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: 7 Imported by: 0

README

go-ruby-http/http

http — go-ruby-http

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of the deterministic core of Ruby's http gem — "http.rb" — the chainable HTTP.get(...) client (not Ruby's stdlib net/http). It reproduces the chainable client DSL, the request/response objects, the status / headers / content-type / cookie value model, the form / JSON / params body encodings, redirect following, and the HTTP::Error tree — without any Ruby runtime.

It is the http.rb client for go-embedded-ruby, but a standalone, reusable module — a sibling of go-ruby-faraday, go-ruby-regexp and go-ruby-erb.

What it is — and isn't. Everything http.rb does around the wire is deterministic and needs no interpreter, so it lives here as pure Go: branching the chainable client (headers / auth / accept / timeout / follow / via / persistent), building the request (URL, merged headers, form/JSON/raw/ params body encoding), following redirects, and exposing the Response (status predicates, headers, content-type, JSON parse, cookies). The HTTP round-trip itself is a host seam: the Transport performs the transport. The default production Transport is NetTransport, backed by net/http; tests inject a TransportFunc stub and the core opens no socket itself.

Features

Faithful port of the http gem's client core, validated byte-for-byte against the gem (MRI) on every platform where it is installed:

  • Chainable client — package-level http.Get/Post/Put/Delete/Head/Patch and the builder chain http.NewClient().Headers(...).Auth(...).BasicAuth(u,p).Accept("json").Timeout(30).Follow().Via(host,port).Get(url), every link returning a new branched *Client (immutable, like http.rb).
  • Verb methodsGet/Head/Post/Put/Delete/Patch/Options/Trace/Connect and the general Request(verb, uri, opts...).
  • Body encodings — request options Form(*Values) (a=1&b=x+y&c[]=1&c[]=2), JSON(any) (application/json; charset=utf-8), Body(string) (raw), and Params(*Values) (merged into the query string). Precedence body ▸ form ▸ json, matching the gem; a GET strips Content-Type.
  • ResponseStatus() (a Status with .Success()/.Redirect()/ .ClientError()/.ServerError()/.Informational(), .Reason(), .Code()), Body().String(), Headers(), ContentType(), Parse() (JSON by Content-Type, or an override), and Cookies().
  • Persistent connectionshttp.Persistent(host) { |c| ... } pins a client to one host; a request to another host raises a StateError.
  • Redirect followingFollow() mirrors HTTP::Redirector: max-hops cap, endless-loop detection, strict/non-strict verb downgrade (301/302/303), cross-origin credential stripping.
  • Error treeHTTP::ErrorConnectionError, RequestError, ResponseError (→ StateError), TimeoutError, HeaderError, matched with errors.Is against the Err* sentinels (a superclass matches its subclasses).
  • Transport seamclient.WithTransport(Transport); DefaultTransport() is the net/http transport, a TransportFunc a test stub. The core never opens a socket. A host wires DefaultClientTransport once for the package-level shortcuts.

CGO-free, dependency-free (stdlib only), 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x — big-endian).

Install

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

Usage

package main

import (
	"errors"
	"fmt"

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

func main() {
	resp, err := http.NewClient().
		Headers(http.KV{Key: "X-Trace", Val: "1"}).
		Auth("Bearer tok").
		Accept("json").
		Timeout(30).
		Follow().
		Get("https://api.example.com/widgets")
	if err != nil {
		// a *http.Error: errors.Is(err, http.ErrTimeoutError), etc.
		if errors.Is(err, http.ErrConnectionError) {
			return
		}
		return
	}
	fmt.Println(resp.Status().Code(), resp.Status().Success())
	fmt.Println(resp.Body().String())

	if resp.Status().Success() {
		v, _ := resp.Parse() // JSON parsed by Content-Type
		fmt.Println(v)
	}
}
POST body encodings
http.Post("https://api.example.com/widgets", http.JSON(map[string]any{"name": "gadget"}))
http.Post("https://api.example.com/form", http.Form(http.NewValues(http.KV{"a", "1"})))
http.Post("https://api.example.com/raw", http.Body("raw bytes"))
http.Get("https://api.example.com/search", http.Params(http.NewValues(http.KV{"q", "go http"})))
Injecting a transport (tests / hosts)
c := http.NewClient().WithTransport(http.TransportFunc(func(req *http.Request) (*http.Response, error) {
	return http.NewResponse(200,
		http.NewHeaders(http.KV{"Content-Type", "application/json"}),
		`{"ok":true}`, "1.1", req.URL), nil
}))
resp, _ := c.Get("/ping")
v, _ := resp.Parse() // map[string]any{"ok": true}

Value model

gem this package
HTTP.get/post/...(url, opts) http.Get/Post/...(url, opts...)
HTTP.headers(...).auth(...).get(url) http.NewClient().Headers(...).Auth(...).Get(url)
HTTP.post(url, form:/json:/body:/params:) http.Post(url, http.Form/JSON/Body/Params(...))
HTTP::Response#status (.success? …) (*Response).Status() (.Success() …)
HTTP::Response#body.to_s / #parse (*Response).Body().String() / .Parse()
HTTP::Response#content_type / #cookies (*Response).ContentType() / .Cookies()
HTTP.persistent(host) { ... } http.Persistent(host, func(c *Client) error {...})
HTTP.follow (HTTP::Redirector) client.Follow()
HTTP::Error subtree *Error + Err* sentinels (errors.Is)
HTTP::ContentType.parse ParseContentType
HTTP::Headers::Normalizer NormalizeHeaderName
HTTP::FormData::Urlencoded (*Values).Encode / EscapeFormComponent

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 oracle against the reference http gem: header-name normalization, url-encoded form bodies, URI.encode_www_form_component escaping, content-type parsing, status reason phrases / to_s, and Basic-auth header construction are diffed byte-for-byte against the gem. The oracle scripts $stdout.binmode and skip themselves where the gem is absent. No test opens a socket — the transport is stubbed everywhere.

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-http/http 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 http is a pure-Go (CGO-free) reimplementation of the deterministic core of Ruby's `http` gem — the chainable "http.rb" HTTP client (HTTP.get/post/…), NOT Ruby's stdlib net/http. It reproduces the chainable client DSL, the request/response objects, the status/headers/content-type/ cookie value model, the form/JSON/params body encodings, redirect following, and the HTTP::Error tree that http.rb raises — without any Ruby runtime.

What it is — and isn't

Everything http.rb does *around* the wire is deterministic and needs no interpreter, so it lives here as pure Go: branching the chainable client (headers/auth/basic_auth/accept/timeout/follow/via/persistent), building the request (URL, merged headers, form/JSON/raw/params body encoding), following redirects, and exposing the Response (status predicates, headers, content-type, JSON parse, cookies). The HTTP round-trip itself is a host seam: the Transport performs the transport. The default production Transport is NetTransport, backed by net/http; tests inject a TransportFunc stub, and the core opens no socket itself.

Flow

resp, err := http.NewClient().
	Headers(http.KV{Key: "X-Trace", Val: "1"}).
	Auth("Bearer tok").
	Accept("json").
	Timeout(30).
	Follow().
	Get("https://api.example.com/widgets")
if err != nil { /* an *http.Error: TimeoutError, ConnectionError, … */ }
_ = resp.Status().Success()   // true for 2xx
_ = resp.Body().String()      // raw body
v, _ := resp.Parse()          // parsed JSON (by Content-Type)

A POST with a body encoding:

resp, err := http.Post("https://api.example.com/widgets",
	http.JSON(map[string]any{"name": "gadget"}))

Value model

A host (go-embedded-ruby / rbgo) maps its Ruby HTTP::Client / HTTP::Request / HTTP::Response / HTTP::Headers / HTTP::Response::Status objects to and from these Go shapes, and supplies the production Transport.

Index

Constants

View Source
const DefaultMaxRedirects = 5

DefaultMaxRedirects is HTTP::Redirector's default max_hops.

Variables

View Source
var (
	ErrError           = &Error{Kind: KindError, Message: string(KindError)}
	ErrConnectionError = &Error{Kind: KindConnectionError, Message: string(KindConnectionError)}
	ErrRequestError    = &Error{Kind: KindRequestError, Message: string(KindRequestError)}
	ErrResponseError   = &Error{Kind: KindResponseError, Message: string(KindResponseError)}
	ErrStateError      = &Error{Kind: KindStateError, Message: string(KindStateError)}
	ErrTimeoutError    = &Error{Kind: KindTimeoutError, Message: string(KindTimeoutError)}
	ErrHeaderError     = &Error{Kind: KindHeaderError, Message: string(KindHeaderError)}
)

Sentinel errors for errors.Is matching. A concrete Error whose Kind is the sentinel's Kind (or a descendant of it) matches via Error.Is.

Functions

func BasicAuthHeader

func BasicAuthHeader(user, pass string) string

BasicAuthHeader builds the HTTP Basic Authorization header value for a user and password: "Basic " followed by base64("user:password").

func EscapeFormComponent

func EscapeFormComponent(s string) string

EscapeFormComponent percent-encodes s the way Ruby's URI.encode_www_form_component (used by HTTP::FormData) does: the set [A-Za-z0-9*-._] is left literal, a space becomes '+', and every other byte becomes %XX with upper-case hex.

func NormalizeHeaderName

func NormalizeHeaderName(name string) string

NormalizeHeaderName canonicalizes a header name the way http.rb's HTTP::Headers::Normalizer does: split on '-' and '_', capitalize each part (first letter upper, the rest lower) and join with '-'. So "content_type" and "CONTENT-TYPE" both become "Content-Type", and "WWW-Authenticate" becomes "Www-Authenticate".

func Persistent

func Persistent(host string, block func(*Client) error) error

Persistent runs block against a persistent client pinned to host (HTTP.persistent(host) { |http| ... }).

Types

type Client

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

Client is an http.rb chainable client (HTTP::Client, including HTTP::Chainable): an immutable configuration (Options) plus a Transport. Each chainable method (Headers/Auth/Accept/…) returns a new branched client, so a base client can be shared and specialized without mutation, exactly like http.rb's chainable DSL. The verb methods issue requests through the transport.

func Accept

func Accept(typ string) *Client

Accept starts a chain by setting the Accept header (HTTP.accept).

func Auth

func Auth(value string) *Client

Auth starts a chain by setting the Authorization header (HTTP.auth).

func BasicAuth

func BasicAuth(user, pass string) *Client

BasicAuth starts a chain with HTTP Basic Authorization (HTTP.basic_auth).

func Cookies

func Cookies(kv ...KV) *Client

Cookies starts a chain with default cookies (HTTP.cookies).

func Encoding

func Encoding(enc string) *Client

Encoding starts a chain forcing the response charset (HTTP.encoding).

func Follow

func Follow(opts ...FollowOptions) *Client

Follow starts a chain that follows redirects (HTTP.follow).

func Header(key, val string) *Client

Header starts a chain by setting one default header (HTTP.headers(k => v)).

func NewClient

func NewClient() *Client

NewClient returns a fresh client with empty options and the DefaultClientTransport. Inject a per-client stub with Client.WithTransport.

func Nodelay

func Nodelay() *Client

Nodelay starts a chain with TCP_NODELAY (HTTP.nodelay).

func Through

func Through(host string, port int, userpass ...string) *Client

Through is an alias for Via (HTTP.through).

func Timeout

func Timeout(seconds int) *Client

Timeout starts a chain with a global timeout (HTTP.timeout).

func Use

func Use(features ...string) *Client

Use starts a chain with features enabled (HTTP.use).

func Via

func Via(host string, port int, userpass ...string) *Client

Via starts a chain routing through a proxy (HTTP.via).

func (*Client) Accept

func (c *Client) Accept(typ string) *Client

Accept returns a branched client with the Accept header set to the media type for typ (HTTP.accept(:json)). The alias "json"/":json" expands to "application/json"; any other value is used verbatim.

func (*Client) Auth

func (c *Client) Auth(value string) *Client

Auth returns a branched client with the Authorization header set to value verbatim (HTTP.auth("Bearer tok")).

func (*Client) BasicAuth

func (c *Client) BasicAuth(user, pass string) *Client

BasicAuth returns a branched client with HTTP Basic Authorization (HTTP.basic_auth(user:, pass:)): "Basic base64(user:pass)".

func (*Client) Connect

func (c *Client) Connect(uri string, opts ...RequestOption) (*Response, error)

Connect issues a CONNECT request (HTTP.connect).

func (*Client) Cookies

func (c *Client) Cookies(kv ...KV) *Client

Cookies returns a branched client with the given default cookies added (HTTP.cookies(...)).

func (*Client) DefaultOptions

func (c *Client) DefaultOptions() *Options

Options returns the client's current configuration (HTTP::Client#default_options).

func (*Client) Delete

func (c *Client) Delete(uri string, opts ...RequestOption) (*Response, error)

Delete issues a DELETE request (HTTP.delete).

func (*Client) Encoding

func (c *Client) Encoding(enc string) *Client

Encoding returns a branched client forcing the response body charset (HTTP.encoding("UTF-8")).

func (*Client) Follow

func (c *Client) Follow(opts ...FollowOptions) *Client

Follow returns a branched client that follows redirects (HTTP.follow). Pass a FollowOptions to override max hops / strictness; the default is strict with DefaultMaxRedirects hops.

func (*Client) Get

func (c *Client) Get(uri string, opts ...RequestOption) (*Response, error)

Get issues a GET request (HTTP.get).

func (*Client) Head

func (c *Client) Head(uri string, opts ...RequestOption) (*Response, error)

Head issues a HEAD request (HTTP.head).

func (*Client) Header

func (c *Client) Header(key, val string) *Client

Header returns a branched client with a single default header set.

func (*Client) Headers

func (c *Client) Headers(kv ...KV) *Client

Headers returns a branched client with the given headers merged onto its defaults (HTTP.headers(...)).

func (*Client) Nodelay

func (c *Client) Nodelay() *Client

Nodelay returns a branched client with TCP_NODELAY requested (HTTP.nodelay).

func (*Client) Options

func (c *Client) Options(uri string, opts ...RequestOption) (*Response, error)

Options issues an OPTIONS request (HTTP.options).

func (*Client) Patch

func (c *Client) Patch(uri string, opts ...RequestOption) (*Response, error)

Patch issues a PATCH request (HTTP.patch).

func (*Client) Persistent

func (c *Client) Persistent(host string, block func(*Client) error) error

Persistent runs block against a branched client pinned to host, mirroring HTTP.persistent(host) { |http| ... }. Requests to a different host inside the block fail with a StateError. The block's error (if any) is returned.

func (*Client) Post

func (c *Client) Post(uri string, opts ...RequestOption) (*Response, error)

Post issues a POST request (HTTP.post).

func (*Client) Put

func (c *Client) Put(uri string, opts ...RequestOption) (*Response, error)

Put issues a PUT request (HTTP.put).

func (*Client) Request

func (c *Client) Request(verb, uri string, opts ...RequestOption) (*Response, error)

Request builds and performs a request for verb/uri, following redirects when the client is configured to (HTTP::Client#request). It is the general entry point behind the verb methods.

func (*Client) Through

func (c *Client) Through(host string, port int, userpass ...string) *Client

Through is an alias for Client.Via (HTTP.through).

func (*Client) Timeout

func (c *Client) Timeout(seconds int) *Client

Timeout returns a branched client with a global timeout of seconds (HTTP.timeout(30)). The deterministic core does not open sockets; the value is metadata a host transport honors.

func (*Client) TimeoutOps

func (c *Client) TimeoutOps(connect, read, write int) *Client

TimeoutOps returns a branched client with per-operation timeouts (HTTP.timeout(connect:, read:, write:)).

func (*Client) Trace

func (c *Client) Trace(uri string, opts ...RequestOption) (*Response, error)

Trace issues a TRACE request (HTTP.trace).

func (*Client) Use

func (c *Client) Use(features ...string) *Client

Use returns a branched client with the named features/middleware enabled (HTTP.use(...)).

func (*Client) Via

func (c *Client) Via(host string, port int, userpass ...string) *Client

Via returns a branched client routing through an HTTP proxy (HTTP.via(host, port)); an optional user and password add proxy auth. The alias Client.Through matches http.rb's `through`.

func (*Client) WithTransport

func (c *Client) WithTransport(t Transport) *Client

WithTransport returns a branched client whose round-trips run through t — the host seam. Tests pass a TransportFunc stub; a host wires the real transport.

type ContentType

type ContentType struct {
	// MimeType is the media type, stripped and lower-cased ("application/json"),
	// or "" when the header has no media type.
	MimeType string
	// Charset is the charset parameter value, stripped and unquoted, or "" when
	// absent.
	Charset string
}

ContentType is a parsed Content-Type header, mirroring http.rb's HTTP::ContentType (a Struct of mime_type and charset).

func ParseContentType

func ParseContentType(value string) ContentType

ParseContentType parses a Content-Type header value the way HTTP::ContentType.parse does: the media type is the text before the first ';', stripped and down-cased (empty ⇒ ""); the charset is the value of a case-insensitive `charset=` parameter, stripped and with surrounding quotes removed (absent ⇒ "").

type Cookie struct {
	Name  string
	Value string
}

Cookie is a single name/value cookie, the deterministic subset of HTTP::Cookie the client needs to round-trip a session (attributes such as Path/Domain/Expires are host concerns and are not modeled).

type CookieJar

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

CookieJar is http.rb's HTTP::CookieJar reduced to its deterministic core: an ordered set of name/value cookies. It parses Set-Cookie response headers into cookies and renders a Cookie request header. Later additions of an existing name overwrite the value in place.

func NewCookieJar

func NewCookieJar() *CookieJar

NewCookieJar returns an empty jar.

func (*CookieJar) Add

func (j *CookieJar) Add(name, value string)

Add inserts or overwrites the cookie named name (HTTP::CookieJar#add).

func (*CookieJar) Clone

func (j *CookieJar) Clone() *CookieJar

Clone returns a deep copy of the jar.

func (*CookieJar) Cookies

func (j *CookieJar) Cookies() []Cookie

Cookies returns the jar's cookies in insertion order. The slice must not be mutated.

func (*CookieJar) Get

func (j *CookieJar) Get(name string) (string, bool)

Get returns the value of the named cookie and whether present.

func (*CookieJar) HeaderValue

func (j *CookieJar) HeaderValue() string

HeaderValue renders the jar as a Cookie request-header value: "name=value; name2=value2" in insertion order (empty ⇒ "").

func (*CookieJar) Len

func (j *CookieJar) Len() int

Len reports the number of cookies.

func (*CookieJar) ParseSetCookie

func (j *CookieJar) ParseSetCookie(header string)

ParseSetCookie parses a single Set-Cookie header value and stores the name/value pair (the first "name=value" segment; attributes after the first ';' are ignored). A value with no '=' or an empty name is skipped.

type Error

type Error struct {
	// Kind names the HTTP:: error subclass (see the Err* sentinels).
	Kind ErrorKind
	// Message is the error text (Exception#message).
	Message string
	// Response is the response context for a ResponseError (nil otherwise).
	Response *Response
	// Cause is the underlying transport error, if any.
	Cause error
}

Error is the root of http.rb's error tree (HTTP::Error). Every error carries a message and a Error.Kind naming the concrete HTTP:: subclass; a response error also carries the Error.Response that triggered it, and a transport error wraps the underlying cause. Match with errors.Is against the Err* sentinels — a superclass sentinel matches its subclasses, mirroring Ruby's rescue of a superclass.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface (Exception#message).

func (*Error) Is

func (e *Error) Is(target error) bool

Is reports whether e matches target: true when target is a *Error whose Kind is e's Kind or an ancestor of it, so errors.Is(err, ErrResponseError) matches a StateError and errors.Is(err, ErrError) matches every http.rb error.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap exposes the underlying transport cause for errors.Is/As.

type ErrorKind

type ErrorKind string

ErrorKind identifies an http.rb error subclass.

const (
	KindError           ErrorKind = "HTTP::Error"
	KindConnectionError ErrorKind = "HTTP::ConnectionError"
	KindRequestError    ErrorKind = "HTTP::RequestError"
	KindResponseError   ErrorKind = "HTTP::ResponseError"
	KindStateError      ErrorKind = "HTTP::StateError"
	KindTimeoutError    ErrorKind = "HTTP::TimeoutError"
	KindHeaderError     ErrorKind = "HTTP::HeaderError"
)

The http.rb error subclasses, named as in the gem (HTTP::*).

type FollowOptions

type FollowOptions struct {
	MaxHops int
	Strict  bool
}

FollowOptions configures redirect following, mirroring the `follow` option and HTTP::Redirector. MaxHops caps the redirect chain (default DefaultMaxRedirects); Strict keeps the original verb on 301/302/307/308 (http.rb's default), while a non-strict follow downgrades 301/302 to GET.

type Headers

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

Headers is http.rb's HTTP::Headers: an ordered, case-insensitive header map whose names are normalized to canonical HTTP header casing (see NormalizeHeaderName) and which may carry multiple values per name (Set-Cookie, …). Lookups are case-insensitive; Headers.Get returns the first value, Headers.GetAll every value in order.

func NewHeaders

func NewHeaders(kv ...KV) *Headers

NewHeaders builds a Headers from ordered entries (each added, so repeated keys accumulate as multiple values — use Headers.Set semantics via KV with distinct keys for single values).

func (*Headers) Add

func (h *Headers) Add(key, val string)

Add appends a value for key without removing existing ones (HTTP::Headers#add), so a name may hold several values.

func (*Headers) Clone

func (h *Headers) Clone() *Headers

Clone returns a deep copy of h.

func (*Headers) Delete

func (h *Headers) Delete(key string)

Delete removes every value for key (case-insensitive), preserving order of the rest.

func (*Headers) Get

func (h *Headers) Get(key string) (string, bool)

Get returns the first value for key (case-insensitive) and whether present (HTTP::Headers#[]).

func (*Headers) GetAll

func (h *Headers) GetAll(key string) []string

GetAll returns every value for key in order (HTTP::Headers#get).

func (*Headers) Has

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

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

func (*Headers) Len

func (h *Headers) Len() int

Len reports the number of stored header values (counting duplicates).

func (*Headers) Merge

func (h *Headers) Merge(other *Headers) *Headers

Merge overlays other onto a copy of h: each of other's names replaces (via Headers.Set) the corresponding entry, so merged defaults are overridden.

func (*Headers) Pairs

func (h *Headers) Pairs() []KV

Pairs returns the header entries in insertion order (normalized names). The slice must not be mutated.

func (*Headers) Set

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

Set replaces every value for key with a single value (HTTP::Headers#set / []=). The name is normalized; any existing occurrences are removed first.

func (*Headers) SetDefault

func (h *Headers) SetDefault(key, val string)

SetDefault sets key→val only when key is absent (case-insensitive).

type KV

type KV struct {
	Key string
	Val string
}

KV is one ordered key/value header (or form/query) entry, the convenient literal for the variadic constructors.

type NetTransport

type NetTransport struct {
	// Client performs the request; defaults to http.DefaultClient.
	Client httpClient
}

NetTransport is the default Transport: it turns a Request into a net/http request, executes it with its Client, and maps the response (or a transport failure) back to a Response or an Error. A timeout maps to a TimeoutError, any other transport failure to a ConnectionError.

func DefaultTransport

func DefaultTransport() *NetTransport

DefaultTransport returns the default net/http-backed Transport.

func (*NetTransport) Perform

func (t *NetTransport) Perform(req *Request) (*Response, error)

Perform executes req with net/http and maps the outcome.

type Options

type Options struct {
	// Headers are the default headers applied to every request.
	Headers *Headers
	// Cookies are the default cookies sent with every request.
	Cookies *CookieJar
	// Proxy is the upstream proxy (nil ⇒ direct), set by Via.
	Proxy *Proxy
	// Follow configures redirect following (nil ⇒ do not follow).
	Follow *FollowOptions
	// Timeout carries the request timeouts (nil ⇒ unset), set by Timeout.
	Timeout *TimeoutOptions
	// Persistent is the host a persistent client is pinned to ("" ⇒ per-request).
	Persistent string
	// Features are the enabled feature/middleware names (HTTP#use).
	Features []string
	// Encoding forces the response body charset (HTTP#encoding).
	Encoding string
	// Nodelay disables Nagle's algorithm (HTTP#nodelay); host-honored metadata.
	Nodelay bool
}

Options is the immutable per-client configuration, mirroring HTTP::Options (a client's default_options). The chainable methods on Client branch a client by cloning its Options and tweaking one field, exactly as http.rb's HTTP::Chainable#branch does.

type Proxy

type Proxy struct {
	Host string
	Port int
	User string
	Pass string
}

Proxy is an upstream HTTP proxy, mirroring the `proxy` option built by HTTP::Chainable#via.

type Request

type Request struct {
	// Verb is the upper-case HTTP method ("GET", "POST", …).
	Verb string
	// URL is the fully-built request URL (with any `params:` query merged in).
	URL string
	// Headers are the outgoing headers.
	Headers *Headers
	// Body is the encoded request body (form/JSON/raw); "" when there is none.
	Body string
	// Cookies are the cookies to send (rendered into the Cookie header by the
	// transport or already merged by the client).
	Cookies *CookieJar
	// Proxy is the upstream proxy, when configured.
	Proxy *Proxy
}

Request is a prepared HTTP request handed to a Transport, mirroring HTTP::Request: the verb, the target URL, the fully-merged headers (defaults overlaid by per-request headers and any encoded body's Content-Type), the encoded body, and the cookies/proxy the client is configured with.

type RequestOption

type RequestOption func(*reqBuild)

RequestOption configures a single request body/query, mirroring the keyword arguments to http.rb's verb methods (`form:`, `json:`, `body:`, `params:`).

func Body

func Body(s string) RequestOption

Body sends s as the raw request body verbatim (http.rb `body:`).

func Form

func Form(v *Values) RequestOption

Form sends v as an application/x-www-form-urlencoded body (http.rb `form:`).

func JSON

func JSON(v any) RequestOption

JSON sends v JSON-encoded with Content-Type application/json; charset=utf-8 (http.rb `json:`).

func Params

func Params(v *Values) RequestOption

Params merges v into the request URL's query string (http.rb `params:`).

type Response

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

Response is the result of a request, mirroring HTTP::Response: the status (with its predicates), headers, body, parsed content type, cookies, and the version/URI. Build one with NewResponse (a transport does this); read it via the accessors.

func Delete

func Delete(uri string, opts ...RequestOption) (*Response, error)

Delete issues a DELETE request with a fresh default client (HTTP.delete).

func Get

func Get(uri string, opts ...RequestOption) (*Response, error)

Get issues a GET request with a fresh default client (HTTP.get).

func Head(uri string, opts ...RequestOption) (*Response, error)

Head issues a HEAD request with a fresh default client (HTTP.head).

func NewResponse

func NewResponse(status int, headers *Headers, body, version, uri string) *Response

NewResponse builds a Response from the parts a transport produces. The cookies are derived from the Set-Cookie response headers. A nil headers is treated as empty.

func Patch

func Patch(uri string, opts ...RequestOption) (*Response, error)

Patch issues a PATCH request with a fresh default client (HTTP.patch).

func Post

func Post(uri string, opts ...RequestOption) (*Response, error)

Post issues a POST request with a fresh default client (HTTP.post).

func Put

func Put(uri string, opts ...RequestOption) (*Response, error)

Put issues a PUT request with a fresh default client (HTTP.put).

func (*Response) Body

func (r *Response) Body() *ResponseBody

Body returns the response body (HTTP::Response#body).

func (*Response) Code

func (r *Response) Code() int

Code returns the numeric status code (HTTP::Response#code).

func (*Response) ContentType

func (r *Response) ContentType() ContentType

ContentType returns the parsed Content-Type header (HTTP::Response#content_type). A missing header yields the zero ContentType.

func (*Response) Cookies

func (r *Response) Cookies() *CookieJar

Cookies returns the cookies parsed from the response's Set-Cookie headers (HTTP::Response#cookies).

func (*Response) Headers

func (r *Response) Headers() *Headers

Headers returns the response headers (HTTP::Response#headers).

func (*Response) Parse

func (r *Response) Parse(as ...string) (any, error)

Parse decodes the body according to its media type, mirroring HTTP::Response#parse. With no argument it uses the response's Content-Type; an optional override forces a media type (":json"/"json"/"application/json" all select JSON). Only JSON is decoded by the deterministic core; any other media type yields an Error of kind KindError ("unknown MIME type"), and a malformed JSON body yields a KindError wrapping the decode failure.

func (*Response) Reason

func (r *Response) Reason() string

Reason returns the status reason phrase (HTTP::Response#reason).

func (*Response) Request

func (r *Response) Request() *Request

Request returns the Request that produced this response, when the client set it (HTTP::Response#request); nil for a bare NewResponse.

func (*Response) Status

func (r *Response) Status() Status

Status returns the Status (HTTP::Response#status).

func (*Response) URI

func (r *Response) URI() string

URI returns the effective request URI as a string (HTTP::Response#uri).

func (*Response) Version

func (r *Response) Version() string

Version returns the HTTP version string (HTTP::Response#version), e.g. "1.1".

type ResponseBody

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

ResponseBody is the body of a Response, mirroring http.rb's HTTP::Response::Body. The deterministic core carries the fully-read body as a string; a streaming host transport may fill it lazily, but callers observe it through ResponseBody.String/ResponseBody.To_s.

func (*ResponseBody) String

func (b *ResponseBody) String() string

String returns the body as a string (HTTP::Response::Body#to_s). It also satisfies fmt.Stringer.

func (*ResponseBody) To_s

func (b *ResponseBody) To_s() string

To_s is the http.rb-named alias for ResponseBody.String (Body#to_s), for hosts that surface the Ruby method name.

type Status

type Status int

Status is an HTTP status code with http.rb's HTTP::Response::Status query methods. The zero value is code 0 (a nil status, as http.rb models a response with no status line).

func (Status) ClientError

func (s Status) ClientError() bool

ClientError reports whether the code is 4xx (#client_error?).

func (Status) Code

func (s Status) Code() int

Code returns the numeric status code (HTTP::Response::Status#code / #to_i).

func (Status) Informational

func (s Status) Informational() bool

Informational reports whether the code is 1xx (#informational?).

func (Status) Reason

func (s Status) Reason() string

Reason returns the reason phrase for the status code, or "" when the code has no registered phrase (HTTP::Response::Status#reason). The table mirrors HTTP::Response::Status::REASONS.

func (Status) Redirect

func (s Status) Redirect() bool

Redirect reports whether the code is 3xx (#redirect?).

func (Status) ServerError

func (s Status) ServerError() bool

ServerError reports whether the code is 5xx (#server_error?).

func (Status) String

func (s Status) String() string

String renders the status as http.rb's HTTP::Response::Status#to_s does: "<code> <reason>" when a reason phrase exists, otherwise just "<code>".

func (Status) Success

func (s Status) Success() bool

Success reports whether the code is 2xx (#success?).

type TimeoutOptions

type TimeoutOptions struct {
	Mode    string
	Global  int
	Connect int
	Read    int
	Write   int
}

TimeoutOptions carries http.rb's timeout configuration. Mode is "" for the default (global) timeout or "per_operation" when connect/read/write are set individually. Values are in seconds; 0 means unset. The deterministic core does not open sockets, so these are metadata a host transport honors.

type Transport

type Transport interface {
	Perform(req *Request) (*Response, error)
}

Transport is the host seam that performs the HTTP round-trip, mirroring the role http.rb's HTTP::Client delegates to its socket/connection. Given a prepared Request, a Transport returns the finished Response or a transport Error (ConnectionError / TimeoutError).

The default production Transport is DefaultTransport (backed by net/http). The core opens no socket itself: every request runs through whatever Transport the client is configured with, so tests inject a TransportFunc stub and never touch the network.

var DefaultClientTransport Transport = DefaultTransport()

DefaultClientTransport is the Transport a NewClient — and thus every package-level shortcut (Get/Post/Auth/…) — starts with. A host wires the production transport here once; tests swap in a stub so the package-level entry points never open a socket.

type TransportFunc

type TransportFunc func(req *Request) (*Response, error)

TransportFunc adapts a function to the Transport interface — the convenient way to inject a stub in tests or a custom transport in a host.

func (TransportFunc) Perform

func (f TransportFunc) Perform(req *Request) (*Response, error)

Perform invokes f(req).

type Values

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

Values is an ordered, multi-valued string map used for form bodies and query params, mirroring the Hashes http.rb threads into `form:` and `params:`. Insertion order is preserved (http.rb's FormData encoder keeps Hash order), and a key with several values is encoded array-style ("key[]=a&key[]=b"), as HTTP::FormData::Urlencoded does.

func NewValues

func NewValues(kv ...KV) *Values

NewValues builds a Values from ordered key/value entries (each added).

func (*Values) Add

func (v *Values) Add(key, val string) *Values

Add appends val under key, preserving insertion order. Repeated Adds under one key make it array-valued for encoding.

func (*Values) AddNil

func (v *Values) AddNil(key string) *Values

AddNil records a bare key with no value ("key" with no '='), matching a nil Hash value in http.rb's urlencoder.

func (*Values) Encode

func (v *Values) Encode() string

Encode renders the values as an application/x-www-form-urlencoded string, byte-faithful to HTTP::FormData::Urlencoded: keys and values are escaped with EscapeFormComponent (space→'+', unreserved [A-Za-z0-9*-._] literal, else %XX), a multi-valued key is emitted array-style ("key[]=..."), and a bare (nil) key is emitted with no '='.

func (*Values) Get

func (v *Values) Get(key string) (string, bool)

Get returns the first value for key and whether present.

func (*Values) Len

func (v *Values) Len() int

Len reports the number of distinct keys.

func (*Values) Set

func (v *Values) Set(key, val string) *Values

Set replaces any values under key with the single val.

Jump to

Keyboard shortcuts

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