httpx

package module
v1.0.4 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: MIT Imports: 26 Imported by: 0

README

httpx 🚀

A small, fluent Go HTTP client built on net/http with typed JSON decoding, retries, tracing, streaming, and configurable connection pooling.

Go Dependencies

Features

  • Fluent builders for GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, and QUERY
  • Typed JSON decoding with ToJSON[T]
  • Non-2xx handling via HTTPError
  • Typed JSON error bodies with ErrorInto
  • Configurable timeouts and connection pooling
  • Optional retries with bounded backoff
  • Request and response hooks for instrumentation
  • Bearer, Basic, and API key authentication
  • Multipart uploads, form submissions, raw bodies, and query parameters
  • Request logging and httptrace diagnostics
  • Streaming response copies via Response.CopyTo

Requirements

  • Go 1.23 or newer

Installation

go get github.com/thedevsaddam/httpx

Quick start

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/thedevsaddam/httpx"
)

type Todo struct {
	ID        int    `json:"id"`
	Title     string `json:"title"`
	Completed bool   `json:"completed"`
}

func main() {
	client := httpx.New(
		httpx.WithBaseURL("https://jsonplaceholder.typicode.com"),
		httpx.WithTimeout(10*time.Second),
	)

	todo, err := httpx.ToJSON[Todo](
		client.GET("/todos/1").Do(context.Background()),
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Todo #%d: %s\n", todo.ID, todo.Title)
}

ToJSON[T] decodes a successful JSON response and closes the response body automatically.

Common request examples

GET with query parameters
resp, err := client.GET("/users").
	QueryParam("status", "active").
	QueryParam("limit", "20").
	Header("Accept", "application/json").
	Do(ctx)
if err != nil {
	return err
}
defer resp.Close()

if !resp.IsSuccess() {
	return fmt.Errorf("request failed: %s", resp.Status)
}
POST JSON
type CreateUser struct {
	Name  string `json:"name"`
	Email string `json:"email"`
}

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

user, err := httpx.ToJSON[User](
	client.POST("/users").
		JSON(CreateUser{Name: "Jane Doe", Email: "jane@example.com"}).
		Do(ctx),
)
if err != nil {
	return err
}

fmt.Println("Created user:", user.ID)
POST URL-encoded form

Use Form to send application/x-www-form-urlencoded data, such as OAuth tokens or simple form submissions.

resp, err := client.POST("/oauth/token").
	Form(map[string]string{
		"grant_type":    "client_credentials",
		"client_id":     "my-client",
		"client_secret": "my-secret",
	}).
	Do(ctx)
if err != nil {
	return err
}
defer resp.Close()

if !resp.IsSuccess() {
	return fmt.Errorf("token request failed: %s", resp.Status)
}

Form URL-encodes the values and sets Content-Type: application/x-www-form-urlencoded.

If you need multiple values for a single key, use FormValues instead:

resp, err := client.POST("/search").
	FormValues(url.Values{
		"tag": {"golang", "backend"},
	}).
	Do(ctx)

Use either Form or FormValues, not both in the same request.

Streaming responses

Do returns a response whose body can be read directly. Use CopyTo to stream a large response to a writer without buffering the entire body in memory:

resp, err := client.GET("/exports/archive").Do(ctx)
if err != nil {
	return err
}
if !resp.IsSuccess() {
	defer resp.Close()
	return fmt.Errorf("download failed: %s", resp.Status)
}

file, err := os.Create("archive.zip")
if err != nil {
	_ = resp.Close()
	return err
}
defer file.Close()

if _, err := resp.CopyTo(file); err != nil {
	return err
}

WithMaxResponseBytes applies to ToJSON and HTTP error body reads. It does not limit manual streaming.

Multipart uploads and raw bodies

Multipart upload
resp, err := client.POST("/upload").
	Field("user_id", "12345").
	Fields(url.Values{"tag": {"tag-1", "tag-2"}}).
	File("avatar", "./avatar.png").
	FileReader("attachments", bytes.NewReader(report), "report.pdf").
	FileReader("attachments", logFile, "app.log").
	FileReader("attachments", pdfReader, "report.pdf", "application/pdf").
	Do(ctx)
if err != nil {
	return err
}
defer resp.Close()

if !resp.IsSuccess() {
	return fmt.Errorf("upload failed: %s", resp.Status)
}

The multipart implementation assembles the payload in memory. Consider this when uploading large files.

Raw request body
resp, err := client.POST("/messages").
	Header("Content-Type", "text/plain").
	Body(strings.NewReader("hello")).
	Do(ctx)
if err != nil {
	return err
}
defer resp.Close()

Handling HTTP errors

When ToJSON[T] receives a non-2xx response, it returns an *httpx.HTTPError. Use errors.As to inspect it:

user, err := httpx.ToJSON[User](client.GET("/users/{id}").PathParam("id", "1").Do(ctx))
if err != nil {
	var httpErr *httpx.HTTPError
	if errors.As(err, &httpErr) {
		log.Printf("HTTP %d for %s: %s", httpErr.StatusCode, httpErr.URL, httpErr.Body)
	}
	return err
}
_ = user

HTTPError.Body is a bounded excerpt, not necessarily the full response. Avoid logging it if it could contain secrets or personal information.

Decoding structured error bodies

Many APIs describe failures in a JSON body, such as {"code": "...", "message": "..."}. Pass a pointer to your own struct with ErrorInto and httpx fills it in when the response is not 2xx:

type APIError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

var apiErr APIError
user, err := httpx.ToJSON[User](
	client.GET("/users/123").ErrorInto(&apiErr).Do(ctx),
)
if err != nil {
	var httpErr *httpx.HTTPError
	if errors.As(err, &httpErr) {
		if httpErr.Decoded {
			return fmt.Errorf("api error %s: %s (HTTP %d)", apiErr.Code, apiErr.Message, httpErr.StatusCode)
		}
		// Not JSON (an HTML page from a proxy, plain text, ...): use the raw excerpt.
		return fmt.Errorf("HTTP %d: %s", httpErr.StatusCode, httpErr.Body)
	}
	return err // network error, timeout, and so on
}
_ = user

How it behaves:

  • The error is still an *httpx.HTTPError, so existing errors.As checks keep working. ErrorInto only adds the decoded struct and the Decoded flag.
  • Check httpErr.Decoded before reading your struct. It is true only when the body was decoded without error.
  • Only JSON responses are decoded: Content-Type of application/json or any +json type (such as application/problem+json). A missing Content-Type counts as JSON. HTML, plain text and other types are skipped and left in HTTPError.Body.
  • The whole error body is read for decoding, up to WithMaxResponseBytes (10 MiB by default). A body larger than that is not decoded. HTTPError.Body stays a short excerpt either way.
  • A 2xx response never touches your struct, and requests without ErrorInto behave as before.
  • It works with ToJSON, ToBytes and ToString. Pass a non-nil pointer; anything else makes Do return an error.
var apiErr APIError

// Any of these fills apiErr on a non-2xx response:
user, err := httpx.ToJSON[User](client.GET(u).ErrorInto(&apiErr).Do(ctx))
raw,  err := httpx.ToBytes(client.GET(u).ErrorInto(&apiErr).Do(ctx))
text, err := httpx.ToString(client.GET(u).ErrorInto(&apiErr).Do(ctx))

Request Inspection

httpx provides ToText() and ToCURL() to inspect outgoing HTTP requests without sending them. These methods are useful for debugging, troubleshooting API integrations, and reproducing requests.

ToText()

ToText() returns a human-readable representation of the outgoing HTTP request, including its method, URL, headers, and body.

req := httpx.New().
    POST("https://api.example.com/users").
    Header("Authorization", "Bearer your-token").
    JSON(map[string]any{
        "name": "John Doe",
        "email": "john@example.com",
    })

text, err := req.ToText(ctx)
if err != nil {
    log.Fatal(err)
}

fmt.Println(text)

Example output:

POST /users HTTP/1.1
Host: api.example.com
Authorization: Bearer your-token
Content-Type: application/json

{"email":"john@example.com","name":"John Doe"}
ToCURL()

ToCURL() returns an equivalent cURL command that you can copy into a terminal to reproduce the request.

curl, err := req.ToCURL(ctx)
if err != nil {
    log.Fatal(err)
}

fmt.Println(curl)

Example output:

curl -X 'POST' 'https://api.example.com/users' \
  -H 'Authorization: Bearer your-token' \
  -H 'Content-Type: application/json' \
  -d '{"email":"john@example.com","name":"John Doe"}'
Notes
  • Both methods build the outgoing request without sending it.
  • ToText() is useful for inspecting request details in logs or during development.
  • ToCURL() is useful for reproducing requests in a terminal or sharing them for debugging.
  • Request inspection may involve reading the request body. Verify body replay behavior for your request type, especially when using streaming bodies or file uploads.
  • Treat the output as sensitive: it may contain authorization headers, cookies, API keys, or personal data. Avoid logging or sharing it in production without appropriate redaction.
  • For arbitrary binary bodies and multipart requests, verify that the generated cURL command preserves the original request data before relying on it for reproduction.

Client configuration

Configure the client once and reuse it across requests:

client := httpx.New(
	httpx.WithBaseURL("https://api.example.com/v1"),
	httpx.WithClientName("billing-service"),
	httpx.WithTimeout(10*time.Second),
	httpx.WithHeader("Content-Type", "application/json"),
	httpx.WithHeader("Accept", "application/json"),
	httpx.WithAuth(httpx.NewBearerAuth("<YOUR_TOKEN>")),
	httpx.WithMaxResponseBytes(5<<20), // 5 MiB for helper reads
	httpx.WithRetryPolicy(httpx.StandardRetryPolicy()),
	httpx.WithPoolConfig(httpx.PoolConfig{
		MaxIdleConns:          100,
		MaxIdleConnsPerHost:   20,
		MaxConnsPerHost:       50,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ResponseHeaderTimeout: 5 * time.Second,
	}),
)
Configuration options
Option Description
WithBaseURL(url) Base URL for relative request paths.
WithTimeout(d) Default request timeout; 30 seconds by default.
WithHeader(key, value) Default request header.
WithQueryParam(key, value) Default query parameter.
WithClientName(name) Sets the X-Client-Name header.
WithAuth(auth) Default authentication provider.
WithLogger(logger) Structured request logging.
WithTracer(tracer) Injects distributed-tracing context into requests.
WithPoolConfig(cfg) Configures the standard transport connection pool and timeouts.
WithMaxResponseBytes(n) Maximum body size read by JSON and HTTP-error helpers; default is 10 MiB.
WithRetryPolicy(policy) Enables bounded retries. Retries are disabled by default.
WithRequestHook(hook) Called before each network attempt.
WithResponseHook(hook) Called after response headers arrive.
WithTransport(transport) Supplies a custom *http.Transport.
WithHTTPClient(client) Supplies a custom *http.Client.

For PoolConfig, zero values keep the transport defaults. Pool settings apply when the client uses a standard *http.Transport.

Retries

MaxAttempts includes the initial request. For a standard policy, use StandardRetryPolicy(): three total attempts, retries for network errors and HTTP 429/502/503/504, with capped exponential backoff.

client := httpx.New(
	httpx.WithRetryPolicy(httpx.StandardRetryPolicy()),
)

For a custom policy, set Jitter: true to randomize each delay between zero and its exponential backoff cap, and RespectRetryAfter: true to honor Retry-After on retryable responses:

client := httpx.New(
	httpx.WithRetryPolicy(httpx.RetryPolicy{
		MaxAttempts:          3,
		InitialBackoff:       100 * time.Millisecond,
		MaxBackoff:           time.Second,
		RetryableStatusCodes: []int{429, 502, 503, 504},
		RetryNetworkErrors:   true,
		Jitter:               true,
		RespectRetryAfter:    true,
	}),
)

By default, retries are restricted to supported idempotent methods (GET, HEAD, OPTIONS, DELETE, and QUERY) and replayable request bodies. To explicitly opt a request such as POST into retries, use Retryable() and set an idempotency key when appropriate.

resp, err := client.POST("/orders").
	IdempotencyKey("order-12345").
	JSON(payload).
	Retryable().
	Do(ctx)

For operations that create payments, orders, or other side effects, prefer a server-supported idempotency key; the client cannot guarantee deduplication by itself.

Hooks

Use hooks for metrics, correlation IDs, or instrumentation:

client := httpx.New(
	httpx.WithRequestHook(func(ctx context.Context, req *http.Request) {
		req.Header.Set("X-Request-Source", "billing-service")
	}),
	httpx.WithResponseHook(func(ctx context.Context, resp *http.Response) {
		// Record status and latency in your metrics system.
	}),
)

Hooks may run concurrently, so protect any shared state they access.

Authentication

// Bearer token
client := httpx.New(httpx.WithAuth(httpx.NewBearerAuth("your-token")))

// Basic authentication
client = httpx.New(httpx.WithAuth(httpx.NewBasicAuth("username", "password")))

// API key authentication
client = httpx.New(httpx.WithAuth(httpx.NewAPIKeyAuth("X-API-Key", "your-api-key")))

You can implement the httpx.Auth interface to provide custom authentication. A request can override the client default with .Auth(auth).

Logging and tracing

Implement httpx.Logger to connect your preferred structured logger:

type MyLogger struct{}

func (l *MyLogger) Debug(ctx context.Context, msg string, kv ...any) {}
func (l *MyLogger) Info(ctx context.Context, msg string, kv ...any)  {}
func (l *MyLogger) Error(ctx context.Context, msg string, kv ...any) {}

Implement httpx.Tracer to inject tracing headers:

type MyTracer struct{}

func (t *MyTracer) Inject(ctx context.Context, req *http.Request) {
	// Inject trace context into req.
}

Call .Debug() on an individual request to log timing and connection diagnostics. Read the trace snapshot with resp.Trace() or the formatted report with resp.Debug(). Trace timings are best used for debugging and performance analysis.

Concurrency and response lifecycle

  • Reuse a configured client for concurrent requests; the underlying net/http client and transport support concurrent use.
  • Build a separate request builder for each concurrent operation. Do not mutate and send the same builder concurrently.
  • Hooks and custom auth, logger, and tracer implementations must be safe for concurrent use if shared by requests.
  • Always close response bodies when reading responses directly. ToJSON[T] closes the body automatically.
  • Response.Close() closes the body and attempts to drain a small amount to improve connection reuse.

Testing

Run the test suite and static checks from the module root:

go test ./...
go test -race ./...
go vet ./...

API reference

API Description
New(options...) Creates a client with pooling enabled by default.
client.GET(url) / POST / PUT / PATCH / DELETE / HEAD / OPTIONS / QUERY Creates a request builder.
builder.Header(key, value) / Headers(map) Sets request headers.
builder.QueryParam(key, value) / QueryParams(map) Adds query parameters.
builder.JSON(value) Encodes a JSON request body.
builder.Body(reader) Sets a raw request body.
builder.File(field, path) / Field(key, value) Adds multipart fields.
builder.Auth(auth) / Timeout(d) Overrides request authentication or timeout.
builder.Debug() Enables request diagnostics.
builder.Do(ctx) Executes the request.
resp.IsSuccess() Reports whether the status code is 2xx.
resp.CopyTo(writer) Streams the response body to a writer and closes it.
resp.Close() Closes the response body.
resp.Trace() / resp.Debug() Returns trace information or a formatted report.
ToJSON[T](resp, err) Decodes JSON and closes the response body.

License

The project is licensed under the MIT License.

Documentation

Overview

Package httpx is a small fluent HTTP client on top of net/http, with retries, request tracing and typed response helpers.

Index

Constants

View Source
const Version = "v1.0.3"

Variables

View Source
var ErrEmptyBody = errors.New("httpx: empty response body")

ErrEmptyBody is returned by ToJSON for a successful response with no body (a 204, say).

Functions

func IsTimeout

func IsTimeout(err error) bool

IsTimeout reports whether err is a timeout or a deadline error.

func ToBytes

func ToBytes(resp *Response, err error) ([]byte, error)

ToBytes reads and closes the body of a 2xx response.

func ToJSON

func ToJSON[T any](resp *Response, err error) (T, error)

ToJSON decodes a 2xx response into T and closes the body. An empty body returns ErrEmptyBody.

func ToString

func ToString(resp *Response, err error) (string, error)

ToString is like ToBytes but returns a string.

Types

type APIKeyAuth

type APIKeyAuth struct {
	Header string
	Key    string
}

APIKeyAuth sends an API key in a header such as X-API-Key.

func (APIKeyAuth) Apply

func (a APIKeyAuth) Apply(req *http.Request) error

type APIKeyQueryAuth

type APIKeyQueryAuth struct {
	Param string
	Key   string
}

APIKeyQueryAuth sends an API key as a query parameter. Prefer the header variant when the server supports it: httpx keeps query strings out of its logs and errors, but proxies and servers may still record them.

func (APIKeyQueryAuth) Apply

func (a APIKeyQueryAuth) Apply(req *http.Request) error

type Auth

type Auth interface {
	Apply(req *http.Request) error
}

Auth adds credentials to outgoing requests.

func NewAPIKeyAuth

func NewAPIKeyAuth(header, key string) Auth

func NewAPIKeyQueryAuth

func NewAPIKeyQueryAuth(param, key string) Auth

func NewBasicAuth

func NewBasicAuth(username, password string) Auth

func NewBearerAuth

func NewBearerAuth(token string) Auth

type BasicAuth

type BasicAuth struct{ Username, Password string }

BasicAuth sends HTTP Basic credentials.

func (BasicAuth) Apply

func (b BasicAuth) Apply(req *http.Request) error

type BearerAuth

type BearerAuth struct{ Token string }

BearerAuth sends an Authorization: Bearer header.

func (BearerAuth) Apply

func (b BearerAuth) Apply(req *http.Request) error

type Client

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

Client is safe for concurrent use once built with New.

func New

func New(options ...Option) *Client

New creates a client. Connection pooling is on by default.

func (*Client) Close

func (c *Client) Close()

Close drops idle connections held by the transport.

func (*Client) DELETE

func (c *Client) DELETE(url string) *RequestBuilder

func (*Client) GET

func (c *Client) GET(url string) *RequestBuilder

func (*Client) HEAD

func (c *Client) HEAD(url string) *RequestBuilder

func (*Client) OPTIONS

func (c *Client) OPTIONS(url string) *RequestBuilder

func (*Client) PATCH

func (c *Client) PATCH(url string) *RequestBuilder

func (*Client) POST

func (c *Client) POST(url string) *RequestBuilder

func (*Client) PUT

func (c *Client) PUT(url string) *RequestBuilder

func (*Client) QUERY

func (c *Client) QUERY(url string) *RequestBuilder

QUERY is the safe, idempotent QUERY method, normally with the query in the body. The server and any proxies in between have to support it.

type HTTPError

type HTTPError struct {
	Method     string
	URL        string
	StatusCode int
	Status     string
	Header     http.Header
	Body       string
	// Decoded is true when the body was decoded into the value given to ErrorInto.
	// Only JSON responses are decoded (see ErrorInto).
	Decoded bool
}

HTTPError is what you get for a non-2xx response. Body is cut off at about 4 KiB and URL has no query string.

func (*HTTPError) Error

func (e *HTTPError) Error() string

type Logger

type Logger interface {
	Debug(ctx context.Context, msg string, keysAndValues ...any)
	Info(ctx context.Context, msg string, keysAndValues ...any)
	Error(ctx context.Context, msg string, keysAndValues ...any)
}

Logger is an optional structured logger.

type Middleware

type Middleware func(http.RoundTripper) http.RoundTripper

Middleware wraps a RoundTripper. The first middleware given is the outermost.

type Option

type Option func(*Client)

Option configures a Client. Options only record what was asked for and New builds the http.Client once at the end, so their order doesn't matter.

func WithAuth

func WithAuth(auth Auth) Option

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL sets the base for relative paths. Its own path is kept, so "https://x.com/v1" plus "/users" gives "https://x.com/v1/users".

func WithCheckRedirect

func WithCheckRedirect(fn func(req *http.Request, via []*http.Request) error) Option

WithCheckRedirect sets the redirect policy. Return http.ErrUseLastResponse to stop following.

func WithClientName

func WithClientName(value string) Option

WithClientName sets the X-Client-Name header.

func WithCookieJar

func WithCookieJar(jar http.CookieJar) Option

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient builds on a copy of client, the original isn't modified. Its Timeout is the default request timeout unless WithTimeout is also set.

func WithHeader

func WithHeader(key, value string) Option

func WithLogger

func WithLogger(logger Logger) Option

func WithMaxResponseBytes

func WithMaxResponseBytes(n int64) Option

WithMaxResponseBytes caps what ToJSON, ToBytes and ToString will read. A body you read yourself from Do is not limited.

func WithMiddleware

func WithMiddleware(m ...Middleware) Option

WithMiddleware adds RoundTripper middlewares (metrics, rate limiting, ...).

func WithPoolConfig

func WithPoolConfig(cfg PoolConfig) Option

WithPoolConfig sets up connection pooling. Needs an *http.Transport underneath.

func WithProxy

func WithProxy(proxy func(*http.Request) (*url.URL, error)) Option

WithProxy sets the proxy function. Pass nil to turn proxies off.

func WithQueryParam

func WithQueryParam(key, value string) Option

func WithRequestHook

func WithRequestHook(h RequestHook) Option

func WithResponseHook

func WithResponseHook(h ResponseHook) Option

func WithRetryPolicy

func WithRetryPolicy(p RetryPolicy) Option

WithRetryPolicy turns on retries. Only idempotent methods with replayable bodies are retried.

func WithTLSConfig

func WithTLSConfig(cfg *tls.Config) Option

WithTLSConfig sets the TLS config, e.g. custom CAs or client certs for mTLS. Needs an *http.Transport underneath.

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout sets the default timeout for a request attempt. Zero disables it.

func WithTracer

func WithTracer(tracer Tracer) Option

func WithTransport

func WithTransport(transport http.RoundTripper) Option

type PoolConfig

type PoolConfig struct {
	MaxIdleConns          int
	MaxIdleConnsPerHost   int
	MaxConnsPerHost       int
	IdleConnTimeout       time.Duration
	TLSHandshakeTimeout   time.Duration
	ResponseHeaderTimeout time.Duration
}

PoolConfig tunes the connection pool of the default transport. Zero values leave the transport's own setting alone.

type RequestBuilder

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

RequestBuilder builds a single request. Don't share one between goroutines that send it at the same time.

func (*RequestBuilder) Auth

func (rb *RequestBuilder) Auth(auth Auth) *RequestBuilder

Auth overrides the client's auth for this request.

func (*RequestBuilder) Body

func (rb *RequestBuilder) Body(body io.Reader) *RequestBuilder

Body sets a raw body. For retries it has to be replayable, which *bytes.Buffer, *bytes.Reader and *strings.Reader are.

func (*RequestBuilder) Debug

func (rb *RequestBuilder) Debug() *RequestBuilder

Debug prints the trace once the headers arrive or the request fails, to the logger if there is one and to stderr otherwise.

func (*RequestBuilder) Do added in v1.0.1

func (rb *RequestBuilder) Do(ctx context.Context) (*Response, error)

Do sends the request. The caller has to close the response body.

The timeout applies to each attempt on its own, so a 10s timeout gives every retry a fresh 10s, and on the last attempt it keeps running until the body is closed. ctx bounds the whole call, backoff waits included.

func (*RequestBuilder) ErrorInto added in v1.0.3

func (rb *RequestBuilder) ErrorInto(v any) *RequestBuilder

ErrorInto sets where the JSON body of a non-2xx response is decoded, so ToJSON, ToBytes and ToString fill it in and still return an *HTTPError:

var apiErr APIError
todo, err := httpx.ToJSON[Todo](client.GET(u).ErrorInto(&apiErr).Do(ctx))

Only responses with a JSON Content-Type (application/json or a +json type) are decoded; a missing Content-Type counts as JSON. HTML, plain text and other types are left alone and stay available in HTTPError.Body.

v has to be a non-nil pointer. With it set, the whole error body is read (up to the client's max response size) for decoding; HTTPError.Body still holds only a short excerpt, and HTTPError.Decoded says whether decoding worked.

func (*RequestBuilder) Field

func (rb *RequestBuilder) Field(key, value string) *RequestBuilder

Field adds a multipart text field.

func (*RequestBuilder) Fields

func (rb *RequestBuilder) Fields(fields url.Values) *RequestBuilder

Fields adds several multipart text fields.

func (*RequestBuilder) File

func (rb *RequestBuilder) File(fieldName, filePath string) *RequestBuilder

File attaches a local file to a multipart upload. Call it as often as you like, even with the same field name; parts go out in call order.

func (*RequestBuilder) FileReader

func (rb *RequestBuilder) FileReader(fieldName string, r io.Reader, filename string, contentType ...string) *RequestBuilder

FileReader attaches the content of r as a multipart file part. The content type is optional and falls back to the filename's extension.

r is read once, when Do builds the body, and the result is buffered in memory so retries can replay it. You still own r and should close it. Since r is used up, sending the same builder twice needs a fresh reader.

func (*RequestBuilder) Form

func (rb *RequestBuilder) Form(fields map[string]string) *RequestBuilder

Form sends URL-encoded form data, one value per key.

func (*RequestBuilder) FormValues

func (rb *RequestBuilder) FormValues(values url.Values) *RequestBuilder

FormValues is Form with several values allowed per key.

func (*RequestBuilder) Header

func (rb *RequestBuilder) Header(key, value string) *RequestBuilder

func (*RequestBuilder) Headers

func (rb *RequestBuilder) Headers(headers map[string]string) *RequestBuilder

func (*RequestBuilder) IdempotencyKey

func (rb *RequestBuilder) IdempotencyKey(key string) *RequestBuilder

IdempotencyKey sets the Idempotency-Key header and marks the request retryable, so POST and PATCH can be retried against servers that honor it.

func (*RequestBuilder) JSON

func (rb *RequestBuilder) JSON(v any) *RequestBuilder

JSON encodes v as the request body without escaping <, > and &. An encoding error is returned from Do.

func (*RequestBuilder) PathParam

func (rb *RequestBuilder) PathParam(name, value string) *RequestBuilder

PathParam fills in "{name}" in the URL with the escaped value, e.g. "/users/{id}".

func (*RequestBuilder) QueryParam

func (rb *RequestBuilder) QueryParam(key, value string) *RequestBuilder

QueryParam adds a query parameter.

func (*RequestBuilder) QueryParams

func (rb *RequestBuilder) QueryParams(params map[string]string) *RequestBuilder

QueryParams sets query parameters, replacing existing values for the same keys.

func (*RequestBuilder) Retryable

func (rb *RequestBuilder) Retryable() *RequestBuilder

Retryable lets a non-idempotent request (a POST, say) use the retry policy. Only do that when repeating it is harmless, ideally with an idempotency key, and the body can be replayed.

func (*RequestBuilder) Timeout

func (rb *RequestBuilder) Timeout(timeout time.Duration) *RequestBuilder

Timeout overrides the client timeout for this request. It's per attempt; the context passed to Do still limits the whole call.

func (*RequestBuilder) ToCURL added in v1.0.4

func (rb *RequestBuilder) ToCURL(ctx context.Context) (string, error)

ToCURL returns a cURL command representation of the outgoing HTTP request without sending it.

func (*RequestBuilder) ToText added in v1.0.4

func (rb *RequestBuilder) ToText(ctx context.Context) (string, error)

ToText returns a textual representation of the outgoing HTTP request without sending it.

type RequestHook

type RequestHook func(context.Context, *http.Request)

RequestHook runs right before every attempt. It must be safe for concurrent use.

type Response

type Response struct {
	*http.Response
	// contains filtered or unexported fields
}

Response wraps http.Response.

func (*Response) Close

func (r *Response) Close() error

Close drains up to 4 KiB so the connection can be reused, then closes the body. The cap stops a huge response from blocking Close.

func (*Response) CopyTo

func (r *Response) CopyTo(dst io.Writer) (int64, error)

CopyTo streams the body into dst and closes it afterwards.

func (*Response) Debug

func (r *Response) Debug() string

Debug returns the trace as a printable report.

func (*Response) IsSuccess

func (r *Response) IsSuccess() bool

IsSuccess reports whether the status is 2xx.

func (*Response) Trace

func (r *Response) Trace() TraceInfo

Trace returns a snapshot of the request timings.

type ResponseHook

type ResponseHook func(context.Context, *http.Response)

ResponseHook runs after the headers of every attempt arrive. It must be safe for concurrent use.

type RetryPolicy

type RetryPolicy struct {
	MaxAttempts          int
	InitialBackoff       time.Duration
	MaxBackoff           time.Duration
	RetryableStatusCodes []int
	RetryNetworkErrors   bool
	Jitter               bool
	RespectRetryAfter    bool
}

RetryPolicy controls retries. MaxAttempts counts the first try, so anything below 2 means no retries. With Jitter each backoff is a random value between zero and the exponential cap. With RespectRetryAfter, a server asking for more than MaxBackoff (when set) is not waited for: the response is handed back to the caller instead.

func AggressiveRetryPolicy added in v1.0.2

func AggressiveRetryPolicy() RetryPolicy

AggressiveRetryPolicy retries more often, and on 500s too.

func BackgroundRetryPolicy added in v1.0.2

func BackgroundRetryPolicy() RetryPolicy

BackgroundRetryPolicy is for batch jobs where nobody is waiting on the result.

func FastRetryPolicy added in v1.0.2

func FastRetryPolicy() RetryPolicy

FastRetryPolicy is meant for quick internal calls.

func NoRetryPolicy added in v1.0.2

func NoRetryPolicy() RetryPolicy

NoRetryPolicy turns retries off.

func RateLimitRetryPolicy added in v1.0.2

func RateLimitRetryPolicy() RetryPolicy

RateLimitRetryPolicy is tuned for 429s.

func ResilientRetryPolicy added in v1.0.2

func ResilientRetryPolicy() RetryPolicy

ResilientRetryPolicy is for flaky third-party APIs.

func StandardRetryPolicy

func StandardRetryPolicy() RetryPolicy

StandardRetryPolicy is a sane default: three attempts, network errors plus 429/502/503/504, jittered backoff capped at 2s, and Retry-After honored.

type TraceInfo

type TraceInfo struct {
	Method         string
	URL            string
	Path           string
	StatusCode     int
	DNSLookup      time.Duration
	ConnTime       time.Duration
	TCPConnTime    time.Duration
	TLSHandshake   time.Duration
	ServerTime     time.Duration
	ResponseTime   time.Duration
	TotalTime      time.Duration
	IsConnReused   bool
	IsConnWasIdle  bool
	ConnIdleTime   time.Duration
	RequestAttempt int
	RemoteAddr     string
	// contains filtered or unexported fields
}

TraceInfo holds timings collected through net/http/httptrace. They're best-effort: some stay zero on a reused connection, behind a proxy, or when the transport doesn't fire the hook. TotalTime stops when the response headers arrive. URL never carries the query string since it may hold secrets.

func (*TraceInfo) Snapshot

func (t *TraceInfo) Snapshot() TraceInfo

Snapshot returns a copy that is safe to read while the request is still running.

func (*TraceInfo) String

func (t *TraceInfo) String() string

type Tracer

type Tracer interface {
	Inject(ctx context.Context, req *http.Request)
}

Tracer injects trace context into outgoing requests.

Jump to

Keyboard shortcuts

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