Skip to content

Architecture

Pingclair is a Cargo workspace. The running server is the pingclair binary, which links the crates below.

Crate Responsibility
pingclair Command-line entry point: argument parsing, logging, startup, and the service wrapper.
pingclair-config Configuration compiler: lexes, parses, and semantically checks the Pingclairfile.
pingclair-proxy HTTP/1.1 and HTTP/2 proxying on Pingora, the HTTP/3 listener on quiche, load balancing, and the shared request policy layer.
pingclair-static Static file serving: file reads, MIME types, range requests, and streaming.
pingclair-tls Certificate management: manual certificates, a persistent internal certificate authority, and automatic ACME issuance.
pingclair-api Admin API for inspecting state and reloading configuration.
pingclair-core Data structures and server lifecycle shared by the crates above.
client
|
| TLS with ALPN, or QUIC
v
listener HTTP/1.1 and HTTP/2 on TCP, HTTP/3 on UDP
|
v
transport adapter Pingora ProxyHttp for TCP, tokio-quiche for QUIC
|
v
policy layer routing, matchers, headers, rate limits, access log
|
v
handler file server | reverse proxy | FastCGI | static response
|
v
upstream or disk

Both transports converge on the same policy layer, so routing, header handling, rate limiting, and access logging behave the same on HTTP/1.1, HTTP/2, and HTTP/3. The transports differ only where the protocol requires it, and those differences are listed below.

  • Bodies are streamed. Request and response bodies move through the proxy in bounded chunks. Compression, middleware, and proxying do not buffer a complete body, so a large upload or a slow reader does not consume memory proportional to the body size.
  • Upstream connections are pooled. Keepalive connections to backends are reused. Hostname upstreams are re-resolved on the interval set by dns_refresh, so a container that restarts on a new address is followed without an operator action.
  • Runtime state is immutable at request time. Requests read a published snapshot. A reload publishes a new snapshot instead of mutating the one in use.

Some behavior differs by protocol by design. It is listed here rather than discovered later:

Area Behavior
Trailers Declared request trailers are not forwarded. The server answers 501 before the response is committed, resets an already committed HTTP/3 stream, and answers 502 when an upstream advertises response trailers.
CONNECT CONNECT and extended CONNECT return 501 on HTTP/3 until tunnel support is implemented.
FastCGI php_fastcgi works on HTTP/1.1 and HTTP/2. Routes that need FastCGI return 501 on HTTP/3 until that path has its own FastCGI client.

WebSocket upgrades fail intermittently under load: roughly 10-15% of upgrades on a busy machine. The cause is a race in the upstream pingora-proxy crate rather than in Pingclair’s own upgrade handling, and it is invisible on an idle developer machine, which is why it is documented here. Upstream issue: cloudflare/pingora#946.