Skip to content

cdyue/lian

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

8 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Lian - Modern Go HTTP Client Library

Lian is a high-performance, production-ready HTTP client library for Go, designed with best practices in mind, offering a clean and intuitive API while providing powerful features for modern microservices architectures.

✨ Key Features

  • Fluent Chainable API: Clean and readable method chaining for request configuration
  • Automatic Compression: Built-in support for gzip, brotli, zstd, and deflate decompression
  • Request Compression: Optional zstd compression for request bodies with dictionary support and instance pooling
  • Automatic Retry: Configurable automatic retry with exponential backoff and jitter for transient errors
  • Structured Logging: Uses standard library slog by default, supports custom logger implementations
  • OpenTelemetry Support: Built-in distributed tracing integration with configurable propagation formats (W3C, B3)
  • Context-Aware: Full support for standard context.Context propagation
  • Customizable Header Mapping: Flexible configuration for authentication and tenant headers
  • Automatic Response Unmarshaling: Directly unmarshal JSON responses into structs
  • Debugging Tools: Request/response dumping and HTTP trace capabilities
  • Connection Pooling: Optimized HTTP client with configurable connection pooling
  • No Framework Lock-In: Works with any Go web framework (Gin, Echo, Fiber, standard library, etc.)

πŸ“¦ Installation

go get github.com/cdyue/lian

πŸš€ Quick Start

Simple GET Request

package main

import (
	"context"
	"fmt"
	"github.com/cdyue/lian"
)

func main() {
	// Simple GET request
	resp := lian.Get("https://jsonplaceholder.typicode.com/posts/1")
	if resp.IsError() {
		panic(resp.Error())
	}

	// Parse JSON response
	var post map[string]interface{}
	if err := resp.JSON(&post); err != nil {
		panic(err)
	}

	fmt.Printf("Post Title: %s\n", post["title"])
}

POST Request with JSON Body

user := map[string]interface{}{
	"name":  "John Doe",
	"email": "john@example.com",
}

resp := lian.NewRequest().
	SetJSONBody(user).
	SetBearerToken("your-auth-token").
	Post("https://api.example.com/users")

if resp.IsSuccess() {
	fmt.Println("User created successfully!")
}

βš™οΈ Advanced Configuration

Custom HTTP Client

import "time"

// Create a custom HTTP client with specific settings
client := lian.NewClient(
	lian.WithTimeout(10 * time.Second),
	lian.WithInsecure(), // Skip TLS certificate verification (use carefully!)
	lian.WithMaxIdleConns(200),
)

// Use the custom client for requests
req := lian.NewRequest().SetClient(client)
resp := req.Get("https://api.example.com/data")

Custom Header Mapping

Lian provides flexible configuration for core headers like authentication, tenant ID, etc.

Global Configuration (applies to all requests)

// Customize header names globally
lian.SetDefaultAuthHeaderName("X-Access-Token")
lian.SetDefaultTenantIDHeaderName("X-Organization-ID")
lian.SetDefaultUserIDHeaderName("X-Current-User-ID")

// Custom value extractor functions (for integrating with your existing context)
lian.SetDefaultTenantIDExtractor(func(ctx context.Context) string {
	// Example: Extract tenant ID from Gin context
	if ginCtx, ok := ctx.Value("gin.Context").(*gin.Context); ok {
		return ginCtx.GetString("tenant_id")
	}
	return ""
})

Per-Request Configuration

// Customize headers for a specific request
customMapping := lian.HeaderMapping{
	AuthHeader:     "X-API-Key",
	TenantIDHeader: "X-Company-ID",
	// Leave other fields empty to disable extraction
}

req := lian.NewRequest().
	SetHeaderMapping(customMapping).
	FromContext(ctx).
	Get("https://api.example.com/data")

Context Header Propagation

// In your middleware, store headers in context
ctx := context.Background()
ctx = lian.SetHeaderToContext(ctx, "Authorization", "Bearer token123")
ctx = lian.SetHeaderToContext(ctx, "X-Tenant-Id", "acme-corp")

// Later when making requests, headers are automatically extracted
resp := lian.NewRequest().
	FromContext(ctx).
	Get("https://api.example.com/protected")

Logging Configuration

import (
	"log/slog"
	"os"
)

// Use a custom slog instance
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
	Level: slog.LevelDebug,
}))
lian.SetLogger(logger)

// Or implement the Logger interface for full customization
type CustomLogger struct {
	// Your logger implementation
}

