Bastion

Architecture

How the request pipeline, middleware chain, and extension lifecycle work in Bastion.

Bastion is organized around a middleware pipeline that processes every request through a series of well-defined stages. The gateway registers itself as a Forge extension and manages routes, upstreams, and resilience features through a unified engine.

Request pipeline

Every request entering the gateway flows through this pipeline. Each stage has a clear responsibility and can short-circuit the chain (for example, a rate limiter returning 429 or a circuit breaker returning 503).

Client Request


┌──────────────────────────┐
│  1. IP Filter            │  Allow/deny based on client IP
└──────────┬───────────────┘


┌──────────────────────────┐
│  2. CORS                 │  Handle preflight requests, set CORS headers
└──────────┬───────────────┘


┌──────────────────────────┐
│  3. Rate Limiter (global)│  Token-bucket check against global limit
└──────────┬───────────────┘


┌──────────────────────────┐
│  4. Route Matcher        │  Match request path to a configured route
└──────────┬───────────────┘


┌──────────────────────────┐
│  5. Rate Limiter (route) │  Per-route token-bucket check
└──────────┬───────────────┘


┌──────────────────────────┐
│  6. Authentication       │  API key, Bearer token, or forward-auth
└──────────┬───────────────┘


┌──────────────────────────┐
│  7. Response Cache (get) │  Check in-memory cache for a cached response
└──────────┬───────────────┘
           │ cache miss

┌──────────────────────────┐
│  8. Request Hooks        │  OnRequest callbacks (header injection, logging)
└──────────┬───────────────┘


┌──────────────────────────┐
│  9. Traffic Splitter     │  Canary, blue-green, A/B, shadow routing
└──────────┬───────────────┘


┌──────────────────────────┐
│ 10. Load Balancer        │  Select upstream target (round-robin, weighted, etc.)
└──────────┬───────────────┘


┌──────────────────────────┐
│ 11. Circuit Breaker      │  Check target circuit state (closed/open/half-open)
└──────────┬───────────────┘


┌──────────────────────────┐
│ 12. TLS / mTLS           │  Establish upstream TLS, present client certs
└──────────┬───────────────┘


┌──────────────────────────┐
│ 13. Protocol Proxy       │  HTTP, WebSocket, SSE, or gRPC proxy
│     + Path Rewriting     │  Strip prefix, rewrite path
│     + Header Manipulation│  Add/remove/modify headers
└──────────┬───────────────┘


┌──────────────────────────┐
│ 14. Upstream Connection  │  Send request, receive response
└──────────┬───────────────┘


┌──────────────────────────┐
│ 15. Response Cache (put) │  Store response in cache if policy allows
└──────────┬───────────────┘


┌──────────────────────────┐
│ 16. Response Hooks       │  OnResponse callbacks (header injection, logging)
└──────────┬───────────────┘


┌──────────────────────────┐
│ 17. Access Logger        │  Structured log entry with timing, status, route
└──────────┬───────────────┘


      Client Response

Short-circuit behavior

Several stages can terminate the pipeline early:

StageConditionResponse
IP FilterClient IP is denied403 Forbidden
Rate Limiter (global)Token bucket exhausted429 Too Many Requests
Route MatcherNo matching route404 Not Found
Rate Limiter (route)Per-route bucket exhausted429 Too Many Requests
AuthenticationInvalid or missing credentials401 Unauthorized
Response CacheCache hitCached response (skips upstream)
Circuit BreakerCircuit is open503 Service Unavailable

Middleware chain

The pipeline is implemented as a chain of HTTP middleware handlers. Each middleware wraps the next, forming a nested call stack. This design allows each stage to:

  1. Inspect and modify the request before passing it downstream
  2. Inspect and modify the response on the way back up
  3. Short-circuit by writing a response directly and not calling the next handler

The order of middleware is fixed and matches the pipeline diagram above. You cannot reorder stages, but you can disable individual stages via configuration (for example, disabling authentication or rate limiting).

Protocol handling

Bastion supports four protocols, each with dedicated proxy logic:

HTTP

Standard reverse proxy using Go's httputil.ReverseProxy with customized transport, error handling, and response modification. Supports HTTP/1.1 and HTTP/2.

WebSocket

WebSocket connections are detected via the Upgrade: websocket header. Bastion performs the WebSocket handshake with the upstream, then bidirectionally copies frames between client and upstream. The connection is maintained until either side closes it.

SSE (Server-Sent Events)

Server-Sent Events are proxied by establishing a long-lived HTTP connection to the upstream and streaming the response body back to the client with chunked transfer encoding and appropriate Content-Type: text/event-stream headers. Bastion disables response buffering for SSE routes.

gRPC

