Understanding the Easegress Pipeline Flow Mechanism and Jump Conditions

The Easegress pipeline flow mechanism processes requests through a configurable chain of filters where each node can define conditional jumps via the jumpIf field, redirecting execution to specific filter aliases or the built-in END filter based on filter return results.

The Easegress API gateway from the megaease/easegress repository implements a flexible request processing architecture centered on pipelines—declarative chains of filters that execute conditionally. Understanding the Easegress pipeline flow mechanism and jump conditions is essential for building complex traffic management logic, as it allows developers to route requests dynamically based on filter outcomes rather than static sequences.

Core Architecture of Easegress Pipelines

An Easegress pipeline consists of an ordered collection of FlowNodes defined in pkg/object/pipeline/pipeline.go. Each node represents a single processing step that can execute conditionally based on jump targets defined in the node's configuration.

The FlowNode Structure

The fundamental building block is the FlowNode struct, which encapsulates filter metadata and jump conditions:

type FlowNode struct {
    FilterName  string            `json:"filter" jsonschema:"required,format=urlname"`
    FilterAlias string            `json:"alias,omitempty"`
    Namespace   string            `json:"namespace,omitempty"`
    JumpIf      map[string]string `json:"jumpIf,omitempty"`
    filter      filters.Filter
}

Source: [pkg/object/pipeline/pipeline.go](https://github.com/megaease/easegress/blob/main/pkg/object/pipeline/pipeline.go#L80-L86)

  • FilterName identifies the filter implementation to invoke
  • FilterAlias provides a short reference name used as jump targets
  • JumpIf maps filter result strings to target aliases, enabling conditional flow control

Pipeline Execution Flow

The Pipeline.Handle method serves as the entry point for request processing. It delegates to the private doHandle function, which implements the core execution algorithm:

func (p *Pipeline) doHandle(ctx *context.Context, flow []FlowNode, stats []FilterStat) (string, []FilterStat, bool) {
    result, next, sawEnd := "", "", false

    for i := range flow {
        node := &flow[i]
        alias := node.filterAlias()

        // Skip nodes that are not the target of a previous JumpIf.
        if next != "" && next != alias {
            continue
        }

        // The built‑in END filter terminates the pipeline.
        if node.FilterName == BuiltInFilterEnd {
            sawEnd = true
            break
        }

        // Execute the filter.
        start := fasttime.Now()
        ctx.UseNamespace(node.Namespace)
        result = node.filter.Handle(ctx)
        stats = append(stats, FilterStat{
            Name:     alias,
            Kind:     node.filter.Kind().Name,
            Duration: fasttime.Since(start),
            Result:   result,
        })

        // Resolve jump target (if any) for the obtained result.
        var ok bool
        if next, ok = node.JumpIf[result]; result != "" && !ok {
            next = BuiltInFilterEnd
        }

        // END target forces termination.
        if next == BuiltInFilterEnd {
            sawEnd = true
            break
        }
    }
    return result, stats, sawEnd
}

Source: [pkg/object/pipeline/pipeline.go](https://github.com/megaease/easegress/blob/main/pkg/object/pipeline/pipeline.go#L71-L104)

The algorithm processes nodes sequentially unless a jump condition redirects execution. When a filter returns a result, doHandle looks up the result in node.JumpIf. If found, execution continues at the matching alias; if not found, the pipeline jumps to BuiltInFilterEnd and terminates.

How Jump Conditions Work in Easegress

Jump conditions transform linear pipelines into directed graphs, enabling conditional request processing paths based on authentication outcomes, rate limiting status, or business logic results.

The JumpIf Mechanism

The JumpIf field is a map[string]string where:

  • Keys represent filter result strings (e.g., "Unauthorized", "OverLimit")
  • Values specify target filter aliases or the reserved "END" keyword

After executing a filter, the runtime evaluates the result against this map. If the result matches a key, the pipeline jumps to the corresponding target. This logic appears in lines 95-100 of the doHandle implementation:

var ok bool
if next, ok = node.JumpIf[result]; result != "" && !ok {
    next = BuiltInFilterEnd
}

If a filter returns a non-empty result not present in JumpIf, the pipeline automatically terminates. This default behavior ensures that unexpected filter outcomes fail safely rather than continuing with potentially invalid request states.

Built-in END Filter and Termination

The BuiltInFilterEnd constant represents a virtual filter that immediately terminates execution. Two conditions trigger termination:

  1. Explicitly jumping to "END" via jumpIf configuration
  2. Encountering a node with FilterName == BuiltInFilterEnd during iteration

When the algorithm detects either condition, it sets sawEnd = true and exits the processing loop, returning the final result to the caller.

Validation and Safety Guarantees

Before a pipeline accepts traffic, the Spec.ValidateJumpIf method ensures all jump targets are resolvable and valid. This validation runs during configuration loading in pkg/object/pipeline/pipeline.go:

func (s *Spec) ValidateJumpIf(specs map[string]filters.Spec) {
    validTargets := map[string]int{BuiltInFilterEnd: 1}
    for i := len(s.Flow) - 1; i >= 0; i-- {
        node := &s.Flow[i]
        if node.FilterName == BuiltInFilterEnd { continue }
        spec := specs[node.FilterName]
        results := filters.GetKind(spec.Kind()).Results
        for result, target := range node.JumpIf {
            if result != "" && !stringtool.StrInSlice(result, results) {
                panic(fmt.Errorf("filter %s: result %s is not in %v", node.FilterName, result, results))
            }
            if count := validTargets[target]; count == 0 {
                panic(fmt.Errorf("filter %s: target filter %s not found", node.FilterName, target))
            } else if count > 1 {
                panic(fmt.Errorf("duplicated filter name/alias: %s", target))
            }
        }
        validTargets[node.filterAlias()]++
    }
}

Source: [pkg/object/pipeline/pipeline.go](https://github.com/megaease/easegress/blob/main/pkg/object/pipeline/pipeline.go#L111-L138)

The validator walks the flow backwards to build a set of valid jump targets, checking that:

  • Each JumpIf result is a valid output of the filter's defined results
  • Each target alias exists in the pipeline
  • No duplicate aliases exist that could cause ambiguous jumps

Configuring Pipeline Flows in YAML

Developers define the Easegress pipeline flow mechanism and jump conditions in YAML specifications. The flow array declares execution order and conditional branches:

name: auth-pipeline
filters:
  - name: jwtAuth
    kind: JWTAuth
  - name: rateLimiter
    kind: RateLimiter
  - name: proxy
    kind: Proxy
flow:
  - filter: jwtAuth
    alias: auth
    jumpIf:
      Invalid: END
      Expired: rateLimiter
  - filter: rateLimiter
    alias: limiter
    jumpIf:
      OverLimit: END
  - filter: proxy
    alias: backend

In this configuration:

  • Valid JWTs proceed directly to the proxy (no jump defined, continues to next node)
  • Expired tokens jump to the rate limiter for additional validation
  • Invalid tokens or over-limit requests terminate the pipeline immediately

The runtime translates these declarations into the FlowNode slice processed by doHandle. Each filter's return value determines the next hop in the execution graph.

Summary

  • Easegress pipelines use a linear FlowNode array processed by the doHandle method in pkg/object/pipeline/pipeline.go
  • Jump conditions are defined via the jumpIf map, which associates filter results with target filter aliases
  • Default termination occurs when a filter returns a non-empty result not mapped in jumpIf, automatically routing to BuiltInFilterEnd
  • Validation happens at startup through ValidateJumpIf, ensuring all jump targets exist and results are valid for their filter types
  • YAML configuration combines filter definitions with a flow array that specifies execution order and conditional branching

Frequently Asked Questions

What happens if a filter returns a result not defined in jumpIf?

If a filter returns a non-empty result string that does not exist as a key in the node's jumpIf map, the pipeline automatically jumps to the built-in END filter and terminates execution. This safety mechanism prevents undefined behavior by ensuring only explicitly configured result codes allow continued processing.

Can multiple filters share the same alias in a pipeline?

No. The ValidateJumpIf function explicitly checks for duplicate aliases during configuration validation. If two filters define the same alias, the validator panics with an error message indicating "duplicated filter name/alias", preventing ambiguous jump targets that could break the execution flow.

How does the pipeline handle namespace isolation between filters?

Each FlowNode includes an optional Namespace field. Before executing a filter's Handle method, the runtime calls ctx.UseNamespace(node.Namespace) to switch the context to the appropriate namespace. This allows filters to maintain isolated state and configuration contexts while processing the same request through different pipeline stages.

Is it possible to create loops or cycles using jumpIf conditions?

No, the current implementation does not prevent forward jumps only. While the validation walks backwards to confirm target existence, it does not enforce acyclic graphs. However, because jumps only skip forward to later nodes (or END), and the execution index always increments, infinite loops cannot occur within a single pipeline execution—the flow always progresses toward termination.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →