httpr

package module
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Nov 27, 2025 License: MIT Imports: 16 Imported by: 1

README

a try-hard general purpose http client for golang. hipster docs here

Go Badge Go Report Card integrity

Table of Contents

Rationale

why does this library exist when all these already do? Why not just use the standard library?

I'm in an environment that requires myself and a team to integrate with several money movement vendors to move large amounts of money on a daily basis. These vendors vary from omega hipster startups to evilcorp banks. The APIs surfaced by these vendors vary significantly in many ways e.g. auth, response formats, error handling, etc.

After trying all of the libraries that provide full featured http clients, we weren't able to cover the spectrum of requirements we have. They all fell a bit short when it came to how interceptors work or how error response bodies are handled.

httpr is something that works for me. I'm sharing it in case it works for you too. It comes with a few features that I believe help increase the likelihood of me being a responsible adult: observability

Getting Started

Installation

go get github.com/mistermoe/httpr

Usage

the following is an example of a handful of features that httpr provides. This example sends a POST request to https://reqres.in/api/register with a request body and expects a response body of type RegisterResponse or RegisterErrorResponse in case of an error response.

package main

import (
  "context"
  "fmt"
  "log"
  "net/http"

  "github.com/mistermoe/httpr"
)

type RegisterRequest struct {
  Email    string `json:"email"`
  Password string `json:"password"`
}

type RegisterResponse struct {
  ID    int    `json:"id"`
  Token string `json:"token"`
}

type RegisterErrorResponse struct {
  Error string `json:"error"`
}

func main() {
  client := httpr.NewClient(
    httpr.BaseURL("https://reqres.in"),
    httpr.Inspect(),
  )

  reqBody := RegisterRequest{
    Email: "[email protected]",
    // Email:    "[email protected]", // uncomment for 400 response
    Password: "wowsosecret",
  }

  var respBody RegisterResponse
  var errBody RegisterErrorResponse

  resp, err := client.Post(
    context.Background(),
    "/api/register",
    httpr.RequestBodyJSON(reqBody),
    httpr.ResponseBodyJSON(&respBody, &errBody),
  )

  if err != nil {
    log.Fatalf("Request failed: %v", err)
  }

  if resp.StatusCode == http.StatusBadRequest {
    fmt.Printf("(%v): %v\n", resp.StatusCode, errBody.Error)
  } else {
    fmt.Printf("registration successful: %v\n", respBody.ID)
  }
}

[!TIP] httpr.Inspect() is an interceptor that logs the request and response. It's useful for debugging.

Output
Request:
POST /api/register HTTP/1.1
Host: reqres.in
User-Agent: Go-http-client/1.1
Content-Length: 55
Content-Type: application/json
Accept-Encoding: gzip

{"email":"[email protected]","password":"wowsosecret"}
Response:
HTTP/2.0 200 OK
Content-Length: 36
Access-Control-Allow-Origin: *
Cf-Cache-Status: DYNAMIC
Cf-Ray: 8d2d1d1dbef56bae-DFW
Content-Type: application/json; charset=utf-8
Date: Tue, 15 Oct 2024 04:37:25 GMT
Etag: W/"24-4iP0za1geN2he+ohu8F0FhCjLks"
Nel: {"report_to":"heroku-nel","max_age":3600,"success_fraction":0.005,"failure_fraction":0.05,"response_headers":["Via"]}
Report-To: {"group":"heroku-nel","max_age":3600,"endpoints":[{"url":"https://nel.heroku.com/reports?ts=1728967044&sid=c4c9725f-1ab0-44d8-820f-430df2718e11&s=da4UajPHCv9cP90lDWJTH0yPoeHNweUdOPgmJcavq8s%3D"}]}
Reporting-Endpoints: heroku-nel=https://nel.heroku.com/reports?ts=1728967044&sid=c4c9725f-1ab0-44d8-820f-430df2718e11&s=da4UajPHCv9cP90lDWJTH0yPoeHNweUdOPgmJcavq8s%3D
Server: cloudflare
Via: 1.1 vegur
X-Powered-By: Express

{"id":4,"token":"QpwL5tke4Pnpja7X4"}
registration successful: 4

