httputils

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2025 License: MIT Imports: 19 Imported by: 0

README

http-utils

Helper functions for Go HTTP clients.

Features

  • Generic HTTP Client Support: Works with both standard net/http and bogdanfinn's http client
  • Intelligent Retry Logic: Automatically retries on network errors while avoiding retries on configuration issues
  • Flexible Logging: Built-in logging support with multiple logger implementations
  • Context Support: Full context cancellation and timeout support
  • Standalone Functions: Use retry functionality without creating client instances
  • Response Decompression: Automatic decompression of gzip, deflate, brotli, and zstd compressed responses

Quick Start

Examples
Using the standalone function
package main

import (
    "context"
    "net/http"
    "github.com/Matthew17-21/http-utils"
)

func main() {
    // Create a standard HTTP client
    httpClient := &http.Client{}
    
    // Create a request
    req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
    ctx := context.Background()
    
    // Use the standalone function with retry capability
    resp, err := httputils.DoWithRetry(ctx, httpClient, req, 3, nil)
    if err != nil {
        // Handle error
    }
    defer resp.Body.Close()
    
    // Process response
}
Using the RetryableHTTPClient
package main

import (
    "context"
    "net/http"
    "github.com/Matthew17-21/http-utils"
)

func main() {
    // Create a standard HTTP client
    httpClient := &http.Client{}
    
    // Create a retryable client with options
    retryableClient := httputils.NewRetryableHTTPClient(
		client,
		httputils.WithRetries[*http.Client](3),
		httputils.WithLogger[*http.Client](httputils.NewLogger()),
	)
    
    // Create a request
    req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
    ctx := context.Background()
    
    // Make request with automatic retries
    resp, err := retryableClient.MakeRequest(ctx, req)
    if err != nil {
        // Handle error
    }
    defer resp.Body.Close()
    
    // Process response
}
With Custom Logger
logger := httputils.NewLogger()
resp, err := httputils.DoWithRetry(ctx, httpClient, req, 3, logger)
With Context Timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

resp, err := httputils.DoWithRetry(ctx, httpClient, req, 3, nil)
With TLS HTTP Client
import tlsHttp "github.com/bogdanfinn/fhttp"

tlsClient := &tlsHttp.Client{}
tlsReq, _ := tlsHttp.NewRequest("GET", "https://api.example.com/data", nil)

resp, err := httputils.DoWithRetry(ctx, tlsClient, tlsReq, 3, nil)
Decompressing Different Content Types

The library supports automatic decompression of various compression formats. Here are examples for each supported format:

Gzip Decompression
// Request gzip-compressed content
req, _ := http.NewRequest("GET", "https://api.example.com/gzipped-data", nil)
req.Header.Set("Accept-Encoding", "gzip")

resp, err := httputils.DoWithRetry(ctx, httpClient, req, 3, nil)
if err != nil {
    return err
}
defer resp.Body.Close()

// Decompress the response
decompressed, err := httputils.DecompressResponse(resp.Header, resp.Body)
if err != nil {
    return err
}
fmt.Println("Decompressed content:", string(decompressed))
Brotli Decompression
// Request brotli-compressed content
req, _ := http.NewRequest("GET", "https://httpbin.org/brotli", nil)
req.Header.Set("Accept-Encoding", "br")

resp, err := httputils.DoWithRetry(ctx, httpClient, req, 3, nil)
if err != nil {
    return err
}
defer resp.Body.Close()

// Decompress the response
decompressed, err := httputils.DecompressResponse(resp.Header, resp.Body)
if err != nil {
    return err
}
fmt.Println("Decompressed content:", string(decompressed))
Zstd Decompression
// Request zstd-compressed content
req, _ := http.NewRequest("GET", "https://api.example.com/zstd-data", nil)
req.Header.Set("Accept-Encoding", "zstd")

resp, err := httputils.DoWithRetry(ctx, httpClient, req, 3, nil)
if err != nil {
    return err
}
defer resp.Body.Close()

