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.
- 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
slogby 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.Contextpropagation - 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.)
go get github.com/cdyue/lianpackage 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"])
}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!")
}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")Lian provides flexible configuration for core headers like authentication, tenant ID, etc.
// 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 ""
})// 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")// 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")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{})// 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")Lian supports automatic retry for transient errors with configurable exponential backoff and jitter.
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
)// 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")Lian provides enhanced zstd support with instance pooling and pre-trained dictionary support for improved performance and compression ratios.
// 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)
)// 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)Lian has built-in OpenTelemetry distributed tracing support (enabled by default, no additional build tags required).
// Disable OpenTelemetry tracing globally for all requests
lian.DisableOtelTrace()
// Re-enable OpenTelemetry tracing globally
lian.EnableOtelTrace()// 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.
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 (traceparentandtracestateheaders)TracePropagationB3: B3 single header format (b3header)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.0To use the stable standard library encoding/json, modify the imports in:
response.go: Changeencoding/json/v2toencoding/jsonbody.go: Changeencoding/json/v2toencoding/json
Then build normally:
go build ./...Build with the JSON v2 experiment enabled:
GOEXPERIMENT=jsonv2 go build ./...lian.Get(url string) *Responselian.Post(url string) *Responselian.Put(url string) *Responselian.Delete(url string) *Responselian.Patch(url string) *Responselian.Do(method, url string, body interface{}, headers map[string]string) *Response
WithRetry(maxRetries int) OpFunc- Enable retry with default configurationWithRetryConfig(maxRetries int, interval time.Duration, backoffFactor float64, jitter float64) OpFunc- Full retry configurationWithRetryableStatuses(statuses ...int) OpFunc- Custom retryable HTTP status codesWithZstdCompressionLevel(level int) OpFunc- Set zstd compression levelWithZstdDictionary(dict []byte) OpFunc- Set pre-trained zstd dictionaryWithZstdPooling(enable bool) OpFunc- Enable/disable zstd encoder/decoder poolingWithTimeout(d time.Duration) OpFunc- Set overall request timeoutWithInsecure() OpFunc- Disable TLS certificate verificationWithDisableCompression() OpFunc- Disable response compressionWithMaxIdleConns(n int) OpFunc- Set maximum number of idle connectionsWithIdleConnTimeout(d time.Duration) OpFunc- Set idle connection timeoutWithTenant(tenant string) OpFunc- Set tenant ID headerWithUserID(userID string) OpFunc- Set user ID headerWithOperator(operator string) OpFunc- Deprecated alias for WithUserIDWithTracePropagationFormats(formats ...string) OpFunc- Set trace propagation formatsWithB3TracePropagation() OpFunc- Enable B3 single header propagation formatWithCompositeTracePropagation() OpFunc- Enable both W3C and B3 propagation formats
SetHeader(key, value string) *RequestSetQueryParam(key, value string) *RequestSetJSONBody(v interface{}) *RequestSetFormBody(v interface{}) *RequestSetRawBody(body io.Reader, contentType string) *RequestSetBearerToken(token string) *RequestSetBasicAuth(username, password string) *RequestSetTenant(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 SetUserIDFromContext(ctx context.Context) *RequestSetRetry(maxRetries int) *Request- Enable retry for this requestSetRetryConfig(maxRetries int, interval time.Duration, backoffFactor float64, jitter float64) *Request- Set full retry configurationSetRetryableStatuses(statuses ...int) *Request- Set custom retryable status codesEnableZstdCompression() *Request- Enable zstd compression for request bodyEnableZstdCompressionWithLevel(level int) *Request- Enable zstd compression with custom levelSetZstdCompressionLevel(level int) *Request- Set zstd compression levelSetZstdDictionary(dict []byte) *Request- Set pre-trained zstd dictionary for this requestSetTracePropagationFormats(formats ...string) *Request- Set trace propagation formats for this requestEnableB3TracePropagation() *Request- Enable B3 single header propagation formatEnableCompositeTracePropagation() *Request- Enable both W3C and B3 propagation formats
resp.StatusCode() intresp.IsSuccess() boolresp.IsError() boolresp.Bytes() ([]byte, error)resp.String() (string, error)resp.JSON(v interface{}) errorresp.Save(w io.Writer) (int64, error)
MIT License