Skip to main content

Routing & Flows

Routing​

gateway:
routing:
trusted_proxies:
- 127.0.0.1/32
- 10.0.0.0/8
rate_limiter:
enabled: true
config:
limit: 100
window: 1s
flows:
- ...
FieldTypeDefaultDescription
trusted_proxieslist[CIDR][]IP ranges whose X-Forwarded-* headers are trusted
rate_limiter.enabledboolfalseEnable per-IP rate limiting
rate_limiter.configmap-Rate limiter configuration (limit, window)

Trusted proxies: when a request arrives from an IP that is not in trusted_proxies, Aastro overwrites X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port, and Forwarded with values derived from the actual connection. When a request comes from a trusted IP, Aastro appends to the existing chain rather than overwriting - preserving the full proxy path. Leave this list empty if Aastro is your outermost edge.

Rate limiter: the limit is applied per client IP after trusted proxy resolution. The IP used for rate limiting is the same one extracted from X-Forwarded-For / X-Real-IP / RemoteAddr.

Flows​

A flow defines how an incoming request is matched, processed, and dispatched to upstreams.

flows:
- path: /api/v1/users/{user_id}
method: GET
aggregation:
strategy: merge
best_effort: true
on_conflict:
policy: prefer
prefer_upstream: users
plugins:
- ...
middlewares:
- ...
upstreams:
- ...
# Streaming flow - no aggregation
flows:
- path: /api/v1/events/{user_id}
method: GET
streaming: true
upstreams:
- ...
FieldTypeRequiredDefaultDescription
pathstringtrue-URL path to match. Supports {param} path parameters
methodstringtrue-HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
streamingboolfalsefalseEnable unbuffered streaming proxy mode. See Streaming
aggregationobjectif >1 upstream-Aggregation configuration. Only required for flows with more than one upstream - see below

A flow with exactly one upstream never reads aggregation, streaming or not - it is dispatched by its own proxy path instead of the aggregator. See Single-Upstream Flows for how its response differs from a multi-upstream flow's.

Aggregation​

FieldTypeRequiredDefaultDescription
aggregation.strategystringtrue-merge, array, or namespace
aggregation.best_effortboolfalsefalseReturn partial results when some upstreams fail
aggregation.on_conflict.policystringif mergeoverwriteKey collision policy: overwrite, first, error, prefer
aggregation.on_conflict.prefer_upstreamstringif prefer-Name of the upstream whose values win on collision

When best_effort is true and some (but not all) upstreams fail, the gateway returns HTTP 206 Partial Content with the aggregated data from the successful upstreams as the body - the same shape a full 200 would have - and the failed upstreams' error codes in the X-Partial-Errors response header, one value per failure. When false, a single upstream failure causes the entire request to fail: an RFC 9457 Problem Details document is returned instead, with no data at all. See Response Format for the full behavior, including the response headers and the exact status code chosen on failure.

Aggregation Strategies​

StrategyDescription
mergeMerges JSON objects from all upstreams into a single flat object. All upstreams must return a JSON object at the root level
arrayWraps each upstream response as an element in a JSON array, preserving order
namespacePlaces each upstream response under a key equal to the upstream name: {"users": {...}, "stats": {...}}

merge: merge requires all upstream responses to be JSON objects ({}). If any upstream returns a JSON array or primitive, it is treated as a malformed response. With best_effort: true such a response contributes an UPSTREAM_MALFORMED error but does not stop aggregation.

namespace: if an upstream returns a null body (empty response with no content), its key is written as null rather than omitted. This makes missing upstream data explicit rather than invisible.

Conflict Policies (merge only)​

PolicyDescription
overwriteThe last upstream to set a key wins
firstThe first upstream to set a key wins; later values are ignored
errorAny key collision immediately returns 409 Conflict with no data
preferThe value from prefer_upstream always wins on collision; order of other upstreams does not matter

Streaming​

When streaming: true, the flow proxies the request directly to a single upstream without reading the body into memory or aggregating the response. The response body is streamed chunk-by-chunk to the client.

  • Requires exactly one upstream - configuration validation rejects multiple upstreams
  • aggregation config is ignored and not required
  • Request-phase plugins still run before the upstream call
  • Response-phase plugins do not run - the body is already streaming by the time they would execute
  • Designed for Server-Sent Events (SSE), chunked transfer, and any long-lived HTTP connection

See Streaming & SSE for the full guide. A non-streaming flow with exactly one upstream is also proxied rather than aggregated, just buffered - see Single-Upstream Flows.