// Decompress the response
decompressed, err := httputils.DecompressResponse(resp.Header, resp.Body)
if err != nil {
    return err
}
fmt.Println("Decompressed content:", string(decompressed))
Deflate Decompression
// Request deflate-compressed content
req, _ := http.NewRequest("GET", "https://api.example.com/deflate-data", nil)
req.Header.Set("Accept-Encoding", "deflate")

resp, err := httputils.DoWithRetry(ctx, httpClient, req, 3, nil)
if err != nil {
    return err
}
defer resp.Body.Close()

// Decompress the response
decompressed, err := httputils.DecompressResponse(resp.Header, resp.Body)
if err != nil {
    return err
}
fmt.Println("Decompressed content:", string(decompressed))
Automatic Decompression with Multiple Formats
// Request any supported compression format
req, _ := http.NewRequest("GET", "https://api.example.com/compressed-data", nil)
req.Header.Set("Accept-Encoding", "br, gzip, deflate, zstd")

resp, err := httputils.DoWithRetry(ctx, httpClient, req, 3, nil)
if err != nil {
    return err
}
defer resp.Body.Close()

// The library automatically detects and decompresses based on Content-Encoding header
decompressed, err := httputils.DecompressResponse(resp.Header, resp.Body)
if err != nil {
    return err
}
fmt.Println("Decompressed content:", string(decompressed))

Note: When using bogdanfinn/fhttp client, some decompression (such as brotli) is handled automatically by the client itself, so you don't need to manually decompress responses.

Installation

go get github.com/Matthew17-21/http-utils

Why?

Sometimes I use different HTTP clients and wanted to make it easier to work with them consistently. This library provides a unified interface for retry logic, logging, and response handling across different HTTP client implementations.

Acknowledgments

The decompression functionality in this library includes code adapted from the Hyper Solutions Go SDK.

Documentation

Overview

Package httputils provides utility functions for HTTP operations

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BrotliDecompress

func BrotliDecompress(body io.ReadCloser) ([]byte, error)

BrotliDecompress decompresses Brotli-compressed data.

func DecompressResponse

func DecompressResponse(headers map[string][]string, body io.ReadCloser) ([]byte, error)

DecompressResponse decompresses the response body based on the Content-Encoding header. It supports multiple compression formats: gzip, deflate, brotli (br), and zstd. If the Content-Encoding is unknown or empty, it returns the body as-is.

func DeflateDecompress

func DeflateDecompress(body io.ReadCloser) ([]byte, error)

DeflateDecompress decompresses deflate-compressed data with fallback support for different formats.

func DoWithRetry

func DoWithRetry[T Doer[Rq, Rp], Rq Request, Rp Response](
	ctx context.Context,
	client T,
	req Rq,
	numRetries int,
	logger Logger,
) (Rp, error)

DoWithRetry is a standalone function that provides retry functionality for HTTP requests without requiring the creation of a RetryableHTTPClient.

It will retry the request up to numRetries times in case of network-related errors. The function uses intelligent error classification to determine which errors are retryable:

  • BadURL errors are not retried (immediate failure)
  • Proxy-related errors are not retried (immediate failure)
  • Network timeouts, connection errors, and other transient issues are retried

Parameters:

  • ctx: Context for cancellation and timeout control
  • client: The HTTP client that implements the Doer interface
  • req: The HTTP request to send
  • numRetries: Number of retry attempts (0 means no retries)
  • logger: Logger instance for debugging and error reporting (optional, uses silent logger if nil)

Returns:

  • Rp: The HTTP response if successful
  • error: An error if the request fails after all retry attempts

func GzipDecompress

func GzipDecompress(body io.ReadCloser) ([]byte, error)

GzipDecompress decompresses gzip-compressed data using a pooled gzip.Reader for better performance. The function automatically closes the input body and manages the lifecycle of the pooled reader.

func IsClientError

func IsClientError(err error) bool

Returns if the error was a http client related error

func IsIn100s

