Documentation
¶
Overview ¶
Package excon is a pure-Go (CGO-free) reimplementation of the deterministic core of Ruby's `excon` gem — the fast, persistent HTTP client. It models a reusable Connection bound to a base URL and default options, the one-shot verb helpers (Get, Post, …), the option handling Excon performs around the wire (path/query building, header merging, Basic auth, the :expects status assertion, and :idempotent retry), the Response (Status/Body/Headers/ RemoteIp/ReasonPhrase), and the full Excon::Error tree.
What it is — and isn't ¶
Everything Excon does around the socket is deterministic and needs no interpreter, so it lives here as pure Go: merging per-request options over the connection defaults, building the absolute URL (path plus an order-preserving, CGI-escaped query string), adding the Basic Authorization header, asserting the response status against :expects (raising the matching status error), and retrying an :idempotent request on a transport failure. The HTTP round-trip itself is a host seam: the Doer transport performs it. The default is NetHTTP, backed by net/http (it also captures the remote IP via httptrace); tests inject a DoerFunc stub or drive NetHTTP over an httptest server, and a host (go-embedded-ruby / rbgo) wires the real transport.
Flow ¶
conn := excon.New("https://api.example.com", excon.Options{
Headers: excon.HeadersOf([2]string{"Accept", "application/json"}),
})
resp, err := conn.Get(excon.Options{
Path: "/widgets",
Query: excon.QueryOf([2]string{"q", "gadget"}),
Expects: []int{200},
})
if err != nil { /* an excon.Error: NotFound, InternalServerError, Timeout, … */ }
_ = resp.Status() // 200
_ = resp.Body() // response body
_ = resp.RemoteIp() // resolved peer IP
_ = resp.Success() // true for 2xx
// one-shot form
resp, err = excon.Post("https://api.example.com/widgets",
excon.Options{Body: `{"name":"gadget"}`, Expects: []int{201}})
Errors ¶
An :expects mismatch raises a status Error whose Kind names the Excon subclass for the code (404 → KindNotFound, 500 → KindInternalServerError, an unmapped 4xx → KindClient, …); a transport failure raises KindSocket or KindTimeout. The whole tree matches with errors.Is via the sentinels: e.g. errors.Is(err, excon.ErrClient) matches any 4xx, errors.Is(err, excon.ErrHTTPStatus) any status error, errors.Is(err, excon.ErrError) any excon error — mirroring Ruby's rescue of a superclass. Helpers IsClientError, IsServerError, IsHTTPStatusError, IsSocketError and IsTimeout wrap the common checks.
Value model ¶
Query params are an ordered Query (CGI-escaped like Excon::Utils.query_string); headers a case-insensitive ordered Headers; the body a string. A host maps its Ruby Excon::Connection / Response objects to and from these shapes.
Index ¶
- Variables
- func BasicHeaderFrom(user, password string) string
- func Escape(s string) string
- func IsClientError(err error) bool
- func IsHTTPStatusError(err error) bool
- func IsServerError(err error) bool
- func IsSocketError(err error) bool
- func IsTimeout(err error) bool
- func Unescape(s string) string
- type Connection
- func (c *Connection) Delete(opts ...Options) (*Response, error)
- func (c *Connection) Get(opts ...Options) (*Response, error)
- func (c *Connection) Head(opts ...Options) (*Response, error)
- func (c *Connection) Patch(opts ...Options) (*Response, error)
- func (c *Connection) Post(opts ...Options) (*Response, error)
- func (c *Connection) Put(opts ...Options) (*Response, error)
- func (c *Connection) Request(opts ...Options) (*Response, error)
- func (c *Connection) Transport(d Doer) *Connection
- type Doer
- type DoerFunc
- type Error
- type ErrorKind
- type Handler
- type Headers
- func (h *Headers) Clone() *Headers
- func (h *Headers) Delete(key string)
- func (h *Headers) Get(key string) (string, bool)
- func (h *Headers) Has(key string) bool
- func (h *Headers) Len() int
- func (h *Headers) Merge(other *Headers) *Headers
- func (h *Headers) Pairs() []Pair
- func (h *Headers) Set(key, val string)
- type Middleware
- type NetHTTPDoer
- type Options
- type Pair
- type Query
- type QueryPair
- type Request
- type Response
- func Delete(rawurl string, opts ...Options) (*Response, error)
- func Get(rawurl string, opts ...Options) (*Response, error)
- func Head(rawurl string, opts ...Options) (*Response, error)
- func NewResponse(status int, body string, headers *Headers, reason, remoteIP string) *Response
- func Patch(rawurl string, opts ...Options) (*Response, error)
- func Post(rawurl string, opts ...Options) (*Response, error)
- func Put(rawurl string, opts ...Options) (*Response, error)
Constants ¶
This section is empty.
Variables ¶
var ( ErrError = &Error{Kind: KindError, Message: string(KindError)} ErrSocket = &Error{Kind: KindSocket, Message: string(KindSocket)} ErrCertificate = &Error{Kind: KindCertificate, Message: string(KindCertificate)} ErrTimeout = &Error{Kind: KindTimeout, Message: string(KindTimeout)} ErrResponseParse = &Error{Kind: KindResponseParse, Message: string(KindResponseParse)} ErrProxyConnectionError = &Error{Kind: KindProxyConnectionError, Message: string(KindProxyConnectionError)} ErrProxyParse = &Error{Kind: KindProxyParse, Message: string(KindProxyParse)} ErrTooManyRedirects = &Error{Kind: KindTooManyRedirects, Message: string(KindTooManyRedirects)} ErrHTTPStatus = &Error{Kind: KindHTTPStatus, Message: string(KindHTTPStatus)} ErrInformational = &Error{Kind: KindInformational, Message: string(KindInformational)} ErrRedirection = &Error{Kind: KindRedirection, Message: string(KindRedirection)} ErrClient = &Error{Kind: KindClient, Message: string(KindClient)} ErrServer = &Error{Kind: KindServer, Message: string(KindServer)} ErrBadRequest = &Error{Kind: KindBadRequest, Message: string(KindBadRequest)} ErrPaymentRequired = &Error{Kind: KindPaymentRequired, Message: string(KindPaymentRequired)} ErrForbidden = &Error{Kind: KindForbidden, Message: string(KindForbidden)} ErrNotFound = &Error{Kind: KindNotFound, Message: string(KindNotFound)} ErrMethodNotAllowed = &Error{Kind: KindMethodNotAllowed, Message: string(KindMethodNotAllowed)} ErrNotAcceptable = &Error{Kind: KindNotAcceptable, Message: string(KindNotAcceptable)} ErrProxyAuthenticationRequired = &Error{Kind: KindProxyAuthenticationRequired, Message: string(KindProxyAuthenticationRequired)} ErrRequestTimeout = &Error{Kind: KindRequestTimeout, Message: string(KindRequestTimeout)} ErrConflict = &Error{Kind: KindConflict, Message: string(KindConflict)} ErrGone = &Error{Kind: KindGone, Message: string(KindGone)} ErrLengthRequired = &Error{Kind: KindLengthRequired, Message: string(KindLengthRequired)} ErrPreconditionFailed = &Error{Kind: KindPreconditionFailed, Message: string(KindPreconditionFailed)} ErrRequestEntityTooLarge = &Error{Kind: KindRequestEntityTooLarge, Message: string(KindRequestEntityTooLarge)} ErrRequestURITooLong = &Error{Kind: KindRequestURITooLong, Message: string(KindRequestURITooLong)} ErrUnsupportedMediaType = &Error{Kind: KindUnsupportedMediaType, Message: string(KindUnsupportedMediaType)} ErrRequestedRangeNotSatisfiable = &Error{Kind: KindRequestedRangeNotSatisfiable, Message: string(KindRequestedRangeNotSatisfiable)} ErrExpectationFailed = &Error{Kind: KindExpectationFailed, Message: string(KindExpectationFailed)} ErrUnprocessableEntity = &Error{Kind: KindUnprocessableEntity, Message: string(KindUnprocessableEntity)} ErrTooManyRequests = &Error{Kind: KindTooManyRequests, Message: string(KindTooManyRequests)} ErrInternalServerError = &Error{Kind: KindInternalServerError, Message: string(KindInternalServerError)} ErrNotImplemented = &Error{Kind: KindNotImplemented, Message: string(KindNotImplemented)} ErrBadGateway = &Error{Kind: KindBadGateway, Message: string(KindBadGateway)} ErrGatewayTimeout = &Error{Kind: KindGatewayTimeout, Message: string(KindGatewayTimeout)} )
Sentinel errors for errors.Is matching. Each names an Excon error kind; a concrete Error with that Kind (or a descendant of it) matches via Error.Is.
Functions ¶
func BasicHeaderFrom ¶
BasicHeaderFrom returns the Basic Authorization header value for a user and password: "Basic " followed by the newline-free base64 of "user:password", matching what Excon sets when :user/:password are supplied.
func Escape ¶
Escape percent-encodes s the way Ruby's CGI.escape does (the escaper Excon applies to query keys and values): [A-Za-z0-9 _.-~] stays literal, a space becomes '+', and any other byte becomes %XX with upper-case hex.
func IsClientError ¶
IsClientError reports whether err is an Excon 4xx client status error.
func IsHTTPStatusError ¶
IsHTTPStatusError reports whether err is an Excon HTTP status error (or any subclass), i.e. an error raised by an :expects mismatch.
func IsServerError ¶
IsServerError reports whether err is an Excon 5xx server status error.
func IsSocketError ¶
IsSocketError reports whether err is an Excon socket (transport) error.
Types ¶
type Connection ¶
type Connection struct {
// contains filtered or unexported fields
}
Connection is a persistent, reusable HTTP client bound to a base URL and a set of default options, mirroring Excon::Connection. Build one with New; issue requests with Connection.Request or the verb helpers.
func New ¶
func New(rawurl string, opts ...Options) *Connection
New builds a Connection for a base URL, mirroring Excon.new(url, opts). The URL's scheme, host, port, path, query and userinfo seed the connection defaults; the optional opts override them. The connection defaults to the net/http transport (NetHTTP); tests override it with Connection.Transport.
func (*Connection) Delete ¶
func (c *Connection) Delete(opts ...Options) (*Response, error)
Delete issues a DELETE request (Excon::Connection#delete).
func (*Connection) Get ¶
func (c *Connection) Get(opts ...Options) (*Response, error)
Get issues a GET request (Excon::Connection#get).
func (*Connection) Head ¶
func (c *Connection) Head(opts ...Options) (*Response, error)
Head issues a HEAD request (Excon::Connection#head).
func (*Connection) Patch ¶
func (c *Connection) Patch(opts ...Options) (*Response, error)
Patch issues a PATCH request (Excon::Connection#patch).
func (*Connection) Post ¶
func (c *Connection) Post(opts ...Options) (*Response, error)
Post issues a POST request (Excon::Connection#post).
func (*Connection) Put ¶
func (c *Connection) Put(opts ...Options) (*Response, error)
Put issues a PUT request (Excon::Connection#put).
func (*Connection) Request ¶
func (c *Connection) Request(opts ...Options) (*Response, error)
Request issues a request, merging the given per-request options over the connection defaults, mirroring Excon::Connection#request. It builds the Request, runs it through the middleware stack around the retry/transport/ :expects core, and returns the Response (or an Error).
func (*Connection) Transport ¶
func (c *Connection) Transport(d Doer) *Connection
Transport sets the connection's transport Doer (the host seam) and returns the connection for chaining. Tests inject a DoerFunc stub; rbgo wires the real transport.
type Doer ¶
Doer is the transport host seam: given a fully-resolved Request, it performs the HTTP round-trip and returns the Response, or a transport Error (Socket/Timeout). The core opens no socket itself — every request runs through whatever Doer the connection is set to, so tests inject a stub (see DoerFunc or drive NetHTTP over an httptest server) and rbgo wires the real transport.
type DoerFunc ¶
DoerFunc adapts a function to the Doer interface, the convenient way to inject a stub transport in tests or a custom one in a host.
type Error ¶
type Error struct {
// Kind names the specific Excon error subclass (see the Err* sentinels).
Kind ErrorKind
// Message is the error text (Excon::Error#message).
Message string
// Request is the request context for a status error (nil otherwise).
Request *Request
// Response is the response context for a status error (nil otherwise).
Response *Response
// Cause is the underlying transport error for Socket/Timeout errors.
Cause error
}
Error is the root of Excon's error tree (Excon::Error, aliased in the gem as Excon::Errors::Error). Every excon error carries a human message; a status error additionally carries the Request and Response that triggered it, and a transport error wraps the underlying cause.
The concrete kinds are distinguished by Error.Kind; the predicate helpers (IsHTTPStatusError, IsClientError, …) and the sentinel values (ErrNotFound, …) match with errors.Is, walking the class hierarchy exactly as Ruby's rescue of an Excon::Error subclass does.
func (*Error) Is ¶
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, ErrClient) matches any 4xx status error, errors.Is(err, ErrHTTPStatus) matches any status error, and errors.Is(err, ErrError) matches every excon error — mirroring Ruby's rescue of a superclass.
type ErrorKind ¶
type ErrorKind string
ErrorKind identifies an Excon error subclass, named as in the gem.
const ( KindError ErrorKind = "Excon::Error" // Transport / non-status errors. KindSocket ErrorKind = "Excon::Error::Socket" KindCertificate ErrorKind = "Excon::Error::Certificate" KindTimeout ErrorKind = "Excon::Error::Timeout" KindResponseParse ErrorKind = "Excon::Error::ResponseParse" KindProxyConnectionError ErrorKind = "Excon::Error::ProxyConnectionError" KindProxyParse ErrorKind = "Excon::Error::ProxyParse" KindTooManyRedirects ErrorKind = "Excon::Error::TooManyRedirects" // HTTP status errors: base and range bases. KindHTTPStatus ErrorKind = "Excon::Error::HTTPStatus" KindInformational ErrorKind = "Excon::Error::Informational" KindRedirection ErrorKind = "Excon::Error::Redirection" KindClient ErrorKind = "Excon::Error::Client" KindServer ErrorKind = "Excon::Error::Server" // 4xx client status errors. KindBadRequest ErrorKind = "Excon::Error::BadRequest" KindPaymentRequired ErrorKind = "Excon::Error::PaymentRequired" KindForbidden ErrorKind = "Excon::Error::Forbidden" KindNotFound ErrorKind = "Excon::Error::NotFound" KindMethodNotAllowed ErrorKind = "Excon::Error::MethodNotAllowed" KindNotAcceptable ErrorKind = "Excon::Error::NotAcceptable" KindProxyAuthenticationRequired ErrorKind = "Excon::Error::ProxyAuthenticationRequired" KindRequestTimeout ErrorKind = "Excon::Error::RequestTimeout" KindConflict ErrorKind = "Excon::Error::Conflict" KindGone ErrorKind = "Excon::Error::Gone" KindLengthRequired ErrorKind = "Excon::Error::LengthRequired" KindPreconditionFailed ErrorKind = "Excon::Error::PreconditionFailed" KindRequestEntityTooLarge ErrorKind = "Excon::Error::RequestEntityTooLarge" KindRequestURITooLong ErrorKind = "Excon::Error::RequestURITooLong" KindUnsupportedMediaType ErrorKind = "Excon::Error::UnsupportedMediaType" KindRequestedRangeNotSatisfiable ErrorKind = "Excon::Error::RequestedRangeNotSatisfiable" KindExpectationFailed ErrorKind = "Excon::Error::ExpectationFailed" KindUnprocessableEntity ErrorKind = "Excon::Error::UnprocessableEntity" KindTooManyRequests ErrorKind = "Excon::Error::TooManyRequests" // 5xx server status errors. KindInternalServerError ErrorKind = "Excon::Error::InternalServerError" KindNotImplemented ErrorKind = "Excon::Error::NotImplemented" KindBadGateway ErrorKind = "Excon::Error::BadGateway" KindGatewayTimeout ErrorKind = "Excon::Error::GatewayTimeout" )
The Excon error subclasses, named as in Excon::Error::*.
type Handler ¶
Handler is one step of the resolved request pipeline: it receives the Request, does its work (typically calling the next handler), and returns the Response or an error. The innermost handler is the connection core (retry + transport + :expects check); each Middleware wraps it.
type Headers ¶
type Headers struct {
// contains filtered or unexported fields
}
Headers is a case-insensitive, insertion-ordered header map, mirroring Excon::Headers. A lookup matches keys case-insensitively while the original casing of the first-seen key is preserved for iteration and display (so "Content-Type" and "content-type" address the same entry).
func (*Headers) Delete ¶
Delete removes key (case-insensitive) if present, keeping the order of the remaining headers.
func (*Headers) Merge ¶
Merge overlays other's headers onto a copy of h and returns the result (an existing key is overwritten in place, a new key is appended), mirroring how a per-request :headers hash deep-merges over the connection's default headers.
type Middleware ¶
Middleware wraps a downstream Handler and returns a new one, mirroring an entry of Excon's :middlewares stack. The stack is composed outermost-first: a request-phase middleware acts before calling next, a response-phase middleware acts on the value next returns. Supply a stack via Options.Middlewares; the built-in retry (:idempotent) and status assertion (:expects) run inside it.
func RequestMiddleware ¶
func RequestMiddleware(fn func(req *Request) error) Middleware
RequestMiddleware builds a request-phase Middleware: fn runs before the downstream handler and may rewrite the request or abort by returning an error.
func ResponseMiddleware ¶
func ResponseMiddleware(fn func(resp *Response) error) Middleware
ResponseMiddleware builds a response-phase Middleware: fn runs on the Response the downstream handler returned (skipped when it errored) and may rewrite it or abort by returning an error.
type NetHTTPDoer ¶
type NetHTTPDoer struct {
// Client performs the request; defaults to http.DefaultClient.
Client httpClient
}
NetHTTPDoer is the default Doer: it turns a Request into a net/http request, executes it with its Client, captures the remote IP via httptrace, and maps the response (or a transport failure) into a Response or a transport Error. A timeout maps to KindTimeout; any other transport failure to KindSocket.
type Options ¶
type Options struct {
// Method is the HTTP method; defaults to "GET" when unset.
Method string
// Path overrides the request path (else the connection/URL path is used).
Path string
// Query is the URL query (:query); when non-nil it replaces the default.
Query *Query
// Headers are request headers (:headers); deep-merged over the defaults.
Headers *Headers
// Body is the request body (:body).
Body string
// Expects asserts the response status: a status not listed raises the
// matching status [Error]. An empty/nil Expects performs no assertion.
Expects []int
// Idempotent enables automatic retry of transport failures (:idempotent).
Idempotent bool
// RetryLimit is the maximum number of attempts for an idempotent request
// (:retry_limit); defaults to 4 on a connection.
RetryLimit int
// RetryInterval is the delay between idempotent retries, in milliseconds
// (:retry_interval).
RetryInterval int
// ReadTimeout/WriteTimeout/ConnectTimeout are per-request timeouts, in
// seconds, that a host transport may honour.
ReadTimeout int
WriteTimeout int
ConnectTimeout int
// User/Password set HTTP Basic credentials (:user/:password): a Basic
// Authorization header is added unless one is already present.
User string
Password string
// Middlewares is the outer middleware stack (:middlewares) wrapped around the
// built-in retry/expects core; when non-nil it replaces the default (none).
Middlewares []Middleware
}
Options configures a request (and, on New, the connection defaults), mirroring the option hash Excon.new / Excon.get accept. A per-request Options is merged over the connection's defaults: :headers deep-merge, every other present field overrides. A zero field is treated as unset and inherits the connection default (so, e.g., an empty Body keeps the connection's).
type Query ¶
type Query struct {
// contains filtered or unexported fields
}
Query is the ordered set of URL query parameters passed as the :query option, mirroring the Hash (or String) Excon accepts. Keys are emitted in insertion order and may repeat (an array value in Excon becomes several pairs sharing a key). Encoding is byte-faithful to Excon::Utils.query_string.
type QueryPair ¶
QueryPair is one entry of a Query: a key, its value, and whether a value is present. A key with no value (HasVal false) is emitted bare — matching Excon, which encodes a nil-valued query key as just the escaped key.
type Request ¶
type Request struct {
// Method is the upper-case HTTP method ("GET", "POST", …).
Method string
// URL is the absolute request URL, query string included.
URL string
// Headers are the outgoing request headers.
Headers *Headers
// Body is the outgoing request body ("" for none).
Body string
// ReadTimeout is the response-read timeout in seconds (0 = unset).
ReadTimeout int
// WriteTimeout is the request-write timeout in seconds (0 = unset).
WriteTimeout int
// ConnectTimeout is the connection-open timeout in seconds (0 = unset).
ConnectTimeout int
}
Request is the fully-resolved HTTP request handed to the transport Doer and threaded through the middleware stack. A Connection builds it from the merged options: the method, the absolute URL (scheme/host/port + path + encoded query), the outgoing headers (including any Basic-auth header), the body, and the per-request timeouts a host transport may honour.
type Response ¶
type Response struct {
// contains filtered or unexported fields
}
Response is the result of a request, mirroring Excon::Response: the status code, the body, the response headers, the reason phrase, and the remote IP the connection resolved to. It is produced by the transport Doer and returned unchanged (on a successful, expectation-satisfying request) to the caller.
func NewResponse ¶
NewResponse builds a Response from its parts. Transports (including a test stub) use it to report a completed round-trip.
func (*Response) ReasonPhrase ¶
ReasonPhrase returns the response reason phrase (Excon::Response#reason_phrase).
func (*Response) RemoteIp ¶
RemoteIp returns the resolved remote IP address (Excon::Response#remote_ip).
