package xhttp

import "git.sr.ht/~jamesponddotco/xstd-go/xnet/xhttp"

Package xhttp provides utility functions for HTTP requests.

Index

Constants

const (
	// ErrNotHTTPS is returned when trying to fetch resources from a non-secure
	// URI.
	ErrNotHTTPS xerrors.Error = "uri is not secure; must use HTTPS"

	// ErrDomainNotTrusted is returned when trying to fetch resources from a
	// domain outside the list of allowed domains.
	ErrDomainNotTrusted xerrors.Error = "domain is not trusted"

	// ErrNilHTTPClient is returned when trying to fetch resources without an
	// HTTP client.
	ErrNilHTTPClient xerrors.Error = "HTTP client cannot be nil"

	// ErrRequestFailed is returned when a request fails for unknown reasons.
	ErrRequestFailed xerrors.Error = "request failed with status code"

	// ErrExceededMaxContentLength is returned when trying to fetch resources
	// bigger than the allowed MaxContentLength.
	ErrExceededMaxContentLength xerrors.Error = "response body exceeds maximum content length"
)
const (
	Age                           string = "Age"
	AltSCV                        string = "Alt-Svc"
	Accept                        string = "Accept"
	AcceptCharset                 string = "Accept-Charset"
	AcceptPatch                   string = "Accept-Patch"
	AcceptRanges                  string = "Accept-Ranges"
	AcceptedLanguage              string = "Accept-Language"
	AcceptEncoding                string = "Accept-Encoding"
	Authorization                 string = "Authorization"
	CrossOriginResourcePolicy     string = "Cross-Origin-Resource-Policy"
	CacheControl                  string = "Cache-Control"
	Connection                    string = "Connection"
	ContentDisposition            string = "Content-Disposition"
	ContentEncoding               string = "Content-Encoding"
	ContentLength                 string = "Content-Length"
	ContentType                   string = "Content-Type"
	ContentLanguage               string = "Content-Language"
	ContentLocation               string = "Content-Location"
	ContentRange                  string = "Content-Range"
	Date                          string = "Date"
	DeltaBase                     string = "Delta-Base"
	ETag                          string = "ETag"
	Expires                       string = "Expires"
	Host                          string = "Host"
	IM                            string = "IM"
	IfMatch                       string = "If-Match"
	IfModifiedSince               string = "If-Modified-Since"
	IfNoneMatch                   string = "If-None-Match"
	IfRange                       string = "If-Range"
	IfUnmodifiedSince             string = "If-Unmodified-Since"
	KeepAliveK                    string = "Keep-Alive"
	LastModified                  string = "Last-Modified"
	Link                          string = "Link"
	Pragma                        string = "Pragma"
	ProxyAuthenticate             string = "Proxy-Authenticate"
	ProxyAuthorization            string = "Proxy-Authorization"
	PublicKeyPins                 string = "Public-Key-Pins"
	RequestID                     string = "Request-Id"
	RetryAfter                    string = "Retry-After"
	Referer                       string = "Referer"
	Server                        string = "Server"
	SetCookie                     string = "Set-Cookie"
	StrictTransportSecurity       string = "Strict-Transport-Security"
	Trailer                       string = "Trailer"
	TK                            string = "Tk"
	TransferEncoding              string = "Transfer-Encoding"
	Location                      string = "Location"
	Upgrade                       string = "Upgrade"
	Vary                          string = "Vary"
	Via                           string = "Via"
	Warning                       string = "Warning"
	WWWAuthenticate               string = "WWW-Authenticate"
	XForwardedFor                 string = "X-Forwarded-For"
	XForwardedHost                string = "X-Forwarded-Host"
	XForwardedProto               string = "X-Forwarded-Proto"
	XRealIP                       string = "X-Real-Ip"
	XContentTypeOptions           string = "X-Content-Type-Options"
	XFrameOptions                 string = "X-Frame-Options"
	XXSSProtection                string = "X-XSS-Protection"
	XDNSPrefetchControl           string = "X-DNS-Prefetch-Control"
	Allow                         string = "Allow"
	Origin                        string = "Origin"
	AccessControlAllowOrigin      string = "Access-Control-Allow-Origin"
	AccessControlAllowCredentials string = "Access-Control-Allow-Credentials"
	AccessControlAllowHeaders     string = "Access-Control-Allow-Headers"
	AccessControlAllowMethods     string = "Access-Control-Allow-Methods"
	AccessControlExposeHeaders    string = "Access-Control-Expose-Headers"
	AccessControlMaxAge           string = "Access-Control-Max-Age"
	AccessControlRequestHeaders   string = "Access-Control-Request-Headers"
	AccessControlRequestMethod    string = "Access-Control-Request-Method"
	TimingAllowOrigin             string = "Timing-Allow-Origin"
	UserAgent                     string = "User-Agent"
)

Common HTTP header keys. Copied from pkg/net/http/header.go for convenience.