func IsIn100s(statusCode int) bool

IsIn100s returns true if the given status code is in the 100-199 range (Informational).

func IsIn200s

func IsIn200s(statusCode int) bool

IsIn200s returns true if the given status code is in the 200-299 range (Success).

func IsIn300s

func IsIn300s(statusCode int) bool

IsIn300s returns true if the given status code is in the 300-399 range (Redirection).

func IsIn400s

func IsIn400s(statusCode int) bool

IsIn400s returns true if the given status code is in the 400-499 range (Client Error).

func IsIn500s

func IsIn500s(statusCode int) bool

IsIn500s returns true if the given status code is in the 500-599 range (Server Error).

func IsProxyError

func IsProxyError(err error) bool

Returns if the error was a proxy related error.

func ParseError

func ParseError(err error) error

Parses an error that was returned from the `.Do()` method and returns a ProxyErr or ClientErr

func ZstdDecompress

func ZstdDecompress(body io.ReadCloser) ([]byte, error)

ZstdDecompress decompresses zstd-compressed data using a pooled zstd.Decoder for better performance. The function automatically closes the input body and manages the lifecycle of the pooled decoder.

Types

type ClientErr

type ClientErr struct {
	TimedOut bool // If the connection timedout
}

Will be used to represent an HTTP client related error

func (ClientErr) Error

func (c ClientErr) Error() string

type ClientOption

type ClientOption[T Doer[Rq, Rp], Rq Request, Rp Response] func(c *RetryableHTTPClient[T, Rq, Rp])

ClientOption is a function type that configures a RetryableHTTPClient. This follows the functional options pattern for flexible client configuration.

func WithLogger

func WithLogger[T Doer[Rq, Rp], Rq Request, Rp Response](logger Logger) ClientOption[T, Rq, Rp]

WithLogger creates a ClientOption that sets the logger for the client. The logger is used for debugging information and error reporting during request attempts.

func WithRetries

func WithRetries[T Doer[Rq, Rp], Rq Request, Rp Response](numRetries int) ClientOption[T, Rq, Rp]

WithRetries creates a ClientOption that sets the number of retry attempts. The client will retry failed requests up to this many times before giving up.

type Doer

type Doer[Rq Request, Rp Response] interface {
	Do(Rq) (Rp, error)
}

Doer is a generic interface that defines the signature for HTTP clients. It requires a Do method that takes a request and returns a response with an error. This abstraction allows the RetryableHTTPClient to work with any HTTP client implementation.

type Logger

type Logger interface {
	Debug(format string, args ...any)
	Info(format string, args ...any)
	Warn(format string, args ...any)
	Error(format string, args ...any)
}

func NewDebugLogger

func NewDebugLogger(logger Logger) Logger

func NewLogger

func NewLogger() Logger

func NewSilentLogger

func NewSilentLogger() Logger

type NetworkError

type NetworkError string

NetworkError is a custom type to categorize network-related errors.

const (
	// DNSNameNotFound indicates the requested name does not contain any
	// records of the requested type (data not found), or the name
	// itself was not found (NXDOMAIN)
	DNSNameNotFound NetworkError = "DNS name not found"

	// ConnectionRefused indicates that the connection was refused by the target machine
	ConnectionRefused NetworkError = "connection refused"

	// ConnectionTimedOut indicates that the connection has timedout
	ConnectionTimedOut NetworkError = "connection timed out"

	// HostUnreachable indicates that the target host was unreachable
	HostUnreachable NetworkError = "host unreachable"

	// NetworkUnreachable indicates that the network was unreachable
	NetworkUnreachable NetworkError = "network unreachable"

	// EOF indicates that connection was most likely closed before or while the headers are read.
	EOF NetworkError = "EOF error"

	// ProxyAuthRequired indicates that the request did not succeed because it
	// lacks valid authentication credentials for the proxy server that sits
	// between the client and the server with access to the requested resource.
	ProxyAuthRequired NetworkError = "proxy error - proxy authentication required"

	// ProxyRelayOffline indicates that the proxy responded with non 200 code:
	// 502 Proxy Error (The selected relay is offline or busy processing other threads)
	ProxyRelayOffline NetworkError = "proxy error - relay is offline or busy processing other threads"

	// BadURL indicates a URL error. This can be for various reasons such as a
	// missing scheme, blank URI, etc.
	BadURL NetworkError = "bad URL"

	// GenericError indicates a generic network error. This can be for various
	// reasons, most commonly that the error has not been added for validation.
	GenericError NetworkError = "network error"
)

