# 🏗️ Architecture Pingclair supports HTTP/1.1, HTTP/2, and HTTP/3. Each transport handles protocol I/O, while a shared policy layer applies routing, header rules, rate limits, and access logging. This page describes the components, the request path, and protocol differences in **v0.2.2**. ## 🧱 The server is one binary built from a few crates Pingclair is a Cargo workspace. The `pingclair` binary links the crates below; each one owns a single responsibility. | Crate | Responsibility | | --- | --- | | `pingclair` | Command-line entry point: argument parsing, logging, startup, shutdown, and reload. | | `pingclair-config` | Configuration compiler: reads the Pingclairfile, checks it, and produces the configuration the server runs. | | `pingclair-proxy` | HTTP/1.1 and HTTP/2 on Pingora, HTTP/3 on quiche, load balancing, and the shared request policy layer. | | `pingclair-static` | Static file serving: file reads, MIME types, range and conditional requests, and streaming. | | `pingclair-fastcgi` | The FastCGI client that `php_fastcgi` uses to reach PHP-FPM. | | `pingclair-tls` | Certificate management: certificate files, the internal certificate authority, and ACME issuance. | | `pingclair-api` | Admin API for inspecting state and reloading configuration. | | `pingclair-core` | Data structures and lifecycle shared by the crates above. | ## 🚦 Every request crosses the same policy layer ```text 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 ``` The transport adapter converts protocol frames into a request and passes it to the shared policy layer. That layer applies routing, header rules, rate limiting, and access logging across HTTP/1.1, HTTP/2, and HTTP/3. Both transports also reach upstreams through the same connector, so connection pooling, upstream TLS, and timeouts are shared as well. ## 🌊 What holds for every request - **Bodies use bounded memory.** Proxy bodies stream by default. Explicit request or response buffering delays forwarding up to its configured ceiling, then streams the remainder; `unlimited` still caps memory at 8 MiB. Static live compression is bounded as well. See the [known streaming defects](/project/status/). - **Upstream connections are reused.** Keepalive connections to backends are pooled. A hostname upstream is resolved again on the interval set by `dns_refresh`, so a backend container that restarts on a new address is resolved automatically. - **Configuration is read, never changed, while requests run.** Each request reads a published snapshot of the compiled configuration. A reload builds a new snapshot and swaps it in; requests already running finish on the old one. ## 🌐 Where the protocols differ A few behaviors differ by protocol. They are listed here so that operators can account for them before deployment. | Area | Behavior in v0.2.2 | | --- | --- | | Trailers | Request trailers are not forwarded on any protocol. A request that declares them is answered `501` before the response starts; an HTTP/3 stream whose response has already started is reset instead. An upstream response that advertises trailers keeps its status and body; the trailer fields are dropped. | | `CONNECT` | A usable `host:port` target receives `405` with `Allow`; a target without a usable port receives `400`. HTTP/1.1 closes after refusal. | | FastCGI | `php_fastcgi` works on every protocol, HTTP/3 included. | 📌 `TRACE` also receives `405` with `Allow`. Malformed HTTP/1 chunked bodies, raw whitespace or controls in request targets, and HTTP/1.1 requests without Host receive `400` and close. ## ⚠️ WebSocket upgrades fail intermittently under load Pingclair proxies WebSocket, but roughly 10-15% of upgrades fail when the machine is busy. From the outside, a failed upgrade is a connection closed immediately after the `101 Switching Protocols` response. The cause is a race in the upstream `pingora-proxy` crate, not in Pingclair's upgrade handling, and no configuration avoids it. The failure is less frequent on an idle machine. [CHANGELOG](https://github.com/dorianverlaine/pingclair/blob/main/CHANGELOG.md). ## 🧭 Related pages - [Configuration model](/concepts/configuration/): how a Pingclairfile becomes the snapshot described above. - [Project status](/project/status/): what the release supports and refuses. 📌 See [Project status](/project/status/) for the remaining streaming and protocol defects. Cancelling an HTTP/3 request releases an idle upstream exchange while other streams on the connection remain usable.