const (
	NoCache               string = "no-cache"
	KeepAliveV            string = "keep-alive"
	Close                 string = "close"
	ApplicationJSON       string = "application/json"
	FormData              string = "multipart/form-data"
	TextHTML              string = "text/html"
	TextPlain             string = "text/plain"
	TextCSS               string = "text/css"
	TextJavascript        string = "text/javascript"
	ApplicationJavascript string = "application/javascript"
	ApplicationXML        string = "application/xml"
	ImageJPEG             string = "image/jpeg"
	ImagePNG              string = "image/png"
	ImageGIF              string = "image/gif"
	ImageSVG              string = "image/svg+xml"
	CharsetUTF8           string = "charset=utf-8"
	Gzip                  string = "gzip"
	Deflate               string = "deflate"
	Brotli                string = "br"
	Wildcard              string = "*"
	SameOrigin            string = "same-origin"
	Deny                  string = "deny"
	SameSiteLax           string = "Lax"
	SameSiteStrict        string = "Strict"
	SameSiteNone          string = "None"
	Secure                string = "secure"
	NoTransform           string = "no-transform"
	Chunked               string = "chunked"
	True                  string = "true"
	False                 string = "false"
)

Common HTTP header values.

const (
	// ErrMissingRequest indicates that a nil http.Request was provided to the
	// ClientIP function.
	ErrMissingRequest xerrors.Error = "missing http.Request"

	// ErrInvalidIP indicates that no valid IP address could be extracted from
	// the given http.Request.
	ErrInvalidIP xerrors.Error = "invalid IP address"
)
const (
	// ErrCannotDrainResponse is returned when a response body cannot be drained.
	ErrCannotDrainResponse xerrors.Error = "cannot drain response body"

	// ErrCannotCloseResponse is returned when a response body cannot be closed.
	ErrCannotCloseResponse xerrors.Error = "cannot close response body"
)
const (
	// DefaultMaxRetries is the default maximum number of times a request will
	// be retried.
	DefaultMaxRetries int = 3

	// DefaultMinRetryDelay is the default minimum duration to wait before
	// retrying a request.
	DefaultMinRetryDelay time.Duration = 1 * time.Second

	// DefaultMaxRetryDelay is the default maximum duration to wait before
	// retrying a request.
	DefaultMaxRetryDelay time.Duration = 30 * time.Second
)
const (
	// DefaultTransportMaxIddleConns is the default maximum number of idle
	// connections in the pool.
	DefaultTransportMaxIddleConns int = 100

	// DefaultTransportIdleConnTimeout is the default maximum amount of time a
	// connection can remain idle before being closed.
	DefaultTransportIdleConnTimeout = 90 * time.Second

	// DefaultTransportTLSHandshakeTimeout is the default maximum amount of time
	// allowed to complete a TLS handshake.
	DefaultTransportTLSHandshakeTimeout = 10 * time.Second

	// DefaultTransportExpectContinueTimeout is the default maximum amount of
	// time to wait for a server's first response headers after fully writing
	// the request headers if the request has an "Expect: 100-continue" header.
	DefaultTransportExpectContinueTimeout = 1 * time.Second
)
const (
	UAEdgeWindows    = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0"
	UAChromeWindows  = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
	UAFirefoxWindows = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0"
	UASafariMacOS    = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.1"
)

Common user agent strings. Copied from Useragents.me for convenience.

const DefaultClientTimeout = 15 * time.Second

DefaultClientTimeout is the default timeout for the http.Client.

const ErrExceededMaxRetries xerrors.Error = "exceeded max retries"

ErrExceededMaxRetries is returned when the maximum number of retries has been exceeded.

Functions

func ClientIP

func ClientIP(req *http.Request) (string, error)

ClientIP implements a best-effort algorithm for determining the real IP address of the client.

Under the hood, it prioritizes the rightmost IP from the last instance of the X-Forwarded-For header and falls back to the RemoteAddr field of http.Request if the header is missing.

func DefaultIsRetryable

func DefaultIsRetryable(resp *http.Response, err error) bool

DefaultIsRetryable defines the default logic to determine if a request should be retried.

It returns true if an error occurs or the response status code indicates a retry may be successful (e.g., 408, 429, 502, 503, 504).

func DrainResponseBody

func DrainResponseBody(resp *http.Response) error

DrainResponseBody reads and discards the remaining content of the response body until EOF, then closes it. If an error occurs while draining or closing the response body, an error is returned.

func NewClient

func NewClient(timeout time.Duration) *http.Client

NewClient returns a new http.Client given the provided timeout. Unlike Go's http.DefaultClient: - It is not shared, thus cannot be altered by other modules. - It doesn't follow redirects. - It doesn't accept cookies.

A zero timeout means to use a default timeout.

func NewRetryingClient

func NewRetryingClient(timeout time.Duration, policy *RetryPolicy, logger *slog.Logger) *http.Client

NewRetryingClient returns a new http.Client given the provided timeout and RetryPolicy. Unlike Go's http.DefaultClient: - It is not shared, thus cannot be altered by other modules. - It doesn't follow redirects. - It doesn't accept cookies. - It retries failed requests.