func ClassifyNetworkError

func ClassifyNetworkError(err error) NetworkError

ClassifyNetworkError attempts to classify the provided error into one of the predefined NetworkError types.

type ProxyErr

type ProxyErr struct {
	InvalidAuth bool // Invalid username or password
	InvalidHost bool // Invalid proxy host name
	InvalidPort bool // Invalid proxy port
}

Will be used to represent an proxy related error

func (ProxyErr) Error

func (p ProxyErr) Error() string

type Request

type Request interface {
	*http.Request | *tlsHttp.Request
}

Request is a constraint that allows either standard http.Request or bogdanfinn's Request. This enables the RetryableHTTPClient to work with both standard and TLS HTTP clients.

type RequestErr

type RequestErr struct {
	Message string
}

func (RequestErr) Error

func (r RequestErr) Error() string

type Response

type Response interface {
	*http.Response | *tlsHttp.Response
}

Response is a constraint that allows either standard http.Response or bogdanfinn's Response. This enables the RetryableHTTPClient to work with both standard and TLS HTTP responses.

type ResponseWithHeader

type ResponseWithHeader interface {
	Header() map[string][]string
}

ResponseWithHeader is a constraint that ensures the response type has a Header and Body field. This allows the DecompressResponse function to work with any HTTP response type that provides access to headers (e.g., http.Response, custom response types).

type RetryableHTTPClient

type RetryableHTTPClient[T Doer[Rq, Rp], Rq Request, Rp Response] struct {
	// contains filtered or unexported fields
}

RetryableHTTPClient is a generic wrapper around HTTP clients that provides retry functionality and logging capabilities.

func NewRetryableHTTPClient

func NewRetryableHTTPClient[T Doer[Rq, Rp], Rq Request, Rp Response](
	client T,
	opts ...ClientOption[T, Rq, Rp],
) *RetryableHTTPClient[T, Rq, Rp]

NewRetryableHTTPClient creates a new RetryableHTTPClient with the provided client and options.

func (*RetryableHTTPClient[T, Rq, Rp]) Do

func (c *RetryableHTTPClient[T, Rq, Rp]) Do(ctx context.Context, req Rq) (Rp, error)

Do is a wrapper around DoWithRetry that provides a simpler interface for making requests. It takes a context and a request, and returns a response and an error. It will retry the request up to numRetries times in case of network-related errors. The function uses intelligent error classification to determine which errors are retryable:

  • BadURL errors are not retried (immediate failure)
  • Proxy-related errors are not retried (immediate failure)

func (*RetryableHTTPClient[T, Rq, Rp]) DoWithRetry

func (c *RetryableHTTPClient[T, Rq, Rp]) DoWithRetry(ctx context.Context, req Rq) (Rp, error)

DoWithRetry attempts to send an HTTP request using the provided HTTP client.

It will retry the request up to numRetries times in case of network-related errors. The function uses intelligent error classification to determine which errors are retryable:

  • BadURL errors are not retried (immediate failure)
  • Proxy-related errors are not retried (immediate failure)
  • Network timeouts, connection errors, and other transient issues are retried

Parameters:

  • ctx: Context for cancellation and timeout control
  • req: The HTTP request to send

Returns:

  • Rp: The HTTP response if successful
  • error: An error if the request fails after all retry attempts

Directories

Path Synopsis
examples
fhttp-client command
net-http-client command

Jump to

Keyboard shortcuts

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