func (l *CustomLogger) Info(msg string, args ...interface{})  { /* ... */ }
func (l *CustomLogger) Debug(msg string, args ...interface{}) { /* ... */ }
func (l *CustomLogger) Warn(msg string, args ...interface{})  { /* ... */ }
func (l *CustomLogger) Error(msg string, args ...interface{}) { /* ... */ }

lian.SetLogger(&CustomLogger{})

Debugging

// Enable request/response dumping
req := lian.NewRequest().
	EnableDumpRequest().  // Log full request details
	EnableDumpResponse(). // Log full response details
	EnableHTTPTrace().    // Log HTTP connection details (DNS, TCP, TLS, etc.)
	Get("https://api.example.com/debug")

Retry Configuration

Lian supports automatic retry for transient errors with configurable exponential backoff and jitter.

Client-level Retry (applies to all requests)

import "time"
import "github.com/klauspost/compress/zstd"

// Create client with default retry configuration (3 retries)
client := lian.NewClient(
    lian.WithRetry(3),
)

// Create client with full custom retry configuration
client := lian.NewClient(
    lian.WithRetryConfig(
        3,                  // max retries
        100*time.Millisecond, // base interval
        2.0,                // backoff factor
        0.2,                // jitter factor (0-1)
    ),
    lian.WithRetryableStatuses(429, 500, 502, 503, 504), // custom retryable status codes
)

Request-level Retry (overrides client configuration)

// Enable retry for a specific request
resp := lian.NewRequest().
    SetRetry(2). // 2 retries for this request
    SetRetryConfig(2, 50*time.Millisecond, 1.5, 0.1). // custom config
    SetRetryableStatuses(429, 503). // custom status codes for this request
    Get("https://api.example.com/data")

Zstd Compression Configuration

Lian provides enhanced zstd support with instance pooling and pre-trained dictionary support for improved performance and compression ratios.

Client-level Zstd Configuration

// Create client with custom zstd settings
client := lian.NewClient(
    lian.WithZstdCompressionLevel(int(zstd.BestSpeed)), // compression level
    lian.WithZstdDictionary(myPreTrainedDictionary), // use pre-trained dictionary
    lian.WithZstdPooling(true), // enable encoder/decoder pooling (default true)
)

Request-level Zstd Configuration

// Enable zstd compression with custom level and dictionary
resp := lian.NewRequest().
    EnableZstdCompressionWithLevel(int(zstd.BestCompression)).
    SetZstdCompressionLevel(int(zstd.BestCompression)).
    SetZstdDictionary(requestSpecificDict).
    Post("https://api.example.com/upload").
    SetJSONBody(largePayload)

OpenTelemetry Tracing

Lian has built-in OpenTelemetry distributed tracing support (enabled by default, no additional build tags required).

Global Control

// Disable OpenTelemetry tracing globally for all requests
lian.DisableOtelTrace()

// Re-enable OpenTelemetry tracing globally
lian.EnableOtelTrace()

Per-Request Control

// Disable tracing for a specific request
req := lian.NewRequest().
	DisableOtelTrace().
	Get("https://api.example.com/debug")

// Force enable tracing for a specific request even when globally disabled
req := lian.NewRequest().
	EnableOtelTraceForRequest().
	Get("https://api.example.com/debug")

Lian automatically creates client spans, propagates trace headers, and records all HTTP events to your OpenTelemetry collector when tracing is enabled.

Trace Propagation Format Configuration

Lian supports configurable trace propagation formats, defaulting to W3C Trace Context with optional B3 support.

// Global configuration
lian.SetTracePropagationFormats(lian.TracePropagationW3C, lian.TracePropagationB3) // Enable both W3C and B3
// Or use convenience methods
lian.EnableB3TracePropagation() // Only use B3 single header format
lian.EnableCompositeTracePropagation() // Use both W3C and B3 (for migration scenarios)

// Per-request override (chainable)
resp := lian.Get("https://api.example.com/data").
    SetTracePropagationFormats(lian.TracePropagationB3Multi). // Use B3 multi-header format for this request
    Send()

// Configuration via functional options when creating Request
req := lian.NewRequest(
    lian.WithB3TracePropagation(),
    lian.WithTimeout(10*time.Second),
)

Supported propagation formats:

  • TracePropagationW3C (default): W3C Trace Context format (traceparent and tracestate headers)
  • TracePropagationB3: B3 single header format (b3 header)
  • TracePropagationB3Multi: B3 multi-header format (X-B3-TraceId, X-B3-SpanId, etc.)

