http

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Dec 27, 2025 License: MIT Imports: 9 Imported by: 0

README

http Package

HTTP client for inter-service communication with retry logic, timeouts, and automatic logging.

Features

  • Automatic Retry: Configurable retry attempts for failed requests
  • Timeout Management: Request-level timeout configuration
  • JSON Support: Automatic JSON serialization/deserialization
  • Custom Headers: Easy header management (Authorization, etc.)
  • Logging Integration: Automatic request/response logging with duration
  • Error Handling: Smart retry logic (no retry on 4xx errors)
  • Context Support: Cancellation and timeout propagation

Installation

import "github.com/LaRestoOU/laresto-go-common/pkg/http"

Quick Start

Create Client
cfg := http.Config{
    Timeout:    30 * time.Second,
    MaxRetries: 3,
    RetryDelay: 1 * time.Second,
}

log := logger.New(logger.Config{...})
client := http.NewClient(cfg, log)
GET Request
ctx := context.Background()
resp, err := client.Get(ctx, "https://api.example.com/users/123", nil)
if err != nil {
    return err
}

var user User
if err := resp.JSON(&user); err != nil {
    return err
}
POST Request
type CreateUserRequest struct {
    Email string `json:"email"`
    Name  string `json:"name"`
}

req := CreateUserRequest{
    Email: "user@example.com",
    Name:  "John Doe",
}

resp, err := client.Post(ctx, "https://api.example.com/users", req, nil)
if err != nil {
    return err
}
With Headers
headers := map[string]string{
    "Authorization": "Bearer " + token,
    "X-Request-ID":  requestID,
}

resp, err := client.Get(ctx, url, headers)

Configuration

type Config struct {
    // Timeout is the request timeout (default: 30s)
    Timeout time.Duration

    // MaxRetries is retry attempts (default: 3)
    MaxRetries int

    // RetryDelay is delay between retries (default: 1s)
    RetryDelay time.Duration

    // UserAgent is the User-Agent header (default: "laresto-http-client/1.0")
    UserAgent string
}

Methods

GET
resp, err := client.Get(ctx, url, headers)
POST
resp, err := client.Post(ctx, url, body, headers)
PUT
resp, err := client.Put(ctx, url, body, headers)
DELETE
resp, err := client.Delete(ctx, url, headers)
Generic Do
resp, err := client.Do(ctx, "PATCH", url, body, headers)

Response Handling

// Get response
resp, err := client.Get(ctx, url, nil)

// Parse JSON
var result MyType
if err := resp.JSON(&result); err != nil {
    return err
}

// Get raw string
text := resp.String()

// Access status code
statusCode := resp.StatusCode

// Access headers
contentType := resp.Headers.Get("Content-Type")

Usage Patterns

Service-to-Service Communication
type UserServiceClient struct {
    httpClient *http.Client
    baseURL    string
}

func (c *UserServiceClient) GetUser(ctx context.Context, userID string) (*User, error) {
    url := fmt.Sprintf("%s/users/%s", c.baseURL, userID)
    
    resp, err := c.httpClient.Get(ctx, url, nil)
    if err != nil {
        return nil, err
    }
    
    var user User
    if err := resp.JSON(&user); err != nil {
        return nil, err
    }
    
    return &user, nil
}

func (c *UserServiceClient) CreateUser(ctx context.Context, req CreateUserRequest) (*User, error) {
    url := fmt.Sprintf("%s/users", c.baseURL)
    
    resp, err := c.httpClient.Post(ctx, url, req, nil)
    if err != nil {
        return nil, err
    }
    
    var user User
    if err := resp.JSON(&user); err != nil {
        return nil, err
    }
    
    return &user, nil
}
Authenticated Requests
type AuthenticatedClient struct {
    httpClient *http.Client
    token      string
}

func (c *AuthenticatedClient) makeRequest(ctx context.Context, method, url string, body interface{}) (*http.Response, error) {
    headers := map[string]string{
        "Authorization": "Bearer " + c.token,
    }
    
    return c.httpClient.Do(ctx, method, url, body, headers)
}

func (c *AuthenticatedClient) GetProfile(ctx context.Context) (*Profile, error) {
    resp, err := c.makeRequest(ctx, "GET", "/api/profile", nil)
    if err != nil {
        return nil, err
    }
    
    var profile Profile
    if err := resp.JSON(&profile); err != nil {
        return nil, err
    }
    
    return &profile, nil
}
Circuit Breaker Pattern
type ResilientClient struct {
    httpClient *http.Client
    failures   int
    threshold  int
    isOpen     bool
}

func (c *ResilientClient) Call(ctx context.Context, url string) error {
    if c.isOpen {
        return errors.New("CIRCUIT_OPEN", "Circuit breaker is open", 503)
    }
    
    _, err := c.httpClient.Get(ctx, url, nil)
    if err != nil {
        c.failures++
        if c.failures >= c.threshold {
            c.isOpen = true
        }
        return err
    }
    
    c.failures = 0
    return nil
}

Retry Logic

