4.9 KiB
RustProxy 0.22.2 specification audit
Scope
This audit covers the 0.22.2 delta over 0.22.1: replay-safe backend failover retry, explicit HTTP/2 upstream request handling, bounded per-backend request concurrency, richer upstream diagnostics, an expanded backend operations view, and public X.509 expiry metadata. The retained 0.22.1 RFC 8441/h2c-listener/tunnel work and 0.22.0 cache work remain historical evidence for retained functionality.
Requirement matrix
| Requirement | Source implementation | Regression / verification |
|---|---|---|
| Retry only replay-safe methods | proxy/mod.rs::retry_safe_method permits GET/HEAD/OPTIONS only |
proxy source regression; compiler gate open here |
| Never replay body-bearing writes | retry requires an exact empty request body; POST/PUT/PATCH fail the method gate | source regression + release checks |
| Retry another backend | Pool::select_retry_wait(..., excluded) excludes attempted IDs |
failover_selection_never_reuses_an_attempted_backend |
| Bounded retry policy | RetryPolicy.max_retries validates 0..3; bounded 5xx list |
balance policy regressions |
| Explicit H2 upstream | explicit protocol=http2 uses HTTP/2 request versioning |
proxy regression + real Hyper h2c upstream test source |
| Per-backend request concurrency | PoolMember.max_concurrent_requests, atomic full-exchange lease acquisition |
backend_concurrency_limit_queues_then_rejects_and_recovers |
| Bounded capacity queue / 503 | queue_timeout_ms validates 0..60000; async selection waits in bounded intervals and returns SelectionFailure::Capacity |
capacity timeout regression |
| Reject vs queue-timeout observability | capacity_rejections_total always counts final capacity rejection; queue_timeouts_total only counts an elapsed non-zero queue |
zero_capacity_queue_timeout_rejects_without_counting_a_queue_timeout |
| No false TCP connection claim | limit is documented and named as concurrent proxy requests/exchanges, not max_connections |
TODO/roadmap explicitly keep socket-count limit open |
| Failure classification | DNS, TLS, connect, response-header timeout, read/send timeout and generic I/O flow through Lease::error_detail |
source contract checks + detailed failure regression |
| Last failure | backend view exposes class, bounded detail and timestamp | detailed_backend_failures_are_exposed_without_losing_aggregate_counters |
| Health ejection/recovery timestamps | health transitions store last ejected/recovered timestamps | source contract checks |
| Backend operational view | dashboard combines status, active/limit, requests/errors, latency, traffic, last failure and last recovery | JS/language/source checks |
| X.509 validity metadata | public leaf PEM parser extracts SAN, issuer, notBefore/notAfter and days remaining without reading keys | X.509 time + generated-certificate regression sources |
| Expiry warning | inventory reports ok, warning (<=30 days), expired, or unknown |
source/OpenAPI/UI checks |
| Renewal status in certificate view | inventory flattens current renewal status, last check and last failure message | OpenAPI/UI/source checks |
| Full downstream H2 -> RustProxy -> upstream H2 interoperability | implementation path present | OPEN: live/compiler-enabled E2E required |
Capacity semantics
max_concurrent_requests is a full proxy-exchange permit. The permit remains held for the response body/tunnel lifetime through the existing backend lease, preventing a streaming response from releasing capacity after headers only. A value of 0 preserves the historical unlimited behavior. queue_timeout_ms=0 rejects immediately when no eligible backend has capacity; a positive value performs a bounded wait and then returns HTTP 503.
Pool readiness remains a health/availability signal rather than a transient-capacity signal. Backup members can receive traffic when eligible primary members are saturated. Sticky affinity is honored only when the matched backend has capacity; otherwise normal eligible selection can move the request to another backend.
A physical TCP connection/socket cap is deliberately not claimed in 0.22.2 because Hyper connection pooling requires a distinct transport-level mechanism.
Certificate safety
Certificate inventory parsing opens only the configured public certificate PEM. Private keys are not opened by the metadata path. The parser is deliberately narrow: it extracts the leaf issuer, RFC 5280 validity values and DNS SAN coverage while rustls remains responsible for certificate/key validation in serving paths.
Release gate
This packaging workspace has no cargo, rustc or rustfmt, and outbound DNS is unavailable, so Rust parsing/type checking, compilation, linking and execution cannot be claimed here. Rust-independent JavaScript/JSON/language/source/package/Go gates are run locally. A Rust-enabled release host must still run ./dev.sh check, ./dev.sh test, release build and live H1/H2/H3/H2-to-H2 acceptance before deployment.