gRPC traffic is proxied at the HTTP/2 level. Bastion forwards gRPC frames (including trailers) between the client and upstream without interpreting the protobuf payload. Both unary and streaming RPCs are supported.

Protocol selection

The protocol is configured per-route via the Protocol field in RouteConfig:

bastion.WithRoute(bastion.RouteConfig{
    Path:     "/api/*",
    Protocol: bastion.ProtocolHTTP,      // HTTP proxying
})

bastion.WithRoute(bastion.RouteConfig{
    Path:     "/ws/*",
    Protocol: bastion.ProtocolWebSocket, // WebSocket proxying
})

bastion.WithRoute(bastion.RouteConfig{
    Path:     "/events/*",
    Protocol: bastion.ProtocolSSE,       // SSE proxying
})

bastion.WithRoute(bastion.RouteConfig{
    Path:     "/grpc/*",
    Protocol: bastion.ProtocolGRPC,      // gRPC proxying
})

For HTTP routes, Bastion also auto-detects WebSocket upgrade requests and handles them transparently.

Extension lifecycle

Bastion implements the Forge extension lifecycle with four phases:

┌─────────────────────┐
│  1. Register        │  Parse configuration (YAML or programmatic)
│                     │  Build route table from static routes
│                     │  Initialize middleware chain
│                     │  Mount HTTP handlers on the Forge router
│                     │  Provide gateway instance to DI container
└──────────┬──────────┘


┌─────────────────────┐
│  2. Start           │  Start health check probes
│                     │  Start FARP service discovery (if enabled)
│                     │  Start configuration file watcher (if enabled)
│                     │  Open WebSocket event stream (if dashboard enabled)
└──────────┬──────────┘


┌─────────────────────┐
│  3. Health          │  Report gateway health status
│                     │  Check upstream connectivity
│                     │  Report route and target counts
└──────────┬──────────┘


┌─────────────────────┐
│  4. Stop            │  Drain in-flight requests
│                     │  Stop health check probes
│                     │  Stop service discovery watcher
│                     │  Close WebSocket connections
│                     │  Release resources
└─────────────────────┘

Hook system

Bastion provides lifecycle hooks that let you extend gateway behavior without modifying the core pipeline. Hooks are registered on the gateway instance and called at specific points in the request lifecycle.

OnRequest

Called after route matching and authentication, before the request is forwarded to the upstream. Use this to inject headers, log requests, or modify the request.

gw.Hooks().OnRequest(func(r *http.Request, route *bastion.Route) error {
    r.Header.Set("X-Gateway-Auth", "validated")
    return nil
})

Returning a non-nil error from OnRequest aborts the request and triggers the OnError hook.

OnResponse

Called after the upstream responds, before the response is sent to the client. Use this to modify response headers or log responses.

gw.Hooks().OnResponse(func(resp *http.Response, route *bastion.Route) {
    resp.Header.Set("X-Served-By", "bastion")
})

OnError

Called when an error occurs during proxying (upstream timeout, connection refused, circuit breaker open, etc.). Use this for custom error logging or error page rendering.

gw.Hooks().OnError(func(err error, route *bastion.Route, w http.ResponseWriter) {
    log.Printf("Gateway error on route %s: %v", route.Path, err)
})

OnRouteChange

Called when a route is added, updated, or removed (via the admin API or service discovery).

gw.Hooks().OnRouteChange(func(event bastion.RouteChangeEvent) {
    log.Printf("Route %s: %s", event.Action, event.Route.Path)
})

OnUpstreamHealth

Called when an upstream target changes health status (healthy to unhealthy or vice versa).

gw.Hooks().OnUpstreamHealth(func(target *bastion.Target, healthy bool) {
    log.Printf("Target %s is now healthy=%v", target.URL, healthy)
})

Package structure

PackageImport pathPurpose
bastiongithub.com/xraph/bastionRoot -- extension constructor, configuration types, option functions
proxygithub.com/xraph/bastion/proxyProtocol-aware reverse proxy engine (HTTP, WS, SSE, gRPC)
resiliencegithub.com/xraph/bastion/resilienceCircuit breakers, retry logic, backoff strategies
healthgithub.com/xraph/bastion/healthActive health probes and passive failure tracking
discoverygithub.com/xraph/bastion/discoveryFARP service discovery integration and OpenAPI aggregation
securitygithub.com/xraph/bastion/securityAuthentication (API key, Bearer, forward-auth), IP filtering, CORS
apigithub.com/xraph/bastion/apiAdmin REST API handlers and WebSocket event stream
storegithub.com/xraph/bastion/storeRoute and configuration persistence backends
configgithub.com/xraph/bastion/configConfiguration parsing, validation, and hot-reload watcher

On this page