Skip to content

Native HTTP filters

How a native filter is wired into the Arion binary: the config enum, the type URL registry, the runtime dispatch, and per-route overrides.

A native HTTP filter is Rust code compiled into Arion itself — the same mechanism behind the built-in RBAC, JWT, CORS, and MCP gateway filters. This page maps the seams a contributor touches to add one; it teaches the shape, not a full tutorial.

Why it matters

Native filters beat WASM where the feature belongs to everyone: always-on behaviour, zero serialization across a guest boundary, direct access to internals such as metrics, access logging, and streaming bodies. The price is that shipping one means changing Arion — a contribution and a rebuild, not a config change.

The config side

A filter's configuration lives in arion-configuration, in src/config/network_filters/http_connection_manager/http_filters/. Each filter is one module with a serde config struct, plus a variant on the HttpFilterType enum:

#[serde(rename_all = "snake_case", tag = "filter_type", content = "filter_settings")]
pub enum HttpFilterType {
    Wasm(WasmConfig),
    Rbac(HttpRbac),
    RateLimit(LocalRateLimit),
    ExternalProcessor(ExternalProcessor),
    // ... Cors, JwtAuthentication, McpGateway, UserRateLimit, CedarPolicy
}

The Envoy dialect reaches this enum through the envoy_conversions module in http_filters.rs, where TryFrom<Any> for SupportedEnvoyFilter maps a @type URL to a protobuf decoder. Arion-original filters register URLs under the arion. namespace:

"type.googleapis.com/arion.extensions.filters.http.cedar.cedar_policy.v3.CedarPolicy" => {
    ProtoCedarPolicy::decode(typed_config.value.as_slice()).map(Self::CedarPolicy)
},

Each config struct implements TryFrom for its Envoy proto, destructuring every field and passing unhandled ones through unsupported_field!. That macro is the compatibility promise in code: a field the filter does not implement makes the config refuse to load rather than being silently dropped.

The runtime side

The executable filter lives in arion-lib, under src/listeners/http_connection_manager/, and gets a variant on HttpFilterValue in src/listeners/http_filters.rs. TryFrom<HttpFilterConfig> builds the runtime filter from its config once at load; per request, FilterFactory::new_from produces a request-local instance. The chain then dispatches:

pub enum FilterDecision {
    Continue,
    Reroute,
    DirectResponse(Box<Response<ArionResponseBody>>),
}

impl HttpFilterValue {
    pub async fn apply_request(&mut self, request: &mut Request<ArionRequestBody>, ctx: &RequestCtx) -> FilterDecision {
        match self {
            HttpFilterValue::Rbac(rbac) => apply_authorization_rules(rbac, request),
            HttpFilterValue::ExternalProcessor(ext_proc) => ext_proc.apply_request(request, ctx).await,
            // ...
        }
    }
}

A filter implements apply_request, and apply_response if it acts on the response path. Continue passes the message on, DirectResponse answers the client and stops the chain, and Reroute re-matches the route table and resumes with the next filter. Request filters run in configured order; response filters run in reverse. Filters that skip a path — RBAC and rate limiting have no response behaviour — return Continue from the unused hook.

Per-route overrides

Route-level control flows through typed_per_filter_config, keyed by the filter's chain name. On the config side that is the FilterConfigOverride enum (today: local rate limit, RBAC, ext_proc) plus the generic FilterConfig wrapper for disabling any filter; at load time per_route_http_filters precomputes the effective filter list for every route, so request handling only does a lookup. A new filter that wants per-route settings adds a variant there and handles it in HttpFilterValue::from_filter_override.

Where tests live

End-to-end coverage sits in arion-e2e-tests/tests/ (filters_ext_proc.rs, filters_wasm.rs, filters_rate_limit.rs, ...), with configs composed by the builders in arion-e2e-tests/src/config_builder/ — a new filter adds a builder there so tests can express it.

Key terms

  • HttpFilterType — the serde config enum; one variant per filter.
  • SupportedEnvoyFilter — the @type URL registry for the Envoy dialect.
  • HttpFilterValue — the runtime enum whose apply_request / apply_response run on the hot path.
  • FilterDecision — what a hook returns: continue, re-route, or answer directly.

Next

Read the filter chain for how the chain is assembled, or start from Extending Arion if you are still choosing an extension mechanism.

Continue

Start typing to search the site.

↑ ↓ to move Enter to open Esc to close