Delegating a decision to a service
GuideOperationsSince version 1.2.0
Operations guide: configure, deploy and observe the gateway. What the capability does, product side: product guide; calling it: developer guide.
Goal: let a service of one's own system decide on a request (a risk score, a business right) or enrich it with a header - without a plugin in the gateway.
Command - the service is declared as an upstream, the policy names it:
upstreams:
risk: { url: http://risk.internal:8080 } # pool, bulkhead, circuit breaker: its own
policies:
definitions:
risk-check:
type: external-call
phase: request # request: before the upstream; response: after its answer
config:
upstream: risk # a declared upstream, never a URL
path: /score
timeout: 150ms # required, at most the operation's budget
on_failure: fail-closed # required: fail-open | fail-closed
send: { headers: [X-Tenant], body: true }
deny: { when: "response.status == 403", status: 403, code: risk_refused }
set_headers: { X-Risk-Score: "response.body.score" }Check: a POST /orders the service refuses gets 403 application/problem+json
(code: risk_refused) without the upstream seeing it; accepted, the upstream receives X-Risk-Score;
flexway_policy_external_calls_total{policy="risk-check"} counts each call by outcome (ok,
denied, failed_open, failed_closed); the trace shows an external-call risk-check span under
the request's.
| Rule | What it means |
|---|---|
| Declared service | upstream names an upstream of upstreams (HTTP, without token_exchange), path a literal path: no URL built from the request. The call is a POST |
| What is sent | The headers send.headers names, taken from the request prepared for the upstream (declared ones, the tenant the gateway wrote); never Authorization nor a protected header. send.body: the request body, read within server.limits.max_body |
| What it decides | deny (when on the answer, a 4xx status, a declared code) or set_headers (CEL expressions on response.status and response.body, the service's answer) - nothing else |
| Failure | No answer in time, service unreachable, circuit open, a 5xx, an answer a condition cannot read: fail-open goes on without the policy, fail-closed refuses (504, 503, 502) - never a default |
| Budget | The call consumes the operation's budget (x-flexway-timeout): a slow service leaves the upstream less time, never more time in total |
| Circuit breaker | The service is an upstream: failing, its circuit opens (Flexway-Degraded: circuit-open; upstream=risk) and the gateway stops calling it |
| Refused at load | No timeout or no on_failure; a timeout past the budget; an undeclared upstream; a protected header in set_headers; send.body on a stream or in the response phase; gRPC, inference - the message names the operation and the policy |
| Simulation | /simulate never calls the service: the policy is not applied there |
Full example: External call.