Features
Gateway extension capabilities
Multi-Protocol Proxying
The gateway transparently proxies traffic across four protocols using a single entry point. Protocol detection is automatic based on request headers:
| Protocol | Detection | Implementation |
|---|---|---|
| HTTP | Default | httputil.ReverseProxy with custom Director |
| WebSocket | Upgrade: websocket header | gobwas/ws bidirectional frame copying |
| SSE | Accept: text/event-stream header | Streaming proxy with http.Flusher |
| gRPC | Content-Type: application/grpc | HTTP/2 transport with trailer propagation |
For each request, the proxy engine:
- Checks global rate limits.
- Matches the request to a route via
RouteManager. - Checks per-route rate limits.
- Authenticates the request (if route auth is configured).
- Checks the response cache (if route caching is enabled).
- Runs
OnRequesthooks. - Selects a target via the load balancer.
- Checks the circuit breaker for the selected target.
- Dispatches to the appropriate protocol handler.
Route Management
Routes map incoming request paths to upstream targets with full lifecycle management:
ext := app.Container().MustResolve("gateway").(*gateway.Extension)
rm := ext.RouteManager()
// Add a route programmatically
rm.AddRoute(&gateway.Route{
ID: "users-api",
Path: "/api/users",
Methods: []string{"GET", "POST", "PUT", "DELETE"},
Targets: []*gateway.Target{
{URL: "http://users-svc:8080", Weight: 1},
{URL: "http://users-svc:8081", Weight: 1},
},
Protocol: gateway.ProtocolHTTP,
Source: gateway.SourceManual,
StripPrefix: true,
Enabled: true,
})
// Update an existing route
rm.UpdateRoute("users-api", updatedRoute)
// Remove a route
rm.RemoveRoute("users-api")
// Enable/disable routes
rm.EnableRoute("users-api")
rm.DisableRoute("users-api")Routes can be created from three sources:
manual-- configured statically in YAML/code or via the admin REST API.farp-- auto-generated from FARP service discovery schemas (OpenAPI, AsyncAPI, GraphQL).discovery-- auto-generated from service discovery backends.
FARP Auto-Discovery
The gateway's most powerful feature is automatic route generation from FARP-enabled services. When
the discovery extension is registered alongside the gateway, all wiring happens automatically through
Forge's DI container.
How It Works
- The gateway resolves
*discovery.Servicefrom the DI container on startup. - It periodically polls (or watches) the discovery backend for services.
- For each service, it checks the metadata for
farp.enabled=true. - If FARP is enabled, it reads
farp.openapi,farp.asyncapi, andfarp.graphqlfrom the metadata. - It generates routes based on the schema types:
- OpenAPI endpoints become HTTP proxy routes.
- AsyncAPI endpoints become WebSocket proxy routes.
- GraphQL endpoints become GraphQL proxy routes.
- Routes are prefixed with the service name (e.g.,
/user-service/*) ifAutoPrefixis enabled.
Example: Gateway with mDNS Discovery
package main
import (
"context"
"github.com/xraph/forge"
"github.com/xraph/forge/extensions/discovery"
"github.com/xraph/forge/extensions/gateway"
)
func main() {
app := forge.NewApp(forge.AppConfig{Name: "api-gateway", Version: "1.0.0"})
// Discovery extension: finds services via mDNS.
app.RegisterExtension(discovery.NewExtension(
discovery.WithEnabled(true),
discovery.WithBackend("mdns"),
discovery.WithServiceName("api-gateway"),
))
// Gateway extension: auto-discovers FARP services and creates routes.
app.RegisterExtension(gateway.NewExtension(
gateway.WithEnabled(true),
gateway.WithDiscoveryEnabled(true),
gateway.WithDiscoveryWatchMode(true),
gateway.WithDiscoveryAutoPrefix(true),
gateway.WithDiscoveryPrefixTemplate("/{{.ServiceName}}"),
gateway.WithDiscoveryStripPrefix(true),
))
ctx := context.Background()
app.Start(ctx)
defer app.Stop(ctx)
}If a user-service on the network has FARP enabled with an OpenAPI endpoint, the gateway
automatically creates a route at /user-service/* that proxies to the service.
Service Filters
Filter which discovered services are proxied by name, tags, or metadata:
gateway.NewExtension(
gateway.WithDiscoveryEnabled(true),
gateway.WithDiscoveryServiceFilters(
gateway.ServiceFilter{
IncludeTags: []string{"api"}, // Only proxy services tagged "api"
ExcludeNames: []string{"internal-*"}, // Skip internal services (wildcard)
RequireMetadata: map[string]string{ // Require specific metadata
"farp.enabled": "true",
},
},
),
)| Filter Field | Description |
|---|---|
IncludeNames | Only include services matching these names (supports wildcards) |
ExcludeNames | Exclude services matching these names (supports wildcards) |
IncludeTags | Only include services with all of these tags |
ExcludeTags | Exclude services with any of these tags |
RequireMetadata | Require specific key-value pairs in service metadata |
FARP Route Sources
Routes generated from FARP are tagged with SourceFARP, while routes generated from generic
discovery (without FARP metadata) are tagged with SourceDiscovery. This lets you filter and
manage routes by their origin:
rm := ext.RouteManager()
routes := rm.ListRoutes()
for _, route := range routes {
switch route.Source {
case gateway.SourceFARP:
// Auto-generated from FARP manifest.
case gateway.SourceDiscovery:
// Auto-generated from discovery backend (no FARP).
case gateway.SourceManual:
// Configured manually via code, YAML, or admin API.
}
}Load Balancing
Five load balancing strategies distribute traffic across upstream targets:
| Strategy | Behavior |
|---|---|
roundRobin | Cycles through targets in order |
weightedRoundRobin | Respects target Weight values |
random | Random target selection |
leastConnections | Routes to the target with fewest active connections |
consistentHash | Hashes a configurable header for sticky routing |
gateway.NewExtension(
gateway.WithLoadBalancing(gateway.LoadBalancingConfig{
Strategy: gateway.StrategyLeastConnections,
ConsistentKey: "X-User-ID", // For consistentHash strategy
}),
)Each target tracks active connections, total requests, total errors, and latency percentiles (AvgLatencyMs, P99LatencyMs) for informed load balancing decisions.
Circuit Breakers
Per-target three-state circuit breakers prevent cascade failures:
Closed ──(failures exceed threshold)──→ Open ──(reset timeout)──→ Half-Open
↑ │
└──────────(requests succeed)────────────────────────────────────────┘gateway.NewExtension(
gateway.WithCircuitBreaker(gateway.CircuitBreakerConfig{
Enabled: true,
FailureThreshold: 5, // Open after 5 failures
FailureWindow: 30 * time.Second, // Within this window
ResetTimeout: 60 * time.Second, // Time before half-open
HalfOpenMax: 2, // Max requests in half-open state
}),
)The circuit breaker manager provides per-target state:
cbm := ext.CircuitBreakerManager()
cb := cbm.Get("target-id")
state := cb.State() // CircuitClosed, CircuitOpen, CircuitHalfOpenState change hooks notify your application when circuit breakers trip:
ext.Hooks().OnCircuitBreak(func(targetID string, from, to gateway.CircuitState) {
log.Printf("circuit breaker: %s changed from %s to %s", targetID, from, to)
})Health Monitoring
Active and passive health monitoring for upstream targets:
Active probes send HTTP requests to a configurable health endpoint at regular intervals:
gateway.NewExtension(
gateway.WithHealthCheck(gateway.HealthCheckConfig{
Enabled: true,
Interval: 10 * time.Second,
Timeout: 5 * time.Second,
Path: "/health",
FailureThreshold: 3, // Mark unhealthy after 3 failures
SuccessThreshold: 2, // Mark healthy after 2 successes
EnablePassive: true,
PassiveFailThreshold: 5,
}),
)Passive monitoring tracks proxy errors as implicit health signals. Targets with too many consecutive errors are automatically marked unhealthy.
Health change events are available via hooks:
ext.Hooks().OnUpstreamHealth(func(event gateway.UpstreamHealthEvent) {
log.Printf("target %s: healthy=%v", event.TargetID, event.Healthy)
})Rate Limiting
Token-bucket rate limiting at three levels:
// Global rate limit
gateway.WithRateLimit(gateway.RateLimitConfig{
Enabled: true,
RequestsPerSec: 1000,
Burst: 100,
})
// Per-route rate limit (in RouteConfig)
route.RateLimit = &gateway.RateLimitConfig{
Enabled: true,
RequestsPerSec: 50,
Burst: 10,
PerClient: true, // Per-IP limiting
KeyHeader: "X-API-Key", // Or per-API-key
}When rate limited, the gateway returns 429 Too Many Requests with Retry-After header.
Retry with Backoff
Configurable retry policies with three backoff strategies:
route.Retry = &gateway.RetryConfig{
Enabled: true,
MaxAttempts: 3,
Backoff: gateway.BackoffExponential,
InitialDelay: 100 * time.Millisecond,
MaxDelay: 5 * time.Second,
Multiplier: 2.0,
Jitter: true,
RetryableStatus: []int{502, 503, 504},
RetryableMethods: []string{"GET", "HEAD"},
BudgetPercent: 20, // Max 20% of requests can be retries
}| Strategy | Behavior |
|---|---|
exponential | Delay doubles each attempt (with optional jitter) |
linear | Delay increases by InitialDelay each attempt |
fixed | Constant delay between attempts |
Traffic Splitting
Advanced traffic routing for canary deployments, A/B testing, blue-green deployments, and shadow traffic:
route.TrafficPolicy = &gateway.TrafficPolicy{
Type: gateway.TrafficCanary,
Rules: []gateway.TrafficRule{
{
Match: gateway.TrafficMatch{
Type: gateway.MatchHeader,
Key: "X-Canary",
Value: "true",
},
Weight: 100,
Targets: []string{"canary-target-id"},
},
},
}| Split Type | Use Case |
|---|---|
canary | Route matching requests to canary targets |
blueGreen | Switch all traffic between two target sets |
abTest | Split traffic based on headers/cookies |
mirror | Copy traffic to shadow targets (fire-and-forget) |
weighted | Percentage-based traffic distribution |
Authentication
The gateway integrates with the Forge auth extension and provides built-in auth providers:
// Register built-in providers
ext.Auth().RegisterProvider(gateway.NewAPIKeyAuthProvider(
"apikey", "X-API-Key", "api_key",
[]*gateway.APIKeyEntry{
{Key: "prod-key-123", Subject: "service-a", Scopes: []string{"read", "write"}},
},
))
ext.Auth().RegisterProvider(gateway.NewBearerTokenAuthProvider(
"jwt",
func(ctx context.Context, token string) (*gateway.GatewayAuthContext, error) {
// Validate JWT and return context
return &gateway.GatewayAuthContext{
Subject: "user-123",
Scopes: []string{"admin"},
}, nil
},
))
// Per-route auth configuration
route.Auth = &gateway.RouteAuthConfig{
Enabled: true,
Providers: []string{"jwt", "apikey"},
Scopes: []string{"read"},
}Authenticated requests get forwarded auth headers (X-Auth-Subject, X-Auth-Provider, X-Auth-Scopes) to upstream services.
Request Transformation
Modify requests and responses as they pass through the gateway:
- Path rewriting --
StripPrefixremoves the matched prefix;AddPrefixprepends a new prefix;RewritePathreplaces the path entirely. - Header manipulation -- Add, set, or remove headers on requests and responses via
HeaderPolicyandTransformConfig.
route := &gateway.Route{
Path: "/v2/api/*",
StripPrefix: true,
AddPrefix: "/internal",
Headers: gateway.HeaderPolicy{
Add: map[string]string{"X-Gateway": "forge"},
Set: map[string]string{"X-Version": "2"},
Remove: []string{"X-Internal-Only"},
},
}Hook System
Extensible lifecycle hooks for custom logic at every stage:
hooks := ext.Hooks()
// Before request is proxied
hooks.OnRequest(func(r *http.Request, route *gateway.Route) error {
r.Header.Set("X-Request-ID", uuid.New().String())
return nil // return error to abort the request
})
// After response is received
hooks.OnResponse(func(resp *http.Response, route *gateway.Route) {
log.Printf("response: %d for %s", resp.StatusCode, route.Path)
})
// On proxy error
hooks.OnError(func(err error, route *gateway.Route, w http.ResponseWriter) {
log.Printf("proxy error for %s: %v", route.Path, err)
})
// On route changes (add, update, delete)
hooks.OnRouteChange(func(event gateway.RouteEvent) {
log.Printf("route %s: %s", event.Action, event.Route.Path)
})Response Caching
In-memory response caching with per-route policies:
route.Cache = &gateway.RouteCacheConfig{
Enabled: true,
TTL: 5 * time.Minute,
Methods: []string{"GET"},
}The cache respects Cache-Control headers and supports custom cache key generation.
OpenAPI Aggregation
The gateway aggregates OpenAPI specifications from all upstream services into a unified specification, served at the gateway with an optional Swagger UI.
aggregator := ext.OpenAPI()
// OpenAPI spec is available at the gateway dashboardAdmin Dashboard and REST API
The gateway serves a ForgeUI-based dashboard with real-time WebSocket updates showing routes, upstreams, stats, and discovered services.
Admin REST API (default prefix: /gateway/api):
| Endpoint | Description |
|---|---|
GET /api/routes | List all routes (filter by source, protocol) |
POST /api/routes | Create a manual route |
PUT /api/routes/:id | Update a route |
DELETE /api/routes/:id | Delete a route |
POST /api/routes/:id/enable | Enable a route |
POST /api/routes/:id/disable | Disable a route |
GET /api/upstreams | List targets with health status |
GET /api/stats | Gateway statistics |
GET /api/stats/routes | Per-route statistics |
GET /api/config | Current configuration (sanitized) |
GET /api/discovery/services | Discovered services |
POST /api/discovery/refresh | Trigger discovery refresh |
Observability
- Prometheus metrics -- request count, latency, error rate, circuit breaker state, upstream health.
- Structured access logging -- method, path, status, latency, target, protocol.
- OpenTelemetry trace propagation -- forwards trace headers to upstream services.
TLS and Mutual TLS
Configure TLS for upstream connections with optional client certificates:
target.TLS = &gateway.TargetTLSConfig{
CACert: "/path/to/ca.pem",
ClientCert: "/path/to/client.pem",
ClientKey: "/path/to/client-key.pem",
SkipVerify: false,
}How is this guide?