Open-Source Wikis

/

Pingora

/

Features

/

Proxy phases

cloudflare/pingora

Proxy phases

The proxy framework is a state machine. At each "phase" your ProxyHttp impl gets a chance to inspect or mutate state. This page is the reference for what every callback does and when it fires.

The narrative version is at docs/user_guide/phase.md. The trait definitions are in pingora-proxy/src/proxy_trait.rs.

The diagram

graph TD
    start("new request")-->early_request_filter
    early_request_filter-->request_filter
    request_filter-->upstream_peer

    upstream_peer-->Connect{IO: connect to upstream}

    Connect-->|connection success|connected_to_upstream
    Connect-->|connection failure|fail_to_connect

    connected_to_upstream-->upstream_request_filter
    upstream_request_filter-->request_body_filter
    request_body_filter-->SendReq{IO: send request}
    SendReq-->RecvResp{IO: read response}
    RecvResp-.feature: adjust_upstream_modules.->adjust_upstream_modules
    adjust_upstream_modules-->upstream_response_filter
    upstream_response_filter-->response_filter
    response_filter-->upstream_response_body_filter
    upstream_response_body_filter-->response_body_filter
    response_body_filter-->logging
    logging-->endreq("request done")

    fail_to_connect-->|can retry|upstream_peer
    fail_to_connect-->|can't retry|fail_to_proxy
    fail_to_proxy-->logging

    request_filter-->|send response|logging

    Error[response filter error]-->error_while_proxy
    IOFailure[IO error]-->error_while_proxy
    error_while_proxy-->|can retry|upstream_peer
    error_while_proxy-->|can't retry|fail_to_proxy

General rules

  • Most filters return pingora_error::Result<T>. An Err short-circuits to fail_to_proxy() and ends the request.
  • All filters are async.
  • A per-request CTX is shared across every callback; it's mutable.
  • Most filters are optional with no-op defaults. The only required method is upstream_peer().
  • upstream_response_* and response_* are paired; the upstream_* versions run before HTTP caching and changes affect the cached copy. The non-upstream_* versions run before sending to the downstream.

The phases

early_request_filter()

First chance to inspect a request. Runs before downstream modules. Use this for module configuration that needs to happen before any module looks at the request.

request_filter()

The main pre-upstream phase. Validate inputs, rate-limit, initialize CTX, optionally short-circuit by writing a response to the downstream and returning early.

request_body_filter()

Called every time a chunk of the request body arrives. Use this if your proxy needs to inspect or transform the request body.

proxy_upstream_filter()

Returns a bool. If false, the proxy skips going upstream and returns 502 by default. Use this for "I want to serve a synthetic response" patterns.

upstream_peer()

The only required callback. Returns a Box<HttpPeer> describing where to send the request and how. Most load-balancing logic lives here. Re-runs on retries.

connected_to_upstream()

Fires after a successful connect. Receives connection info (RTT, TLS digest, etc.). Used for logging and metrics.

fail_to_connect()

Fires on connect failure. Decide whether the error is retryable. If set_retry(true), upstream_peer() is called again — you can pick a different peer.

upstream_request_filter()

Modify the request headers just before they go on the wire. Useful for adding Host, custom auth headers, removing internal hop-by-hop headers, etc. The README example uses this to inject a Host header.

adjust_upstream_modules() (feature: adjust_upstream_modules)

Fires when the upstream response header arrives, before upstream modules process it. You can configure module behavior based on the response — e.g. supply a compression dictionary keyed on the response content. Read-only access to the response header here; use upstream_response_filter to mutate it.

upstream_response_filter() / upstream_response_body_filter() / upstream_response_trailer_filter()

Mutate the response on the way back from the upstream, before the cache layer sees it. Changes here are persisted in the cache.

response_filter() / response_body_filter() / response_trailer_filter()

Mutate the response on the way to the downstream, after the cache layer. Changes here are not persisted.

error_while_proxy()

Fires when an in-flight error happens during proxying after a connection was established. Decide whether to retry. Idempotent methods (GET, HEAD, etc.) are usually retried; non-idempotent are not.

fail_to_proxy()

The catch-all error path. Returns a FailToProxy describing the response to send and whether to suppress the error log. Standard pattern: send a 502 with a body explaining the failure class.

logging()

Always runs as the last phase, success or failure. Use for access logs, metrics, span finalization.

Helpers (not phases)

Helper What it does
request_summary() One-line description of the request. Used in error logs and traces.
suppress_error_log() Return true to skip the default error log line for this error. Useful for spammy downstream errors.
init_downstream_modules() Build the per-session module set.
cache_key_callback() Build the cache key from the request.
cache_lookup_filter() Final cacheability decision.

Cache phases (additional)

When the cache feature is on, additional callbacks fire around the lookup and fill:

  • cache_key_callback
  • cache_lookup_filter
  • cache_hit_filter
  • cache_miss
  • should_serve_stale
  • cache_not_modified_filter
  • cache_revalidate_filter
  • range_header_filter

These are all in pingora-proxy/src/proxy_trait.rs. Their semantics are documented inline.

Where the calls live

The wire-format implementations call these callbacks at the right points:

Phase Called from
early_request_filter proxy_h1.rs::process_request_header, proxy_h2.rs::process_request_header
upstream_peer proxy_common.rs::find_upstream_peer
upstream_request_filter proxy_h1.rs, proxy_h2.rs (just before write)
Cache callbacks proxy_cache.rs
fail_to_proxy proxy_common.rs::fail_to_proxy

When debugging, set a breakpoint at the relevant process_* function and step through.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Proxy phases – Pingora wiki | Factory