A comprehensive Go package for building and executing HTTP requests with advanced features.
🚀 Zero Dependencies - Built entirely using the Go standard library for maximum reliability, security, and minimal maintenance overhead. See go.mod
- 🔨 Fluent Request Builder - Chainable API for constructing HTTP requests
- 🔄 Automatic Retry Logic - Configurable retry strategies with exponential backoff
- 🎯 Type-Safe Generic Client - Go generics for type-safe HTTP responses
- ✅ Input Validation - Comprehensive validation with error accumulation
- 🔐 Authentication Support - Built-in Basic and Bearer token authentication
- 🌐 Proxy Support - HTTP/HTTPS proxy configuration with authentication (supports corporate proxies, authenticated proxies, and custom ports)
- 📝 Optional Logging - slog integration for observability (disabled by default)
- 📦 Zero External Dependencies - Only Go standard library, no third-party packages
Requirements: Go 1.22 or higher
go get github.com/slashdevops/httpxTo upgrade to the latest version, run:
go get -u github.com/slashdevops/httpximport "github.com/slashdevops/httpx"
// Build and execute a simple GET request
req, err := httpx.NewRequestBuilder("https://api.example.com").
WithMethodGET().
WithPath("/users/123").
WithHeader("Accept", "application/json").
Build()
if err != nil {
log.Fatal(err)
}
// Use with standard http.Client
resp, err := http.DefaultClient.Do(req)type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// Create a typed client with configuration
client := httpx.NewGenericClient[User](
httpx.WithTimeout[User](10 * time.Second),
httpx.WithMaxRetries[User](3),
httpx.WithRetryStrategy[User](httpx.ExponentialBackoffStrategy),
)
// Execute typed request
response, err := client.Get("https://api.example.com/users/123")
if err != nil {
log.Fatal(err)
}
// response.Data is strongly typed as User
fmt.Printf("User: %s (%s)\n", response.Data.Name, response.Data.Email)// Create client with retry logic
retryClient := httpx.NewClientBuilder().
WithMaxRetries(3).
WithRetryStrategy(httpx.ExponentialBackoffStrategy).
WithRetryBaseDelay(500 * time.Millisecond).
Build()
// Use with generic client
client := httpx.NewGenericClient[User](
httpx.WithHTTPClient[User](retryClient),
httpx.)
response, err := client.Get("/users/123")The RequestBuilder provides a fluent, chainable API for constructing HTTP requests with comprehensive validation.
- ✅ HTTP methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, TRACE, CONNECT
- ✅ Convenience methods for all standard HTTP methods (WithMethodGET, WithMethodPOST, WithMethodPUT, WithMethodDELETE, WithMethodPATCH, WithMethodHEAD, WithMethodOPTIONS, WithMethodTRACE, WithMethodCONNECT)
- ✅ Query parameters with automatic URL encoding
- ✅ Custom headers with validation
- ✅ Authentication (Basic Auth, Bearer Token)
- ✅ Multiple body formats (JSON, string, bytes, io.Reader)
- ✅ Context support for timeouts and cancellation
- ✅ Input validation with error accumulation
- ✅ Comprehensive error messages
req, err := httpx.NewRequestBuilder("https://api.example.com").
WithMethodPOST().
WithPath("/users").
WithQueryParam("notify", "true").
WithHeader("Content-Type", "application/json").
WithHeader("X-Request-ID", "unique-id-123").
WithBearerAuth("your-token-here").
WithJSONBody(map[string]string{
"name": "John Doe",
"email": "john@example.com",
}).
Build()
if err != nil {
// Handle validation errors
log.Fatal(err)
}The RequestBuilder validates inputs and accumulates errors:
builder := httpx.NewRequestBuilder("https://api.example.com")
builder.HTTPMethod("") // Error: empty method
builder.WithHeader("", "value") // Error: empty header key
builder.WithQueryParam("key=", "val") // Error: invalid character in key
// Check for errors before building
if builder.HasErrors() {
for _, err := range builder.GetErrors() {
log.Printf("Validation error: %v", err)
}
}
// Or let Build() report all errors
req, err := builder.Build()
if err != nil {
// err contains all accumulated validation errors
log.Fatal(err)
}builder := httpx.NewRequestBuilder("https://api.example.com")
// Use builder
req1, _ := builder.WithWithMethodGET().WithPath("/users").Build()
// Reset and reuse
builder.Reset()
req2, _ := builder.WithWithMethodPOST().WithPath("/posts").Build()The GenericClient provides type-safe HTTP requests with automatic JSON marshaling and unmarshaling using Go generics.
- 🎯 Type-safe responses with automatic JSON unmarshaling
- 🔄 Convenience methods: Get, Post, Put, Delete, Patch
- 🔌 Execute method for use with RequestBuilder
- 📦 ExecuteRaw for non-JSON responses
- 🌐 Base URL resolution for relative paths
- 📋 Default headers applied to all requests
- ❌ Structured error responses
- 🔁 Full integration with retry logic
type Post struct {
ID int `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
UserID int `json:"userId"`
}
client := httpx.NewGenericClient[Post](
httpx.WithTimeout[Post](10 * time.Second),
httpx.WithMaxRetries[Post](3),
httpx.WithRetryStrategy[Post](httpx.ExponentialBackoffStrategy),
)
// GET request
response, err := client.Get("https://api.example.com/posts/1")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Title: %s\n", response.Data.Title)
// POST request
newPost := Post{Title: "New Post", Body: "Content", UserID: 1}
postData, _ := json.Marshal(newPost)
response, err = client.Post("https://api.example.com/posts", bytes.NewReader(postData))Combine GenericClient with RequestBuilder for maximum flexibility:
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
client := httpx.NewGenericClient[User](
httpx.WithTimeout[User](15 * time.Second),
httpx.WithMaxRetries[User](3),
)
// Build complex request
req, err := httpx.NewRequestBuilder("https://api.example.com").
WithMethodPOST().
WithPath("/users").
WithContentType("application/json").
WithHeader("X-Request-ID", "unique-123").
WithJSONBody(User{Name: "Jane", Email: "jane@example.com"}).
Build()
if err != nil {
log.Fatal(err)
}
// Execute with type safety
response, err := client.Execute(req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created user ID: %d\n", response.Data.ID)The generic client returns structured errors:
response, err := client.Get("/users/999999")
if err != nil {
// Check if it's an API error
if apiErr, ok := err.(*httpx.ErrorResponse); ok {
fmt.Printf("API Error %d: %s\n", apiErr.StatusCode, apiErr.Message)
// StatusCode: 404
// Message: "User not found"
} else {
// Network error, parsing error, etc.
log.Printf("Request failed: %v\n", err)
}
return
}Use different clients for different response types:
type User struct { /* ... */ }
type Post struct { /* ... */ }
userClient := httpx.NewGenericClient[User](
httpx.WithTimeout[User](10 * time.Second),
)
postClient := httpx.NewGenericClient[Post](
httpx.WithTimeout[Post](10 * time.Second),
)
// Fetch user
userResp, _ := userClient.Get("/users/1")
// Fetch user's posts
postsResp, _ := postClient.Get(fmt.Sprintf("/users/%d/posts", userResp.Data.ID))The package provides transparent retry logic with configurable strategies.
Doubles the wait time between retries:
client := httpx.NewClientBuilder().
WithMaxRetries(3).
WithRetryStrategy(httpx.ExponentialBackoffStrategy).
WithRetryBaseDelay(500 * time.Millisecond).
WithRetryMaxDelay(10 * time.Second).
Build()Wait times: 500ms → 1s → 2s → 4s (capped at maxDelay)
Waits a constant duration between retries:
client := httpx.NewClientBuilder().
WithMaxRetries(3).
WithRetryStrategy(httpx.FixedDelayStrategy).
WithRetryBaseDelay(1 * time.Second).
Build()Wait times: 1s → 1s → 1s
Adds randomization to exponential backoff to prevent thundering herd:
client := httpx.NewClientBuilder().
WithMaxRetries(3).
WithRetryStrategy(httpx.JitterBackoffStrategy).
WithRetryBaseDelay(500 * time.Millisecond).
WithRetryMaxDelay(10 * time.Second).
Build()Wait times: Random between 0-500ms → 0-1s → 0-2s
The retry logic automatically retries:
- Network errors (connection failures, timeouts)
- HTTP 5xx server errors (500-599)
- HTTP 429 (Too Many Requests)
Does NOT retry:
- HTTP 4xx client errors (except 429)
- HTTP 2xx/3xx successful responses
- Requests without GetBody (non-replayable requests)
// Create retry client
retryClient := httpx.NewClientBuilder().
WithMaxRetries(3).
WithRetryStrategy(httpx.ExponentialBackoffStrategy).
Build()
// Use with generic client
client := httpx.NewGenericClient[User](
httpx.WithHTTPClient[User](retryClient),
httpx.)
// Requests automatically retry on failure
response, err := client.Get("/users/1")The ClientBuilder provides fine-grained control over HTTP client configuration.
client := httpx.NewClientBuilder().
// Timeouts
WithTimeout(30 * time.Second).
WithIdleConnTimeout(90 * time.Second).
WithTLSHandshakeTimeout(10 * time.Second).
WithExpectContinueTimeout(1 * time.Second).
// Connection pooling
WithMaxIdleConns(100).
WithMaxIdleConnsPerHost(10).
WithDisableKeepAlive(false).
// Retry configuration
WithMaxRetries(3).
WithRetryStrategy(httpx.ExponentialBackoffStrategy).
WithRetryBaseDelay(500 * time.Millisecond).
WithRetryMaxDelay(10 * time.Second).
Build()| Setting | Default | Valid Range |
|---|---|---|
| Timeout | 5s | 1s - 30s |
| MaxRetries | 3 | 1 - 10 |
| RetryBaseDelay | 500ms | 300ms - 5s |
| RetryMaxDelay | 10s | 300ms - 120s |
| MaxIdleConns | 100 | 1 - 200 |
| IdleConnTimeout | 90s | 1s - 120s |
| TLSHandshakeTimeout | 10s | 1s - 15s |
The builder validates all settings and uses defaults for out-of-range values.
The httpx package provides comprehensive HTTP/HTTPS proxy support across all client types. Configure proxies to route your requests through corporate firewalls, load balancers, or testing proxies.
- ✅ HTTP and HTTPS proxy support
- 🔐 Proxy authentication (username/password)
- 🔄 Works with retry logic
- 🎯 Compatible with all client types
- 🌐 Full URL or host:port formats
- 📝 Graceful fallback on invalid URLs
// HTTP proxy
client := httpx.NewClientBuilder().
WithProxy("http://proxy.example.com:8080").
WithTimeout(10 * time.Second).
Build()
// HTTPS proxy
client := httpx.NewClientBuilder().
WithProxy("https://secure-proxy.example.com:3128").
Build()type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
client := httpx.NewGenericClient[User](
httpx.WithProxy[User]("http://proxy.example.com:8080"),
httpx.WithTimeout[User](10*time.Second),
httpx.WithMaxRetries[User](3),
)
response, err := client.Get("https://api.example.com/users/1")client := httpx.NewHTTPRetryClient(
httpx.WithProxyRetry("http://proxy.example.com:8080"),
httpx.WithMaxRetriesRetry(5),
httpx.WithRetryStrategyRetry(
httpx.ExponentialBackoff(500*time.Millisecond, 30*time.Second),
),
)Include credentials directly in the proxy URL:
client := httpx.NewClientBuilder().
WithProxy("http://username:password@proxy.example.com:8080").
Build()Security Note: For production, consider using environment variables or secret management:
proxyURL := fmt.Sprintf("http://%s:%s@%s:%s",
os.Getenv("PROXY_USER"),
os.Getenv("PROXY_PASS"),
os.Getenv("PROXY_HOST"),
os.Getenv("PROXY_PORT"),
)
client := httpx.NewClientBuilder().
WithProxy(proxyURL).
Build()- HTTP Proxy: 8080, 3128, 8888
- HTTPS Proxy: 3128, 8443
- Squid: 3128 (most common)
- Corporate Proxies: 8080, 80
Override environment proxy settings by passing an empty string:
// Disable proxy (ignore HTTP_PROXY environment variable)
client := httpx.NewClientBuilder().
WithProxy("").
Build()package main
import (
"fmt"
"log"
"time"
"github.com/slashdevops/httpx"
)
type APIResponse struct {
Message string `json:"message"`
Status string `json:"status"`
}
func main() {
// Configure client with proxy and full options
client := httpx.NewGenericClient[APIResponse](
httpx.WithProxy[APIResponse]("http://proxy.example.com:8080"),
httpx.WithTimeout[APIResponse](15*time.Second),
httpx.WithMaxRetries[APIResponse](5),
httpx.WithRetryStrategy[APIResponse](httpx.JitterBackoffStrategy),
httpx.WithRetryBaseDelay[APIResponse](500*time.Millisecond),
httpx.WithRetryMaxDelay[APIResponse](30*time.Second),
)
// Build request with authentication
req, err := httpx.NewRequestBuilder("https://api.example.com").
WithMethodGET().
WithPath("/data").
WithBearerAuth("your-token-here").
WithHeader("Accept", "application/json").
Build()
if err != nil {
log.Fatal(err)
}
// Execute through proxy
response, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Response: %s\n", response.Data.Message)
}The library gracefully handles proxy configuration errors:
client := httpx.NewClientBuilder().
WithProxy("://invalid-url"). // Invalid URL
WithLogger(logger). // Optional: log warnings
Build()
// Client builds successfully, but proxy is not configured
// Warning logged if logger is providedThe httpx package supports optional logging using Go's standard log/slog package. Logging is disabled by default to maintain clean, silent HTTP operations. Enable it when you need observability into retries, errors, and other HTTP client operations.
import (
"log/slog"
"os"
"github.com/slashdevops/httpx"
)
// Create a logger
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelWarn,
}))
// Use with ClientBuilder
client := httpx.NewClientBuilder().
WithMaxRetries(3).
WithLogger(logger). // Enable logging
Build()type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
logger := slog.New(slog.NewJSONHandler(os.Stderr, nil))
client := httpx.NewGenericClient[User](
httpx.WithMaxRetries[User](3),
httpx.WithLogger[User](logger),
)logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
client := httpx.NewHTTPRetryClient(
httpx.WithMaxRetriesRetry(3),
httpx.WithRetryStrategyRetry(httpx.ExponentialBackoff(500*time.Millisecond, 10*time.Second)),
httpx.WithLoggerRetry(logger),
)When a request fails and is being retried:
time=2026-01-17T21:00:00.000+00:00 level=WARN msg="HTTP request returned server error, retrying" attempt=1 max_retries=3 delay=500ms status_code=500 url=https://api.example.com/users method=GET
Attributes logged:
attempt: Current retry attempt number (1-indexed)max_retries: Maximum number of retries configureddelay: How long the client will wait before retryingstatus_code: HTTP status code (for server errors) ORerror: Error message (for network/connection errors)url: Full request URLmethod: HTTP method (GET, POST, etc.)
When all retry attempts are exhausted:
time=2026-01-17T21:00:00.500+00:00 level=ERROR msg="All retry attempts failed" attempts=4 status_code=503 url=https://api.example.com/users method=GET
Attributes logged:
attempts: Total number of attempts made (including initial request)status_codeORerror: Final failure reasonurl: Full request URLmethod: HTTP method
Choose the appropriate log level based on your needs:
// Only log final failures (recommended for production)
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelError,
}))
// Log all retry attempts (useful for debugging)
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelWarn,
}))
// Log everything including debug info from other packages
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))Best for human readability during development:
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelWarn,
}))Output:
time=2026-01-17T21:00:00.000+00:00 level=WARN msg="HTTP request returned server error, retrying" attempt=1 max_retries=3 delay=500ms status_code=500
Best for structured logging and log aggregation:
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelError,
}))Output:
{"time":"2026-01-17T21:00:00.000Z","level":"ERROR","msg":"All retry attempts failed","attempts":4,"status_code":503,"url":"https://api.example.com/users","method":"GET"}logFile, err := os.OpenFile("http.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
defer logFile.Close()
logger := slog.New(slog.NewJSONHandler(logFile, &slog.HandlerOptions{
Level: slog.LevelWarn,
}))-
Default to No Logging: Keep logging disabled in production unless actively troubleshooting:
// Production - no logging (default) client := httpx.NewClientBuilder(). WithMaxRetries(3). Build() // No WithLogger() call = no logging
-
Use Structured Logging in Production: JSON format is machine-readable and works well with log aggregators:
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ Level: slog.LevelError, // Only final failures }))
-
Enable for Specific Troubleshooting: Turn on logging temporarily when investigating issues:
// Temporarily enable for debugging var logger *slog.Logger if os.Getenv("DEBUG_HTTP") != "" { logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, })) } client := httpx.NewClientBuilder(). WithMaxRetries(3). WithLogger(logger). // Will be nil if not debugging Build()
-
Add Context with Attributes: Enhance logs with additional context:
// Create logger with service context logger := slog.New(slog.NewJSONHandler(os.Stderr, nil)). With("service", "api-client"). With("version", "1.0.0") client := httpx.NewClientBuilder(). WithLogger(logger). Build()
-
Different Loggers for Different Clients: Use separate loggers for different clients to distinguish traffic:
// User service client userLogger := slog.New(slog.NewJSONHandler(os.Stderr, nil)). With("client", "user-service") userClient := httpx.NewClientBuilder(). WithLogger(userLogger). Build() // Payment service client paymentLogger := slog.New(slog.NewJSONHandler(os.Stderr, nil)). With("client", "payment-service") paymentClient := httpx.NewClientBuilder(). WithLogger(paymentLogger). Build()
- Minimal Overhead: When logging is disabled (logger is
nil), the overhead is just a simple nil check - No Allocations: Log statements use slog's efficient attribute system
- Deferred Work: The logger only formats messages if the log level is enabled
Simply pass nil or omit the logger:
// Explicitly pass nil
client := httpx.NewClientBuilder().
WithLogger(nil). // No logging
Build()
// Or just don't call WithLogger
client := httpx.NewClientBuilder().
WithMaxRetries(3).
Build() // No logging (default)If you have existing code without logging, no changes are needed. The feature is fully backward compatible:
// Old code - still works, no logging
client := httpx.NewClientBuilder().
WithMaxRetries(3).
Build()
// New code - add logging when needed
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
client := httpx.NewClientBuilder().
WithMaxRetries(3).
WithLogger(logger). // Just add this line
Build()package main
import (
"log/slog"
"os"
"time"
"github.com/slashdevops/httpx"
)
func main() {
// Text output with warn level for debugging
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelWarn,
}))
client := httpx.NewClientBuilder().
WithMaxRetries(3).
WithRetryBaseDelay(500 * time.Millisecond).
WithLogger(logger).
Build()
// You'll see retry attempts in the console
resp, err := client.Get("https://api.example.com/flaky-endpoint")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
}package main
import (
"log/slog"
"os"
"github.com/slashdevops/httpx"
)
func main() {
// JSON output, only errors, to stderr
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelError,
})).With(
"service", "payment-processor",
"environment", "production",
)
client := httpx.NewClientBuilder().
WithMaxRetries(3).
WithLogger(logger).
Build()
// Only final failures will be logged
resp, err := client.Get("https://payment-api.example.com/status")
// ...
}package main
import (
"log/slog"
"os"
"github.com/slashdevops/httpx"
)
func createClient() *http.Client {
var logger *slog.Logger
// Only enable logging if DEBUG environment variable is set
if os.Getenv("DEBUG") != "" {
logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
}
return httpx.NewClientBuilder().
WithMaxRetries(3).
WithLogger(logger). // Will be nil in production
Build()
}-
Check logger level: Make sure the level is set to at least
LevelWarn:logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, // Not Info or Debug }))
-
Verify logger is passed: Make sure you called
WithLogger():client := httpx.NewClientBuilder(). WithLogger(logger). // Don't forget this! Build()
-
Check if retries are happening: Logs only appear when requests fail and retry. Successful first attempts don't log.
- Increase log level to
LevelErrorto only see final failures - Disable logging in production environments where retry behavior is well understood
- Use sampling if your log aggregation system supports it
The logging feature in httpx provides:
- ✅ Optional - Disabled by default, zero overhead when not in use
- ✅ Standard - Uses Go's
log/slogpackage - ✅ Flexible - Configurable output format, level, and destination
- ✅ Informative - Rich attributes for debugging and monitoring
- ✅ Backward Compatible - Existing code works without changes
Enable it when you need visibility, keep it off for clean, silent operations.
package main
import (
"fmt"
"log"
"time"
"github.com/slashdevops/httpx"
)
type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
UserID int `json:"userId"`
}
func main() {
// Create retry client
retryClient := httpx.NewClientBuilder().
WithMaxRetries(3).
WithRetryStrategy(httpx.ExponentialBackoffStrategy).
WithTimeout(10 * time.Second).
Build()
// Create typed client
client := httpx.NewGenericClient[Todo](
httpx.WithHTTPClient[Todo](retryClient),
httpx. httpx. )
// GET - Read
fmt.Println("Fetching todo...")
todo, err := client.Get("/todos/1")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Todo: %s (completed: %v)\n", todo.Data.Title, todo.Data.Completed)
// POST - Create
fmt.Println("\nCreating new todo...")
newTodo := Todo{
Title: "Learn httputils",
Completed: false,
UserID: 1,
}
req, _ := httpx.NewRequestBuilder("https://jsonplaceholder.typicode.com").
WithMethodPOST().
WithPath("/todos").
WithContentType("application/json").
WithJSONBody(newTodo).
Build()
created, err := client.Execute(req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created todo ID: %d\n", created.Data.ID)
// PUT - Update
fmt.Println("\nUpdating todo...")
updateTodo := created.Data
updateTodo.Completed = true
req, _ = httpx.NewRequestBuilder("https://jsonplaceholder.typicode.com").
WithMethodPUT().
WithPath(fmt.Sprintf("/todos/%d", updateTodo.ID)).
WithContentType("application/json").
WithJSONBody(updateTodo).
Build()
updated, err := client.Execute(req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Updated: completed = %v\n", updated.Data.Completed)
// DELETE
fmt.Println("\nDeleting todo...")
deleteResp, err := client.Delete(fmt.Sprintf("/todos/%d", updateTodo.ID))
if err != nil {
log.Fatal(err)
}
fmt.Printf("Deleted (status: %d)\n", deleteResp.StatusCode)
}// Basic Authentication
req, err := httpx.NewRequestBuilder("https://api.example.com").
WithMethodGET().
WithPath("/protected/resource").
WithBasicAuth("username", "password").
Build()
// Bearer Token Authentication
req, err := httpx.NewRequestBuilder("https://api.example.com").
WithMethodGET().
WithPath("/protected/resource").
WithBearerAuth("your-jwt-token").
Build()
// With Generic Client
client := httpx.NewGenericClient[Resource](
httpx. httpx.)// Request with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := httpx.NewRequestBuilder("https://api.example.com").
WithMethodGET().
WithPath("/slow-endpoint").
Context(ctx).
Build()
// Request with cancellation
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(2 * time.Second)
cancel() // Cancel after 2 seconds
}()
req, err := httpx.NewRequestBuilder("https://api.example.com").
WithMethodGET().
WithPath("/endpoint").
Context(ctx).
Build()req, err := httpx.NewRequestBuilder("https://api.example.com").
WithMethodGET().
WithPath("/search").
WithQueryParam("q", "golang").
WithQueryParam("sort", "relevance").
WithQueryParam("limit", "10").
WithHeader("Accept", "application/json").
WithHeader("Accept-Language", "en-US").
WithHeader("X-Request-ID", generateRequestID()).
WithHeader("X-Correlation-ID", getCorrelationID()).
WithUserAgent("MyApp/1.0 (Go)").
Build()NewRequestBuilder(baseURL string) *RequestBuilder
WithMethodGET() *RequestBuilderWithMethodPOST() *RequestBuilderWithMethodPUT() *RequestBuilderWithMethodDELETE() *RequestBuilderWithMethodPATCH() *RequestBuilderWithMethodHEAD() *RequestBuilderWithMethodOPTIONS() *RequestBuilderWithMethodTRACE() *RequestBuilderWithMethodCONNECT() *RequestBuilderWithMethod(method string) *RequestBuilder- Custom HTTP method with validation
WithPath(path string) *RequestBuilder- Set URL pathWithQueryParam(key, value string) *RequestBuilder- Add single query parameterQueryParams(params map[string]string) *RequestBuilder- Add multiple query parameters
WithHeader(key, value string) *RequestBuilder- Set single headerHeaders(headers map[string]string) *RequestBuilder- Set multiple headersWithContentType(contentType string) *RequestBuilder- Set Content-Type headerWithAccept(accept string) *RequestBuilder- Set Accept headerWithUserAgent(userAgent string) *RequestBuilder- Set User-Agent header
WithBasicAuth(username, password string) *RequestBuilder- Set Basic authenticationWithBearerAuth(token string) *RequestBuilder- Set Bearer token authentication
WithJSONBody(body any) *RequestBuilder- Set JSON body (auto-marshals)RawBody(body io.Reader) *RequestBuilder- Set raw bodyWithStringBody(body string) *RequestBuilder- Set string bodyBytesBody(body []byte) *RequestBuilder- Set bytes body
Context(ctx context.Context) *RequestBuilder- Set request contextBuild() (*http.Request, error)- Build and validate request
HasErrors() bool- Check if there are validation errorsGetErrors() []error- Get all validation errorsReset() *RequestBuilder- Reset builder state
NewGenericClient[T any](options ...GenericClientOption[T]) *GenericClient[T]
WithHTTPClient[T any](httpClient HTTPClient) GenericClientOption[T]- Use a pre-configured HTTP client (takes precedence)WithTimeout[T any](timeout time.Duration) GenericClientOption[T]- Set request timeoutWithMaxRetries[T any](maxRetries int) GenericClientOption[T]- Set maximum retry attemptsWithRetryStrategy[T any](strategy Strategy) GenericClientOption[T]- Set retry strategy (fixed, jitter, exponential)WithRetryStrategyAsString[T any](strategy string) GenericClientOption[T]- Set retry strategy from stringWithRetryBaseDelay[T any](baseDelay time.Duration) GenericClientOption[T]- Set base delay for retry strategiesWithRetryMaxDelay[T any](maxDelay time.Duration) GenericClientOption[T]- Set maximum delay for retry strategiesWithMaxIdleConns[T any](maxIdleConns int) GenericClientOption[T]- Set maximum idle connectionsWithIdleConnTimeout[T any](idleConnTimeout time.Duration) GenericClientOption[T]- Set idle connection timeoutWithTLSHandshakeTimeout[T any](tlsHandshakeTimeout time.Duration) GenericClientOption[T]- Set TLS handshake timeoutWithExpectContinueTimeout[T any](expectContinueTimeout time.Duration) GenericClientOption[T]- Set expect continue timeoutWithMaxIdleConnsPerHost[T any](maxIdleConnsPerHost int) GenericClientOption[T]- Set maximum idle connections per hostWithDisableKeepAlive[T any](disableKeepAlive bool) GenericClientOption[T]- Disable HTTP keep-alive
Execute(req *http.Request) (*Response[T], error)- Execute request with type safetyExecuteRaw(req *http.Request) (*http.Response, error)- Execute and return raw responseDo(req *http.Request) (*Response[T], error)- Alias for ExecuteGet(url string) (*Response[T], error)- Execute GET requestPost(url string, body io.Reader) (*Response[T], error)- Execute POST requestPut(url string, body io.Reader) (*Response[T], error)- Execute PUT requestDelete(url string) (*Response[T], error)- Execute DELETE requestPatch(url string, body io.Reader) (*Response[T], error)- Execute PATCH requestGetBaseURL() string- Get configured base URLGetDefaultHeaders() map[string]string- Get configured headers
NewClientBuilder() *ClientBuilder
WithTimeout(timeout time.Duration) *ClientBuilderWithMaxRetries(maxRetries int) *ClientBuilderWithRetryStrategy(strategy Strategy) *ClientBuilderWithRetryBaseDelay(baseDelay time.Duration) *ClientBuilderWithRetryMaxDelay(maxDelay time.Duration) *ClientBuilderWithMaxIdleConns(maxIdleConns int) *ClientBuilderWithMaxIdleConnsPerHost(maxIdleConnsPerHost int) *ClientBuilderWithIdleConnTimeout(idleConnTimeout time.Duration) *ClientBuilderWithTLSHandshakeTimeout(tlsHandshakeTimeout time.Duration) *ClientBuilderWithExpectContinueTimeout(expectContinueTimeout time.Duration) *ClientBuilderWithDisableKeepAlive(disableKeepAlive bool) *ClientBuilderBuild() *http.Client- Build configured client
ExponentialBackoff(base, maxDelay time.Duration) RetryStrategyFixedDelay(delay time.Duration) RetryStrategyJitterBackoff(base, maxDelay time.Duration) RetryStrategy
type Response[T any] struct {
Data T // Parsed response data
StatusCode int // HTTP status code
Headers http.Header // Response headers
RawBody []byte // Raw response body
}type ErrorResponse struct {
Message string `json:"message,omitempty"`
StatusCode int `json:"statusCode,omitempty"`
ErrorMsg string `json:"error,omitempty"`
Details string `json:"details,omitempty"`
}const (
FixedDelayStrategy Strategy = "fixed"
JitterBackoffStrategy Strategy = "jitter"
ExponentialBackoffStrategy Strategy = "exponential"
)req, err := httpx.NewRequestBuilder(baseURL).
WithMethodGET().
WithPath("/endpoint").
Build()
if err != nil {
log.Printf("Request building failed: %v", err)
return
}// Define your model
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
// Create typed client
client := httpx.NewGenericClient[User](
httpx.)
// Enjoy type safety
response, err := client.Get("/users/1")
// response.Data is User, not interface{}client := httpx.NewClientBuilder().
WithMaxRetries(3).
WithRetryStrategy(httpx.ExponentialBackoffStrategy).
WithRetryBaseDelay(500 * time.Millisecond).
WithRetryMaxDelay(10 * time.Second).
WithTimeout(30 * time.Second).
Build()// Create once, reuse many times
retryClient := httpx.NewClientBuilder().
WithMaxRetries(3).
Build()
userClient := httpx.NewGenericClient[User](
httpx.WithHTTPClient[User](retryClient),
)
postClient := httpx.NewGenericClient[Post](
httpx.WithHTTPClient[Post](retryClient),
)ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := httpx.NewRequestBuilder(baseURL).
WithMethodGET().
WithPath("/endpoint").
Context(ctx).
Build()builder := httpx.NewRequestBuilder(baseURL).
WithMethodGET().
WithPath("/endpoint")
// Add potentially invalid inputs
builder.WithHeader(userProvidedKey, userProvidedValue)
builder.WithQueryParam(userProvidedParam, userProvidedValue)
// Check for errors before building
if builder.HasErrors() {
for _, err := range builder.GetErrors() {
log.Printf("Validation error: %v", err)
}
return
}
req, err := builder.Build()response, err := client.Get("/resource")
if err != nil {
if apiErr, ok := err.(*httpx.ErrorResponse); ok {
switch apiErr.StatusCode {
case 404:
log.Printf("Resource not found: %s", apiErr.Message)
case 401:
log.Printf("Authentication failed: %s", apiErr.Message)
case 429:
log.Printf("Rate limit exceeded: %s", apiErr.Message)
default:
log.Printf("API error %d: %s", apiErr.StatusCode, apiErr.Message)
}
} else {
log.Printf("Network error: %v", err)
}
return
}All utilities in this package are safe for concurrent use:
client := httpx.NewGenericClient[User](
httpx.)
// Safe to use from multiple goroutines
var wg sync.WaitGroup
for i := 1; i <= 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
user, err := client.Get(fmt.Sprintf("/users/%d", id))
if err != nil {
log.Printf("Error fetching user %d: %v", id, err)
return
}
log.Printf("Fetched user: %s", user.Data.Name)
}(i)
}
wg.Wait()The package has comprehensive test coverage (88%+):
go test ./... -v
go test ./... -coverContributions are welcome! Please ensure:
- Build passes:
go build ./... - All tests pass:
go test ./... - Code is formatted:
go fmt ./... - Linters pass:
golangci-lint run ./... - Add tests for new features
- Update documentation
Apache License 2.0. See LICENSE for details.
Developed by the slashdevops team using Agentic Development. Inspired by popular HTTP client libraries and Go best practices.