Skip to main content

traffic-split

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

KeyTypeDefaultDescription
rulesarray— (required, non-empty)Evaluated in order; the first rule whose match passes is used.
rules[].matcharrayabsent = match allA triple-array condition (rules AND-ed). Omit to match every request.
rules[].weighted_upstreamsarray— (required, non-empty)The weighted slots; one is chosen by weighted round-robin.
rules[].weighted_upstreams[].upstreamobjectabsent = 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[].weightinteger >= 01Selection weight. Every rule needs at least one slot with weight > 0.
timeout_msinteger60000Whole-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 the success port, and the request continues to the route's normal upstream node.
  • 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 (overriding Host with the target), and body, writes the backend's status/headers/body onto context.response, and short-circuits through the routed port.

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 normal upstream node. This is the path for default slots and non-matching requests.
  • Wire routedclient.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 errorclient.in (or an error-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 routed port; a default slot falls through to the route's upstream node.
  • Upstream references are inline target lists (upstream.targets: [{host, port}]) only — upstream_id references 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.

CodeStatusWhen
TRAFFIC_SPLIT_UPSTREAM_ERROR502The chosen split target timed out, was unreachable, or the request to it could not be built.