A zero timeout means to use a default timeout. A nil policy means to use a default policy. A nil logger means to not log.

func NewTransport

func NewTransport() *http.Transport

NewTransport returns a new http.Transport with similar default values to http.DefaultTransport, but is not shared, thus cannot be altered by other modules.

Types

type Fetcher

type Fetcher interface {
	// Fetch fetches the resource at the given URL and returns the response as
	// a byte array.
	Fetch(ctx context.Context, uri string) ([]byte, error)
}

Fetcher is an interface implemented by HTTP clients to fetch resources from a given URL.

type ResponseError

type ResponseError struct {
	// Message is a human-readable message describing the error.
	Message string `json:"message"`

	// Documentation is a link to documentation describing the error.
	Documentation string `json:"documentation,omitempty"`

	// Code is a machine-readable code describing the error.
	Code int `json:"code"`
}

ResponseError represents the response returned by the HTTP server when an error occurs.

func (ResponseError) Error

func (e ResponseError) Error() string

Error implements the Error interface.

func (ResponseError) Write

func (e ResponseError) Write(ctx context.Context, logger *slog.Logger, w http.ResponseWriter)

Write serializes the ResponseError as a JSON object and writes it to the given HTTP response writer.

type RetryPolicy

type RetryPolicy struct {
	// IsRetryable determines whether a given response and error combination
	// should be retried.
	IsRetryable func(*http.Response, error) bool

	// MaxRetries is the maximum number of times a request will be retried.
	MaxRetries int

	// MinRetryDelay is the minimum duration to wait before retrying a request.
	MinRetryDelay time.Duration

	// MaxRetryDelay is the maximum duration to wait before retrying a request.
	MaxRetryDelay time.Duration
}

RetryPolicy defines a policy for retrying HTTP requests.

func NewRetryPolicy

func NewRetryPolicy() *RetryPolicy

NewRetryPolicy returns a new RetryPolicy with sane default values.

The default values are: - IsRetryable: Retries if the status code is 408, 429, 502, 503, or 504. - MaxRetries: 3. - MinRetryDelay: 1 second. - MaxRetryDelay: 30 seconds.

func (*RetryPolicy) Wait

func (p *RetryPolicy) Wait(ctx context.Context, attempt int) error

Wait calculates the time to wait before the next retry attempt and blocks until it is time to retry the request or the context is canceled. It incorporates an exponential backoff strategy with jitter to prevent the "thundering herd" problem.

If the context is canceled before the wait is over, Wait returns the context's error.

type RetryRoundTripper

type RetryRoundTripper struct {
	// Policy defines the retry behavior.
	Policy *RetryPolicy

	// Logger is used for structured logging. It should be provided if logging
	// of the retry process is required. If nil, logging is disabled.
	Logger *slog.Logger
	// contains filtered or unexported fields
}

RetryRoundTripper is an implementation of http.RoundTripper that retries requests.

func NewRetryRoundTripper

func NewRetryRoundTripper(policy *RetryPolicy, logger *slog.Logger) *RetryRoundTripper

NewRetryRoundTripper creates a new instance of RetryRoundTripper with the specified RetryPolicy and Logger.

If policy is nil, NewRetryRoundTripper will use NewRetryPolicy. If logger is nil, NewRetryRoundTripper will not log.

func (*RetryRoundTripper) RoundTrip

func (rt *RetryRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip executes an HTTP transaction with retry logic.

type SafeFetcher

type SafeFetcher struct {
	// Client is the underlying HTTP client. If nil, ErrNilHTTPClient is
	// returned.
	Client *http.Client

	// Logger is the logger to use when logging events. If nil, no events are
	// logged.
	Logger *slog.Logger

	// Header specifies additional headers to be included in each request.
	Header http.Header

	// TrustedDomain specifies domains from which fetching is permitted.
	TrustedDomain []string

	// MaxContentLength limits the size of the response body. Fetch operations
	// exceeding this limit are aborted to prevent potential resource exhaustion
	// attacks.
	MaxContentLength int64
}

SafeFetcher is an implementation of Fetcher that tries to provide secure-by-default fetching of remote resources.

func (*SafeFetcher) Fetch

func (sf *SafeFetcher) Fetch(ctx context.Context, uri string) ([]byte, error)

Fetch fetches the resource at the given URL and returns the response as a byte array.

Source Files

client.go errors.go fetch.go headers.go ip.go response.go retry.go roundtripper.go transport.go user_agent.go xhttp.go

Directories

PathSynopsis
xnet/xhttp/xhttputilPackage xhttputil provides utility functions for HTTP requests and responses.
xnet/xhttp/xmiddlewarePackage xmiddleware contains simple middleware functions.
Version
v0.13.1 (latest)
Published
Jan 29, 2025
Platform
linux/amd64
Imports
19 packages
Last checked
4 days ago

Tools for package owners.