traffic-split
Steers matching requests to a weighted set of upstream targets, or lets them fall through to the route's normal upstream. A request is matched against an ordered list of rules; the first rule whose match passes selects a set of weighted_upstreams, and one weighted slot is chosen by weighted round-robin. This is the building block for canary, blue-green, and A/B rollouts. Place it before the route's normal upstream node.
Configuration
| Key | Type | Default | Description |
|---|---|---|---|
rules | array | — (required, non-empty) | Evaluated in order; the first rule whose match passes is used. |
rules[].match | array | absent = match all | A triple-array condition (rules AND-ed). Omit to match every request. |
rules[].weighted_upstreams | array | — (required, non-empty) | The weighted slots; one is chosen by weighted round-robin. |
rules[].weighted_upstreams[].upstream | object | absent = default slot | {targets: [{host, port}], ...} (nodes is accepted as an alias of targets, matching APISIX). When present, the slot proxies to one of the targets (round-robin within the set). When absent, the slot is the "default" — requests fall through to the route's normal upstream. |
rules[].weighted_upstreams[].weight | integer >= 0 | 1 | Selection weight. Every rule needs at least one slot with weight > 0. |
timeout_ms | integer | 60000 | Whole-call deadline for requests the plugin proxies itself. |
Config is validated at load: match expressions are compiled, weights must be non-negative integers, there must be at least one rule, and each rule must have at least one positively-weighted slot.
type: traffic-split
config:
timeout_ms: 60000
rules:
- match:
- ["arg_canary", "==", "1"]
weighted_upstreams:
# 90% fall through to the route's normal upstream
- weight: 90
# 10% proxied to the canary target set
- upstream:
targets:
- host: canary-backend
port: 8080
weight: 10
Behavior
For each request the plugin finds the first rule whose match condition passes (a rule with no match matches every request). Within that rule it picks one weighted_upstreams slot by weighted round-robin over the slot weights — a slot with weight 3 is chosen 3 out of every total_weight calls. Zero-weight slots are never selected.
The picked slot has one of two shapes:
- Default slot (no
upstream) — the plugin returns the Context unchanged through thesuccessport, and the request continues to the route's normalupstreamnode. - Target slot (has
upstream.targets) — the plugin proxies the request itself to one of the targets (round-robin within the set), reusing the shared outbound HTTP client. It forwards the method, headers (overridingHostwith the target), and body, writes the backend's status/headers/body ontocontext.response, and short-circuits through theroutedport.
If no rule matches at all, the request passes through the success port unchanged.
Split-node wiring
Because featherbit pipelines need a distinct port for "stop here, send this response" versus "keep going", this node uses its ports as follows:
- Wire
success→ the route's normalupstreamnode. This is the path for default slots and non-matching requests. - Wire
routed→client.in. This is the path for traffic the plugin proxied itself: the response is already populated on the Context and reaches the client directly. - Wire
error→client.in(or anerror-handler). This is a genuine infrastructure failure — the split target was unreachable.
If a split target is unreachable, the node fails with code TRAFFIC_SPLIT_UPSTREAM_ERROR and a prepared 502 JSON body ({"error": "bad_gateway", "message": "traffic-split target unreachable"}, content-type: application/json), routed through the error port.
Ports
traffic-split declares three output ports: success (no rule matched, or the default slot was picked — the request continues to the route's normal upstream), routed (a target slot was picked and proxied; wire straight to client), and error (the split target was unreachable — a genuine infrastructure failure). success and routed are mandatory — the policy compiler rejects any policy that leaves either unwired.
edges:
- from: traffic-split.success
to: upstream.in
- from: traffic-split.routed
to: client.in
- from: traffic-split.error
to: client.in
Behavior notes
- There is no shared upstream registry at this node: a target slot is proxied by the plugin itself and short-circuited through the
routedport; a default slot falls through to the route'supstreamnode. - Upstream references are inline target lists (
upstream.targets: [{host, port}]) only —upstream_idreferences to a shared upstream store are not supported. - Selection is a deterministic weighted round-robin (a per-rule cursor); the long-run distribution matches the configured weights.
Errors
The node returns the Context with an error, so the graph engine routes through the error port and appends the error to context.errors. The status below is the one prepared on context.response; wire error to client (or an error-handler) for the caller to see it.
| Code | Status | When |
|---|---|---|
TRAFFIC_SPLIT_UPSTREAM_ERROR | 502 | The chosen split target timed out, was unreachable, or the request to it could not be built. |