Note: To use B3 propagation formats, you need to install the optional dependency:

go get go.opentelemetry.io/contrib/propagators/b3@v1.42.0

πŸ”§ Compilation Notes

Using Standard JSON (stable)

To use the stable standard library encoding/json, modify the imports in:

  • response.go: Change encoding/json/v2 to encoding/json
  • body.go: Change encoding/json/v2 to encoding/json

Then build normally:

go build ./...

Using JSON v2 (experimental, higher performance)

Build with the JSON v2 experiment enabled:

GOEXPERIMENT=jsonv2 go build ./...

πŸ“– API Reference

Core Methods

  • lian.Get(url string) *Response
  • lian.Post(url string) *Response
  • lian.Put(url string) *Response
  • lian.Delete(url string) *Response
  • lian.Patch(url string) *Response
  • lian.Do(method, url string, body interface{}, headers map[string]string) *Response

Client Options

  • WithRetry(maxRetries int) OpFunc - Enable retry with default configuration
  • WithRetryConfig(maxRetries int, interval time.Duration, backoffFactor float64, jitter float64) OpFunc - Full retry configuration
  • WithRetryableStatuses(statuses ...int) OpFunc - Custom retryable HTTP status codes
  • WithZstdCompressionLevel(level int) OpFunc - Set zstd compression level
  • WithZstdDictionary(dict []byte) OpFunc - Set pre-trained zstd dictionary
  • WithZstdPooling(enable bool) OpFunc - Enable/disable zstd encoder/decoder pooling
  • WithTimeout(d time.Duration) OpFunc - Set overall request timeout
  • WithInsecure() OpFunc - Disable TLS certificate verification
  • WithDisableCompression() OpFunc - Disable response compression
  • WithMaxIdleConns(n int) OpFunc - Set maximum number of idle connections
  • WithIdleConnTimeout(d time.Duration) OpFunc - Set idle connection timeout
  • WithTenant(tenant string) OpFunc - Set tenant ID header
  • WithUserID(userID string) OpFunc - Set user ID header
  • WithOperator(operator string) OpFunc - Deprecated alias for WithUserID
  • WithTracePropagationFormats(formats ...string) OpFunc - Set trace propagation formats
  • WithB3TracePropagation() OpFunc - Enable B3 single header propagation format
  • WithCompositeTracePropagation() OpFunc - Enable both W3C and B3 propagation formats

Request Configuration

  • SetHeader(key, value string) *Request
  • SetQueryParam(key, value string) *Request
  • SetJSONBody(v interface{}) *Request
  • SetFormBody(v interface{}) *Request
  • SetRawBody(body io.Reader, contentType string) *Request
  • SetBearerToken(token string) *Request
  • SetBasicAuth(username, password string) *Request
  • SetTenant(tenant string) *Request - Set tenant ID header (uses configured header name, default "X-Tenant-Id")
  • SetUserID(userID string) *Request - Set user ID header (uses configured header name, default "X-User-Id")
  • SetOperator(operator string) *Request - Deprecated alias for SetUserID
  • FromContext(ctx context.Context) *Request
  • SetRetry(maxRetries int) *Request - Enable retry for this request
  • SetRetryConfig(maxRetries int, interval time.Duration, backoffFactor float64, jitter float64) *Request - Set full retry configuration
  • SetRetryableStatuses(statuses ...int) *Request - Set custom retryable status codes
  • EnableZstdCompression() *Request - Enable zstd compression for request body
  • EnableZstdCompressionWithLevel(level int) *Request - Enable zstd compression with custom level
  • SetZstdCompressionLevel(level int) *Request - Set zstd compression level
  • SetZstdDictionary(dict []byte) *Request - Set pre-trained zstd dictionary for this request
  • SetTracePropagationFormats(formats ...string) *Request - Set trace propagation formats for this request
  • EnableB3TracePropagation() *Request - Enable B3 single header propagation format
  • EnableCompositeTracePropagation() *Request - Enable both W3C and B3 propagation formats

Response Methods

  • resp.StatusCode() int
  • resp.IsSuccess() bool
  • resp.IsError() bool
  • resp.Bytes() ([]byte, error)
  • resp.String() (string, error)
  • resp.JSON(v interface{}) error
  • resp.Save(w io.Writer) (int64, error)

πŸ“„ License

MIT License

About

golang http client

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages