Built-in reverse proxy with 5 load balancing strategies, health checks, connection pooling, response caching, and request interception hooks.
Add a proxy domain to forward traffic from a hostname to one or more backends:
s := core.New(core.Config{ Addr: ":443", }) s.AddProxyDomain(core.DomainConfig{ Domain: "api.example.com", Backends: []core.BackendConfig{ {Addr: "http://localhost:3001"}, {Addr: "http://localhost:3002"}, }, }) s.Router.Build() s.ListenAndServeTLS()
Full control over each proxied domain with timeouts, retries, and connection limits:
s.AddProxyDomain(core.DomainConfig{ Domain: "api.example.com", Backends: []core.BackendConfig{ {Addr: "https://backend1.local", Weight: 3}, {Addr: "https://backend2.local", Weight: 1}, }, LoadBalancer: core.LBWeightedRR, ConnectTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 10 * time.Second, MaxRetries: 2, MaxIdleConns: 32, IdleTimeout: 90 * time.Second, PreserveHost: true, HostHeader: "backend.internal", HealthCheck: core.HealthCheckConfig{ Enabled: true, Path: "/health", Interval: 10 * time.Second, Timeout: 5 * time.Second, FailThreshold: 3, SuccessThreshold: 2, }, })
| Option | Default | Description |
|---|---|---|
Domain |
— | Hostname to match incoming requests |
Backends |
— | List of backend servers with optional weights |
LoadBalancer |
Round Robin | Load balancing strategy |
ConnectTimeout |
10s | Timeout to establish backend connection |
ReadTimeout |
30s | Timeout for reading backend response |
WriteTimeout |
10s | Timeout for writing request to backend |
MaxRetries |
2 | Retries on backend failure |
MaxIdleConns |
32 | Connection pool size per backend |
IdleTimeout |
90s | Max idle time for pooled connections |
PreserveHost |
false | Forward original Host header |
HostHeader |
"" | Override Host header to backend |
Choose from 5 built-in load balancing algorithms:
Simple round-robin rotation across all healthy backends. Default strategy.
Weighted round-robin. Higher weight = more traffic. Weights 1–100.
Sends to the backend with fewest active connections.
Hash client IP for sticky sessions. Same client always hits the same backend.
Random selection from healthy backends.
s.AddProxyDomain(core.DomainConfig{ Domain: "app.example.com", Backends: []core.BackendConfig{ {Addr: "http://10.0.0.1:8080", Weight: 5}, {Addr: "http://10.0.0.2:8080", Weight: 3}, {Addr: "http://10.0.0.3:8080", Weight: 1}, }, LoadBalancer: core.LBWeightedRR, })
Automatic backend health monitoring with configurable thresholds:
HealthCheck: core.HealthCheckConfig{
Enabled: true,
Path: "/health",
Interval: 10 * time.Second,
Timeout: 5 * time.Second,
FailThreshold: 3,
SuccessThreshold: 2,
}
Two check modes:
Cache backend responses in memory with automatic gzip pre-compression:
s.SetProxyCache(core.ProxyCacheConfig{ DefaultMaxAge: 5 * time.Minute, MaxTotalBytes: 256 * 1024 * 1024, MaxEntries: 10000, MaxEntrySize: 4 * 1024 * 1024, PreCompress: true, CompressLevel: 6, CompressMinLen: 256, StaleWhileRev: 30 * time.Second, })
| Option | Default | Description |
|---|---|---|
DefaultMaxAge |
5min | Default TTL for cached entries |
MaxTotalBytes |
256MB | Total cache memory budget |
MaxEntries |
10,000 | Maximum cached entries |
MaxEntrySize |
4MB | Skip entries larger than this |
PreCompress |
false | Store pre-compressed gzip copies |
StaleWhileRev |
0 | Serve stale content while refreshing |
Define per-path caching rules for fine-grained control:
s.SetProxyCache(core.ProxyCacheConfig{ Rules: []core.CacheRule{ { PathPrefix: "/api/v1/", MaxAge: 10 * time.Minute, Methods: []string{"GET"}, StatusOnly: []int{200}, }, { PathPrefix: "/static/", MaxAge: 24 * time.Hour, MaxBytes: 10 * 1024 * 1024, }, }, })
s.OnProxyResponse(func(pr *core.ProxyResponse) { if pr.StatusCode == 200 { pr.CacheThis(5*time.Minute, 100, core.WithPreCompress(true), core.WithCompressMinLen(1024), ) } })
Intercept proxy requests and responses for logging, modification, or blocking:
Modify or block requests before forwarding. Return false to reject the request
with 502:
s.OnProxyRequest(func(pr *core.ProxyRequest) bool { pr.Headers = append(pr.Headers, [2]string{"X-Internal-Auth", "secret"}) return true })
Modify response headers, trigger caching, or prevent caching:
s.OnProxyResponse(func(pr *core.ProxyResponse) { pr.Headers = append(pr.Headers, [2]string{"X-Proxy", "ALOS"}) if pr.StatusCode == 200 { pr.CacheThis(5*time.Minute, 1000) } })
Prevent a specific response from being cached, even if it matches cache rules:
s.OnProxyResponse(func(pr *core.ProxyResponse) { if pr.Path == "/api/user/me" { pr.DontCache() return } pr.CacheThis(10*time.Minute, 500) })
s.OnProxyError(func(pe core.ProxyError) { log.Printf("proxy: %s → %s attempt %d: %v", pe.Domain, pe.Backend, pe.Attempt, pe.Err) })
| Field | Type | Description |
|---|---|---|
Domain |
string |
Matched domain |
Backend |
string |
Selected backend address |
ClientAddr |
string |
Client IP |
Method |
string |
HTTP method |
Path |
string |
Request path |
Headers |
[][2]string |
Mutable request headers |
Host |
string |
Mutable host header |
| Field | Type | Description |
|---|---|---|
Domain |
string |
Matched domain |
Backend |
string |
Backend that responded |
StatusCode |
int |
Mutable status code |
Headers |
[][2]string |
Mutable response headers |
Method |
string |
Original request method |
Path |
string |
Original request path |
ALOS maintains a connection pool per backend to avoid TCP handshake overhead:
DomainConfig{
MaxIdleConns: 64,
IdleTimeout: 120 * time.Second,
}
Programmatically clear cached responses:
| Method | Description |
|---|---|
PurgeProxyCache(method, host, path string) bool |
Remove a specific cached response. Returns true if found. |
PurgeAllProxyCache() |
Clear the entire proxy cache |
PurgeDomainCache(domain string) int64 |
Clear all cached entries for a domain. Returns count purged. |
ProxyCacheStats() (entries, totalBytes int64, hits, misses uint64)
|
Get cache statistics |
s.PurgeProxyCache("GET", "api.example.com", "/api/v1/users") s.PurgeDomainCache("api.example.com") s.PurgeAllProxyCache() entries, totalBytes, hits, misses := s.ProxyCacheStats() fmt.Printf("Cache: %d entries, %d bytes, %d hits, %d misses\n", entries, totalBytes, hits, misses)
Expose a cache management endpoint:
s.Router.DELETE("/admin/cache/:domain", func(req *core.Request, resp *core.Response) { domain := req.ParamValue("domain") count := s.PurgeDomainCache(domain) resp.JSONMarshal(map[string]any{ "domain": domain, "purged": count, }) })
Route traffic to different backends based on path prefix (useful for microservices):
s.SetHTTPRoutes([]core.HTTPRoute{ { PathPrefix: "/api/", Backend: "http://api-service:3000", }, { PathPrefix: "/auth/", Backend: "http://auth-service:4000", HostHeader: "auth.internal", }, { PathPrefix: "/", Backend: "http://frontend:8080", }, })