bhttp

package module
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Jan 17, 2026 License: MIT Imports: 8 Imported by: 0

README

BHTTP - Minimal HTTP Client Helper for Go (Automatic JSON Unwrapping, Simple Retries, Rate Limiting)

License Go Report Card

BHTTP is a lightweight Go library that wraps net/http. This library is able to validate expected status codes, retry on specific status codes, optionally rate limit requests, and decode JSON responses into a destination struct.

Installation

To install BHTTP, run:

go get github.com/bearaujus/bhttp

Import

import "github.com/bearaujus/bhttp"

Features

  • Validate response status codes (defaults to 200 OK).
  • Retry on specific response status codes (e.g., 429, 500, 502, 503, 504).
  • Optional rate limiting using golang.org/x/time/rate.
  • Decode JSON responses into a struct (DoAndUnwrap).
  • Helpful error messages including response body (pretty-printed if JSON).

Usage

1. Simple "send + unwrap"
type HttpBinGetResponse struct {
    URL     string            `json:"url"`
    Headers map[string]string `json:"headers"`
}

func main() {
    req, _ := http.NewRequest(http.MethodGet, "https://httpbin.org/get", nil)

    // BHTTP will automatically unmarshal the JSON response body into HttpBinGetResponse struct.
    resp, err := bhttp.DoAndUnwrap[HttpBinGetResponse](req)
    if err != nil {
        panic(err)
    }

    fmt.Printf("[%T] %+v\n", resp, resp)
}
[*main.HttpBinGetResponse] &{URL:https://httpbin.org/get Headers:map[Accept-Encoding:gzip Host:httpbin.org User-Agent:Go-http-client/2.0]}
2. Use Options to validate expected status codes.
func main() {
    req, _ := http.NewRequest(http.MethodGet, "https://httpbin.org/get", nil)

    opts := &bhttp.Options{
        // Only treat these status codes as success (defaults to 200 if omitted). 
        ExpectedStatusCodes: []int{http.StatusOK},
    }
    
    var out HttpBinGetResponse
    if err := bhttp.New().DoAndUnwrapWithOptions(req, &out, opts); err != nil {
        panic(err)
    }

    fmt.Printf("[%T] %+v\n", resp, resp)
}
[main.HttpBinGetResponse] {URL:https://httpbin.org/get Headers:map[Accept-Encoding:gzip Host:httpbin.org User-Agent:Go-http-client/2.0]}
3. Use Options + Retry to retry on specific status codes (e.g., 429 / 5xx).
func main() {
    // Using httpstat.us to simulate a retryable status.
    req, _ := http.NewRequest(http.MethodGet, "https://httpstat.us/503", nil)
    
    opts := &bhttp.Options{
        ExpectedStatusCodes: []int{http.StatusOK},
        Retry: &bhttp.RetryConfig{
            // Number of retries AFTER the first attempt (total tries = 1 + Attempts).
            Attempts: 2,
            
            // Retry only when the response status code matches one of these.
            RetryStatusCodes: []int{http.StatusTooManyRequests, http.StatusServiceUnavailable},
        },
    }
    
    // Note: On the final attempt, BHTTP will stop treating these as retryable so you get
    // a real error with the response body if it still fails.
    if err := bhttp.DoWithOptions(req, opts); err != nil {
        panic(err)
    }
}
panic: retries exhausted after 2 attempt(s): Get "https://httpstat.us/503": EOF

TODO

  • Add support for back-off retry, jitter & similar kind of components for retry mechanism

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Do

func Do(req *http.Request) error

Do execute an HTTP request using the package default client (http.DefaultClient) and default options.

Defaults:

  • ExpectedStatusCodes: []int{http.StatusOK}
  • Retry: disabled (no retries)
  • RateLimiter: none

Returns an error if the request fails or the response status code is not expected.

func DoAndUnwrap

func DoAndUnwrap[T any](req *http.Request) (T, error)

DoAndUnwrap executes an HTTP request using the package default client (http.DefaultClient) and default options, then unmarshal the JSON response body into a value of type T.

Defaults:

  • ExpectedStatusCodes: []int{http.StatusOK}
  • Retry: disabled (no retries)
  • RateLimiter: none

Returns a pointer to the decoded value, or an error if the request fails, the response status code is not expected, or the response body cannot be unmarshalled into T.

func DoAndUnwrapWithOptions

func DoAndUnwrapWithOptions[T any](req *http.Request, opts *Options) (T, error)

DoAndUnwrapWithOptions executes an HTTP request using the package default client (http.DefaultClient) and the provided options, then unmarshal the JSON response body into a value of type T.

If opts is nil, default options are used. See Options and RetryConfig for details on status code validation, retry behavior, and rate limiting.

Returns a pointer to the decoded value, or an error if the request fails, retries are exhausted, the final response status code is not expected, or the response body cannot be unmarshalled into T.

func DoWithOptions

func DoWithOptions(req *http.Request, opts *Options) error

DoWithOptions executes an HTTP request using the package default client (http.DefaultClient) and the provided options.

If opts is nil, default options are used (same as Do). See Options and RetryConfig for details on status code validation, retry behavior, and rate limiting.

Returns an error if the request fails, retries are exhausted, or the final response status code is not expected.

Types

type BHTTP

type BHTTP interface {
	// Client returns the underlying *http.Client used by this instance.
	// Callers may use it to customize transport/timeouts or to perform advanced requests directly.
	Client() *http.Client

	// Do execute the HTTP request using default behavior.
	//
	// Defaults:
	//   - ExpectedStatusCodes: []int{http.StatusOK}
	//   - Retry: disabled (no retries)
	//   - RateLimiter: none
	//
	// Returns an error if the request fails or the response status code is not expected.
	Do(req *http.Request) error

	// DoWithOptions executes the HTTP request with the provided options.
	//
	// It validates the response status code against opts.ExpectedStatusCodes (default 200).
	// If opts.Retry is configured, it will retry when the response status code matches
	// opts.Retry.RetryStatusCodes, up to 1+opts.Retry.Attempts total tries.
	//
	// Returns an error if the request fails, retries are exhausted, or the final response status
	// code is not expected.
	DoWithOptions(req *http.Request, opts *Options) error

	// DoAndUnwrap executes the request using default behavior and unmarshal the JSON response body
	// into dest.
	//
	// Requirements:
	//   - dest must be a non-nil pointer.
	//
	// Defaults:
	//   - ExpectedStatusCodes: []int{http.StatusOK}
	//   - Retry: disabled (no retries)
	//   - RateLimiter: none
	//
	// Returns an error if the request fails, the response status code is not expected, or the
	// response body cannot be unmarshaled into dest.
	DoAndUnwrap(req *http.Request, dest any) error

	// DoAndUnwrapWithOptions executes the request with the provided options and unmarshal the JSON
	// response body into dest.
	//
	// Requirements:
	//   - dest must be a non-nil pointer.
	//
	// Behavior:
	//   - status code validation uses opts.ExpectedStatusCodes (default 200)
	//   - retry behavior uses opts.Retry.RetryStatusCodes and opts.Retry.Attempts (if provided)
	//   - rate limiting uses opts.RateLimiter (if provided)
	//
	// Returns an error if the request fails, retries are exhausted, the final response status
	// code is not expected, or the response body cannot be unmarshalled into dest.
	DoAndUnwrapWithOptions(req *http.Request, dest any, opts *Options) error
}

BHTTP is a small HTTP helper interface that wraps an underlying *http.Client and provides convenience methods for:

  • validating expected response status codes,
  • retrying based on response status codes,
  • optionally unmarshaling JSON responses into a destination struct.

Notes:

  • Retry is currently status-code based only (http.Client.Do errors are returned immediately).
  • For the final attempt, the implementation may disable RetryStatusCodes so that a previously "retryable" status code becomes a returned error (useful to surface the response body).
  • If you retry requests with a non-empty body (POST/PUT), ensure the request body is replayable (e.g. req.GetBody is set, or you rebuild the request per attempt).

func New

func New() BHTTP

New constructs a BHTTP instance using http.DefaultClient.

Use NewWithClient if you need a custom *http.Client (timeouts, transport, proxy, etc).

func NewWithClient

func NewWithClient(client *http.Client) BHTTP

NewWithClient constructs a BHTTP instance using the provided *http.Client.

If client is nil, http.DefaultClient is used.

type Options

type Options struct {
	// ExpectedStatusCodes defines which HTTP status codes are considered successful.
	// If empty/nil, defaults to []int{http.StatusOK}.
	ExpectedStatusCodes []int

	// Retry configures retry behavior based on response status codes.
	// If nil, it is treated as &RetryConfig{} (no retries by default).
	Retry *RetryConfig

	// RateLimiter, if set, will wait before EACH attempt (including retries) using req.Context().
	// This is useful to cap outgoing QPS across calls.
	// If nil, no rate limiting is applied.
	RateLimiter *rate.Limiter
}

type RetryConfig

type RetryConfig struct {
	// Attempts is the number of retries AFTER the first attempt.
	// Total tries = 1 + Attempts.
	//
	// Example:
	//   Attempts = 0 => 1 try total (no retries)
	//   Attempts = 2 => up to 3 tries total
	Attempts int

	// RetryStatusCodes lists HTTP status codes that should trigger a retry.
	// Only response-status-based retries are supported by your current code
	// (network errors are returned immediately and are not retried).
	//
	// Example common retry codes: 429, 500, 502, 503, 504.
	RetryStatusCodes []int
}

Jump to

Keyboard shortcuts

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