autumn-plugin-pingora
A Pingora reverse proxy for Autumn, in one line.
The proxy has its own port. It sends matched requests to upstream pools. It sends all other requests to your Autumn app.
use ;
async
Run the example: cargo run --example gateway.
Features
- Routes by host (
api.example.com,*.example.com) and path prefix (on a segment boundary). The host decides first, as in nginx. Then the longest prefix wins. - Fallback to the Autumn app, or 404.
- Round-robin or consistent-hash (client IP) load balancing.
- TCP health checks. No healthy upstream gives 503.
- Connect retries on another upstream. A failed connect gives 502. An upstream timeout gives 504.
X-Forwarded-For,-Protoand-Host. The proxy replaces client values and removes other identity headers (X-Real-IP, …), except from peers intrusted_proxies.- Paths with dot segments (also encoded,
..;,..%2f) or backslashes get 400. - Body limit (413), connection limit, per-IP connection limit.
- A
pingoraindicator in/actuator/health(readiness group). pingora_proxy_*metrics in/actuator/prometheus.- Graceful drain on Autumn's shutdown signal.
autumn routeslists the proxied prefixes (methodPROXY).
Configuration
[pingora] in autumn.toml. Profiles ([profile.<name>.pingora],
autumn-<name>.toml) and AUTUMN_PINGORA__<KEY> env variables apply on
top. autumn 0.9 turns on server.strict_config in prod, and a
[profile.prod.pingora] table then stops the boot. Put prod values in
autumn-prod.toml. Code (configure, route, bind) applies last. A bad value stops
boot.
[]
= true
= "" # "": 127.0.0.1:8080 in dev/test, 0.0.0.0:8080 else
= "app" # "app" or "none" (404)
= [] # peers whose X-Forwarded-* values stay
= 5000
= 60000 # upstream and client body; 0: off
= 60000 # upstream and client response; 0: off
= 1 # connect retries on another upstream
= 5000 # 0: off
= 1000
= 10485760 # 0: off
= 10000 # 0: off; at most 1000000
= 0 # 0: off; keep 0 behind a load balancer
= 10000 # capped to fit server.shutdown_timeout_secs
= true
[[]]
= "billing" # unique; used in metrics
= "" # "" (any), "api.example.com" or "*.example.com"
= "/billing"
= ["10.0.0.7:8080", "billing.internal:8080"]
= true
= "" # "": keep the client Host
= "round_robin" # or "consistent"
AUTUMN_PINGORA__ROUTES replaces all routes with a TOML array:
AUTUMN_PINGORA__ROUTES='[{ name = "api", path_prefix = "/api", upstreams = ["10.0.0.9:80"] }]'
Metrics
| Name | Type | Labels |
|---|---|---|
pingora_proxy_up |
gauge | — |
pingora_proxy_connections_active |
gauge | — |
pingora_proxy_upstreams |
gauge | route (one per resolved address) |
pingora_proxy_upstreams_healthy |
gauge | route |
pingora_proxy_requests_total |
counter | route, status (2xx, …, error) |
pingora_proxy_upstream_errors_total |
counter | route |
route is a route name, fallback (the Autumn app) or unmatched (no
route, or a path that the proxy rejects). Pingora rejects malformed
requests before routing. They are not counted.
Behind the proxy
With fallback = "app", the app sees the proxy (127.0.0.1) as the
client. Tell Autumn to trust it:
[]
= true
= ["127.0.0.1/32"]
The plugin warns at boot when this is missing. Do not confuse this with
[pingora] trusted_proxies: that list controls which peers the proxy
itself trusts.
Limits of v0.1
- No TLS. Put the proxy behind a TLS terminator, or on a private network.
fallback = "app"with[server.tls]stops boot. - HTTP/1.1 only on the client side. No h2c.
- No cache, rate limits or custom filters.
- Hostnames resolve once, at boot.
- Slow clients: Pingora waits up to 60 s for each read of the request
header. Use
max_connections_per_ip, or a load balancer with its own limits, against slowloris clients. - Linux and macOS only (Pingora).
Design
See the plan (brainstorming, reverse brainstorming, six hats, AC) and the ADRs.
License
Apache-2.0.