httpclient

package
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Jun 13, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package httpclient provides a generic, type-safe HTTP client for JSON REST APIs.

The client supports optional OAuth2 bearer-token injection, automatic request-ID propagation, custom default headers, and response size limiting. It is safe for concurrent use.

Basic usage:

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

client := httpclient.New("https://api.example.com",
    httpclient.WithTokenProvider(authTokenClient),
)

var user User
if err := client.Get(ctx, "/users/123", &user); err != nil {
    return err
}

All methods (Get, Post, Put, Patch, Delete) accept a context, path, optional request body, and response pointer. Request IDs are automatically propagated from the context if set via middleware.ContextKeyRequestID.

When a token provider is configured, the token is sent only in the Authorization header. For service-to-service calls protected by middleware.InternalAuth, add the WithInternalAuthHeader option to also send X-Internal-Authorization — never enable it on clients that talk to external APIs.

Index

Examples

Constants

View Source
const (
	DefaultTimeout               = 30 * time.Second
	HeaderContentType            = "Content-Type"
	HeaderXRequestID             = "X-Request-ID"
	HeaderAuthorization          = "Authorization"
	HeaderXInternalAuthorization = "X-Internal-Authorization"
	ContentTypeJSON              = "application/json"
	ContentTypeFormURLEncoded    = "application/x-www-form-urlencoded"
)

Variables

View Source
var (
	ErrRequestFailed    = errors.New("httpclient: request failed")
	ErrServiceError     = errors.New("httpclient: service error")
	ErrDecodeResponse   = errors.New("httpclient: failed to decode response")
	ErrCreateRequest    = errors.New("httpclient: failed to create request")
	ErrEncodeBody       = errors.New("httpclient: failed to encode request body")
	ErrAuthFailed       = errors.New("httpclient: authentication failed")
	ErrResponseTooLarge = errors.New("httpclient: response body too large")
)

Functions

func DeleteJSON

func DeleteJSON[T any](ctx context.Context, c *Client, path string, opts ...RequestOption) (T, error)

func DoJSON

func DoJSON[T any](ctx context.Context, c *Client, method, path string, body any, opts ...RequestOption) (T, error)

func GetJSON

func GetJSON[T any](ctx context.Context, c *Client, path string, opts ...RequestOption) (T, error)

func PatchJSON

func PatchJSON[T any](ctx context.Context, c *Client, path string, body any, opts ...RequestOption) (T, error)

func PostJSON

func PostJSON[T any](ctx context.Context, c *Client, path string, body any, opts ...RequestOption) (T, error)

func PutJSON

func PutJSON[T any](ctx context.Context, c *Client, path string, body any, opts ...RequestOption) (T, error)

Types

type AuthConfig

type AuthConfig struct {
	ClientID     string
	ClientSecret string
	BaseURL      string
	Realm        string
	ExpiryBuffer time.Duration
}

type Client

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

func New

func New(baseURL string, opts ...Option) *Client
Example

ExampleNew shows a typical client setup. (Not run: requires a live API.)

package main

import (
	"context"
	"fmt"

	"github.com/andyle182810/gframework/httpclient"
)

func main() {
	type User struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}

	client := httpclient.New(
		"https://api.example.com",
		httpclient.WithMaxResponseSize(1<<20),
	)

	var user User
	if err := client.Get(context.Background(), "/users/123", &user); err != nil {
		fmt.Println("request failed:", err)

		return
	}

	fmt.Println(user.Name)
}

func (*Client) BaseURL

func (c *Client) BaseURL() string

func (*Client) Delete

func (c *Client) Delete(
	ctx context.Context,
	path string,
	response any,
	opts ...RequestOption,
) error

func (*Client) Do

func (c *Client) Do(
	ctx context.Context,
	method string,
	path string,
	body any,
	response any,
	opts ...RequestOption,
) error

func (*Client) Get

func (c *Client) Get(
	ctx context.Context,
	path string,
	response any,
	opts ...RequestOption,
) error

func (*Client) Head

func (c *Client) Head(ctx context.Context, path string, opts ...RequestOption) (*Response, error)

func (*Client) Options

func (c *Client) Options(ctx context.Context, path string, opts ...RequestOption) (*Response, error)

