Skip to content

Pragmatic.Gateway

YARP-based API gateway for Pragmatic.Design applications, with Agent-driven routing, maintenance mode, tenant-aware proxying, and per-cluster resilience.

Status: implemented preview subsystem. The gateway is already functional inside the repo. It is still evolving operationally, but it is no longer just a scaffold or design placeholder.

  • Reverse proxy based on YARP
  • Dynamic routes and clusters loaded from Pragmatic.Agent
  • Static route fallback from configuration
  • Maintenance middleware and graceful drain behavior
  • Tenant-aware request routing
  • JWT authentication, CORS, response compression, and rate limiting
  • Per-cluster timeout and circuit-breaker policies via Pragmatic.Resilience
{
"Gateway": {
"HttpUrl": "http://*:8080",
"AgentSocketPath": "/var/run/pragmatic/agent.sock",
"Routes": [
{
"RouteId": "booking",
"Path": "/booking/{**catch-all}",
"Backend": "http://localhost:5010"
}
]
}
}
Terminal window
dotnet run --project Pragmatic.Gateway/src/Pragmatic.Gateway/Pragmatic.Gateway.csproj

Useful overrides:

Terminal window
dotnet run --project Pragmatic.Gateway/src/Pragmatic.Gateway/Pragmatic.Gateway.csproj -- --listen http://*:8081 --agent-socket /tmp/pragmatic/agent.sock
{
"Gateway": {
"Resilience": {
"Default": {
"Timeout": "00:00:10",
"FailureThreshold": 5,
"BreakDuration": "00:00:30"
},
"Clusters": {
"booking": { "Timeout": "00:00:05", "FailureThreshold": 3 }
}
}
}
}

Resilience is per-cluster: Default applies to every backend, and Clusters overrides individual clusters by YARP cluster ID. Each policy maps to ClusterResiliencePolicy (Timeout, CircuitBreakerEnabled, FailureThreshold, BreakDuration, FailureStatusCodeMin/Max).

ProxyResilienceMiddleware runs inside the YARP proxy pipeline and maintains a circuit per cluster (state in ICircuitBreakerStateStore, in-memory by default):

  • Closed — requests flow through. A response whose status falls in the failure range (default 500–599), a timeout, or a backend connection error is counted as a failure. Any non-failure response resets the failure count. Once consecutive failures reach FailureThreshold, the circuit transitions to Open.
  • Open — requests are rejected immediately (fast-fail) with 503 Service Unavailable, a Retry-After header, and a JSON body {"error":"circuit_open",...}. The backend is not contacted. The circuit stays open for BreakDuration.
  • Half-open — after BreakDuration elapses, the next request is allowed through as a probe. If it succeeds the circuit returns to Closed; if it fails the circuit re-opens for another BreakDuration.

Timeouts are enforced per request via a linked cancellation token; a timed-out request returns 504 Gateway Timeout and counts as a failure. Backend connection errors return 502 Bad Gateway. Set CircuitBreakerEnabled = false on a policy to keep timeouts but disable the breaker, or Enabled = false at the root to bypass resilience entirely.

The gateway can run with only static routes, but it becomes much more useful when connected to the Agent:

  • route and cluster definitions can be reloaded from Agent KV state
  • maintenance state can be coordinated instead of being purely local
  • gateway configuration can participate in the broader Pragmatic operational model

If the Agent is unavailable, the gateway continues with its configured static routes.

Set Gateway:Jwt to enable JWT bearer authentication. The gateway always validates the token signature; issuer and audience are validated when configured (a startup warning is logged if either is absent, since leaving them open allows token reuse/forgery across issuers). A SigningKey enables symmetric validation; a JwksUrl enables JWKS-based validation.

Gateway:ApiKey binds to ApiKeyOptions (HeaderName, default X-Api-Key; and ValidKeys):

{
"Gateway": {
"ApiKey": {
"HeaderName": "X-Api-Key",
"ValidKeys": ["${API_KEY_1}", "${API_KEY_2}"]
}
}
}

Load ValidKeys from a secrets manager or environment variables — never commit raw keys to appsettings.json.

APPROFONDIRE: The ApiKey section is defined and bound into GatewayOptions, but the current gateway host (Program.cs) does not yet wire up an API-key validation middleware — only JWT authentication is enforced today. Configuring ApiKey has no runtime effect until that middleware is added. Treat this section as forward-looking configuration.

Set Gateway:Cors to enable a default CORS policy (CorsOptions):

{
"Gateway": {
"Cors": {
"Origins": ["https://app.example.com"],
"Methods": ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
"Headers": ["*"],
"AllowCredentials": true
}
}
}

Origins: ["*"] allows any origin only when AllowCredentials is false. Combining a wildcard origin with AllowCredentials: true is invalid (the browser refuses it), so the gateway fails fast at startup with an actionable message instead of an opaque ASP.NET error.

Set Gateway:RateLimit to enable a global fixed-window limiter (RateLimitOptions). Requests over the limit get 429 Too Many Requests:

{
"Gateway": {
"RateLimit": {
"PermitLimit": 1000,
"Window": "00:01:00",
"KeyStrategy": "ip"
}
}
}

PermitLimit requests are allowed per Window; KeyStrategy: "ip" partitions the limit by client IP.

TenantRoutingMiddleware runs after authentication and before the proxy. It resolves the tenant from (1) the authenticated tenant_id claim, then (2) the request subdomain (acme.myapp.comacme, excluding reserved infrastructure subdomains such as www, api, admin), and forwards it to backends as the X-Tenant-Id header.

Crucially, the middleware always strips any client-supplied X-Tenant-Id header first, before resolution. The gateway is the authoritative source of this header so downstream services can trust it. If the client value were honored, a user authenticated for tenant A could read or write tenant B’s data simply by setting X-Tenant-Id: B — a cross-tenant isolation breach. Only the gateway-computed value (from the signed claim or DNS-controlled subdomain) is ever forwarded.

TypePurpose
GatewayOptionsFull runtime configuration for listen URLs, auth, routes, maintenance, and resilience
AgentRouteProviderYARP config provider backed by Agent KV plus static fallback
MaintenanceMiddlewareStops normal request flow when maintenance is active
TenantRoutingMiddlewareApplies tenant-aware routing decisions before proxying
ProxyResilienceMiddlewareTimeout and circuit-breaker protection per backend cluster

Implemented today:

  • standalone gateway executable
  • Agent-backed and static route loading
  • maintenance mode
  • health endpoint
  • response compression
  • JWT auth and CORS
  • global rate limiting
  • per-cluster resilience

Still evolving:

  • broader deployment UX and packaging guidance
  • deeper control-plane tooling around route authoring and rollout

Local docs:

Part of the Pragmatic.Design ecosystem — see Licensing. Pragmatic.Gateway is licensed under the PolyForm Small Business 1.0.0 license (free for small businesses; commercial license above the threshold).