Automatic retry on:

  • 5xx server errors
  • Network errors
  • Timeouts

No retry on:

  • 4xx client errors (bad request, unauthorized, not found, etc.)
  • Context cancellation
  • Successfully completed requests
// This will retry up to 3 times
cfg := http.Config{
    MaxRetries: 3,
    RetryDelay: 1 * time.Second,
}

client := http.NewClient(cfg, log)

// 500 error -> retries
resp, err := client.Get(ctx, url, nil)

// 404 error -> no retry (client error)
resp, err := client.Get(ctx, url, nil)

Timeout Handling

// Request-level timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

resp, err := client.Get(ctx, url, nil)

// Client-level timeout
cfg := http.Config{
    Timeout: 30 * time.Second, // All requests timeout after 30s
}
client := http.NewClient(cfg, log)

Best Practices

DO ✅
// Always use context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// Check errors
resp, err := client.Get(ctx, url, nil)
if err != nil {
    return err
}

// Use appropriate HTTP methods
client.Get(ctx, url, nil)    // Read
client.Post(ctx, url, body, nil)   // Create
client.Put(ctx, url, body, nil)    // Update
client.Delete(ctx, url, nil) // Delete

// Include request IDs for tracing
headers := map[string]string{
    "X-Request-ID": requestID,
}
DON'T ❌
// Don't use without timeout
ctx := context.Background() // No timeout!

// Don't ignore errors
client.Get(ctx, url, nil) // Error ignored!

// Don't use wrong HTTP methods
client.Get(ctx, url, body, nil) // GET with body!

// Don't retry on 4xx errors manually
// Library handles this automatically

Error Handling

resp, err := client.Get(ctx, url, nil)
if err != nil {
    // Check error type
    if ctx.Err() == context.DeadlineExceeded {
        // Timeout
        log.Error("Request timeout")
    } else {
        // Other error (network, server, etc.)
        log.Error("Request failed", err)
    }
    return err
}

// Check status code
if resp.StatusCode >= 400 {
    // Handle error response
    var errResp ErrorResponse
    resp.JSON(&errResp)
    return fmt.Errorf("API error: %s", errResp.Message)
}

Testing

Mock Server
func TestClient(t *testing.T) {
    // Create test server
    server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"id":"123"}`))
    }))
    defer server.Close()
    
    // Test client
    client := http.NewClient(http.Config{}, logger.NewDefault())
    resp, err := client.Get(context.Background(), server.URL, nil)
    
    require.NoError(t, err)
    assert.Equal(t, 200, resp.StatusCode)
}

Performance Tips

  1. Reuse clients: Create once, use many times
  2. Set appropriate timeouts: Balance reliability vs responsiveness
  3. Configure retries wisely: Too many = slow, too few = unreliable
  4. Use connection pooling: Handled automatically by http.Client

Security

// ✅ GOOD: Use HTTPS
client.Get(ctx, "https://api.example.com/data", nil)

// ❌ BAD: Use HTTP for sensitive data
client.Get(ctx, "http://api.example.com/data", nil)

// ✅ GOOD: Include auth headers
headers := map[string]string{
    "Authorization": "Bearer " + token,
}

// ✅ GOOD: Validate TLS certificates (automatic)

License

MIT License - see LICENSE file for details

Documentation

Overview

Package http provides HTTP client functionality with retry logic, timeouts, and logging.

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
}

Client wraps http.Client with additional functionality.

func NewClient

func NewClient(cfg Config, log *logger.Logger) *Client

NewClient creates a new HTTP client.

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, url string, headers map[string]string) (*Response, error)

Delete performs a DELETE request.

func (*Client) Do

func (c *Client) Do(ctx context.Context, method, url string, body interface{}, headers map[string]string) (*Response, error)

Do performs an HTTP request with retry logic.

func (*Client) Get

func (c *Client) Get(ctx context.Context, url string, headers map[string]string) (*Response, error)

Get performs a GET request.

func (*Client) Post

func (c *Client) Post(ctx context.Context, url string, body interface{}, headers map[string]string) (*Response, error)

Post performs a POST request with JSON body.

func (*Client) Put

func (c *Client) Put(ctx context.Context, url string, body interface{}, headers map[string]string) (*Response, error)

Put performs a PUT request with JSON body.

type Config

type Config struct {
	// Timeout is the request timeout (default: 30s)
	Timeout time.Duration

	// MaxRetries is the maximum number of retry attempts (default: 3)
	MaxRetries int

	// RetryDelay is the delay between retries (default: 1s)
	RetryDelay time.Duration

	// UserAgent is the User-Agent header (default: "laresto-http-client/1.0")
	UserAgent string
}

Config holds HTTP client configuration.

type Response

type Response struct {
	StatusCode int
	Headers    http.Header
	Body       []byte
}

Response represents an HTTP response.

func (*Response) JSON

func (r *Response) JSON(v interface{}) error

JSON unmarshals the response body into the provided struct.

func (*Response) String

func (r *Response) String() string

String returns the response body as a string.

Jump to

Keyboard shortcuts

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