func (*Client) Patch

func (c *Client) Patch(
	ctx context.Context,
	path string,
	body any,
	response any,
	opts ...RequestOption,
) error

func (*Client) Post

func (c *Client) Post(
	ctx context.Context,
	path string,
	body any,
	response any,
	opts ...RequestOption,
) error

func (*Client) Put

func (c *Client) Put(
	ctx context.Context,
	path string,
	body any,
	response any,
	opts ...RequestOption,
) error

type Doer

type Doer interface {
	Do(req *http.Request) (*http.Response, error)
}

type ErrorResponse

type ErrorResponse struct {
	Message  string `json:"message"`
	Internal string `json:"internal"`
}

type Option

type Option func(*Client)

func WithAuth

func WithAuth(cfg AuthConfig) Option

func WithDefaultHeaders

func WithDefaultHeaders(headers map[string]string) Option

func WithHTTPClient

func WithHTTPClient(httpClient *http.Client) Option

func WithInternalAuthHeader added in v1.4.0

func WithInternalAuthHeader() Option
Example

ExampleWithInternalAuthHeader shows a service-to-service client that authenticates against endpoints protected by middleware.InternalAuth. (Not run: requires Keycloak and a live service.)

package main

import (
	"context"

	"github.com/andyle182810/gframework/httpclient"
)

func main() {
	client := httpclient.New(
		"https://internal-billing.svc.local",
		httpclient.WithAuth(httpclient.AuthConfig{ //nolint:exhaustruct
			BaseURL:      "https://auth.example.com",
			Realm:        "my-realm",
			ClientID:     "report-service",
			ClientSecret: "service-secret",
		}),
		// Sends the token in X-Internal-Authorization as well — only safe for
		// clients that exclusively call internal services.
		httpclient.WithInternalAuthHeader(),
	)

	_ = client.Post(context.Background(), "/internal/sync", nil, nil)
}

func WithMaxResponseSize

func WithMaxResponseSize(size int64) Option

func WithRequestIDKey

func WithRequestIDKey(key any) Option

func WithTimeout

func WithTimeout(timeout time.Duration) Option

func WithTokenProvider

func WithTokenProvider(provider TokenProvider) Option

type Registry

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

func NewRegistry

func NewRegistry(defaultOpts ...Option) *Registry

func (*Registry) Client

func (r *Registry) Client(name string) *Client

func (*Registry) Count

func (r *Registry) Count() int

func (*Registry) GetClient

func (r *Registry) GetClient(name string) (*Client, bool)

func (*Registry) Has

func (r *Registry) Has(name string) bool

func (*Registry) MustClient

func (r *Registry) MustClient(name string) *Client

func (*Registry) Names

func (r *Registry) Names() []string

func (*Registry) Register

func (r *Registry) Register(name, baseURL string, opts ...Option) *Registry

func (*Registry) Unregister

func (r *Registry) Unregister(name string) bool

type RequestOption

type RequestOption func(*requestConfig)

func WithQuery

func WithQuery(key, value string) RequestOption

func WithQueryParams

func WithQueryParams(params map[string]string) RequestOption

func WithRequestHeader

func WithRequestHeader(key, value string) RequestOption

func WithRequestID

func WithRequestID(requestID string) RequestOption

func WithRequestTimeout

func WithRequestTimeout(timeout time.Duration) RequestOption

type Response

type Response struct {
	StatusCode int
	Headers    map[string]string
	RequestID  string
}

type ServiceError

type ServiceError struct {
	StatusCode int
	Message    string
	Internal   string
	RequestID  string
}

func IsServiceError

func IsServiceError(err error) (*ServiceError, bool)

func NewServiceError

func NewServiceError(statusCode int, message, internal, requestID string) *ServiceError

func (*ServiceError) Error

func (e *ServiceError) Error() string

func (*ServiceError) Is

func (e *ServiceError) Is(target error) bool

func (*ServiceError) Unwrap

func (e *ServiceError) Unwrap() error

type TokenProvider

type TokenProvider interface {
	GetToken(ctx context.Context) (string, error)
	InvalidateToken()
}

Jump to

Keyboard shortcuts

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