Features

  • BaseURL configuration - set base url for all requests
  • Setting custom headers both globally and per request
  • Setting custom query params
  • Supplying strongly typed request bodies
  • Unmarshalling response bodies into strong types (for success and error responses)
  • Interceptor support for request/response modification and inspection
  • Built-in request/response inspector for debugging
  • Opt-in OLTP Instrumentation with metrics and traces for observability

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

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

func NewClient

func NewClient(options ...ClientOption) *Client

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, url string, options ...RequestOption) (*http.Response, error)

Delete sends a DELETE request to the specified URL.

func (*Client) Get

func (c *Client) Get(ctx context.Context, url string, options ...RequestOption) (*http.Response, error)

Get sends a GET request to the specified URL.

func (*Client) Patch added in v1.2.0

func (c *Client) Patch(ctx context.Context, url string, options ...RequestOption) (*http.Response, error)

Patch sends a PATCH request to the specified URL.

func (*Client) Post

func (c *Client) Post(ctx context.Context, url string, options ...RequestOption) (*http.Response, error)

Post sends a POST request to the specified URL.

func (*Client) Put

func (c *Client) Put(ctx context.Context, url string, options ...RequestOption) (*http.Response, error)

Put sends a PUT request to the specified URL.

func (*Client) SendRequest

func (c *Client) SendRequest(ctx context.Context, method string, path string, options ...RequestOption) (resp *http.Response, err error)

SendRequest sends a request to the specified URL with the specified method and options.

type ClientOption

type ClientOption interface {
	Client(*Client)
}

func BaseURL

func BaseURL(baseURL string) ClientOption

func HTTPClient

func HTTPClient(h http.Client) ClientOption

func Timeout

func Timeout(timeout time.Duration) ClientOption

type HandleFunc

type HandleFunc func(ctx context.Context, req *http.Request, next Interceptor) (*http.Response, error)

func Inspector

func Inspector() HandleFunc

Inspector is an interceptor that logs the request and response to stdout.

func (HandleFunc) Handle

func (f HandleFunc) Handle(ctx context.Context, req *http.Request, next Interceptor) (*http.Response, error)

type Interceptor

type Interceptor interface {
	Handle(ctx context.Context, req *http.Request, next Interceptor) (*http.Response, error)
}

func Chain

func Chain(interceptors ...Interceptor) Interceptor

type Observer

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

func NewObserver

func NewObserver(opts ...ObserverOption) (*Observer, error)

func (*Observer) Handle

func (o *Observer) Handle(ctx context.Context, req *http.Request, next Interceptor) (*http.Response, error)

type ObserverOption

type ObserverOption func(*Observer)

func WithMetricPrefix

func WithMetricPrefix(prefix string) ObserverOption

type Option

type Option interface {
	ClientOption
	RequestOption
}
func Header(key, value string) Option

Header creates a new Option for setting headers.

func Inspect

func Inspect() Option

func Intercept

func Intercept(i Interceptor) Option

func RequestBody

func RequestBody(contentType string, bodyFunc func() (io.Reader, error)) Option

func RequestBodyBytes

func RequestBodyBytes(contentType string, body []byte) Option

RequestBodyBytes sets the content type to application/octet-stream.

func RequestBodyForm

func RequestBodyForm(data url.Values) Option

RequestBodyForm sets the content type to application/x-www-form-urlencoded.

func RequestBodyJSON

func RequestBodyJSON(body any) Option

RequestBodyJSON json marshals whatever is passed in and sets the content type to application/json.

func RequestBodyStream

func RequestBodyStream(contentType string, body io.Reader) Option

RequestBodyStream sets the content type to the provided value. good for when you have a stream of data.

func RequestBodyString

func RequestBodyString(body string) Option

RequestBodyString sets the content type to text/plain.

func ResponseBody

func ResponseBody(handler responseBodyHandler) Option

func ResponseBodyBytes

func ResponseBodyBytes(dest *[]byte) Option

func ResponseBodyJSON

func ResponseBodyJSON(successBody any, errBody any) Option

func ResponseBodyString

func ResponseBodyString(dest *string) Option

type RequestOption

type RequestOption interface {
	Request(*requestOptions)
}

func QueryParam

func QueryParam(key, value string) RequestOption

Directories

Path Synopsis
examples
basic command
observability command

Jump to

Keyboard shortcuts

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