5.7 KiB
RustProxy 0.27.3 implementation note
Scope
0.27.3 aligns HTTP/3 HEAD cache handling with HTTP/2, reduces long-lived cache/rate-limit memory, excludes the rebuildable managed Python runtime from backups, and promotes package repositories as the normal installation path. SQLite schema 8, saved configuration compatibility and the COLD cache on-disk format remain unchanged.
HTTP/3 HEAD cache parity
HTTP semantics do not make HEAD meaningfully different between HTTP/2 and HTTP/3. RustProxy already allows GET and HEAD in cache policy, but the forwarding path actively probed an initially unknown request-body stream only for GET.
The HTTP/3 receive body starts with an unknown end-of-stream state until it is polled. A body-less HEAD could therefore reach cache policy as body_empty=false and be bypassed with request_body. HTTP/2 happened to expose end-of-stream immediately for the same curl request.
0.27.3 runs the existing bounded empty_for_cache probe for both GET and HEAD. A genuinely body-bearing request is still bypassed, while a body-less H3 HEAD follows the same cache lookup path as H2.
COLD cache RAM
The COLD tier stores bodies on disk, but its old in-memory index retained a complete response HeaderMap, origin validators, a duplicate 32-byte cache key and a 64-character SHA-256 string for every object. With many small COLD objects, these metadata allocations could become a material RSS component even though body bytes were no longer resident.
0.27.3 keeps the fields needed for matching, invalidation, dashboard rows, LRU and freshness in the COLD index, but removes the persistent header/validator copies. On a real COLD read RustProxy reads and validates the existing metadata JSON, materializes headers/validators for that request, verifies the body digest and then promotes/serves the object as before. The in-memory digest is stored as 32 raw bytes and the map-owned cache key is no longer duplicated inside ColdEntry.
This trades one metadata-file read on a COLD hit for substantially lower long-lived metadata RAM. HOT behavior, COLD disk format, invalidation fields and response semantics are unchanged.
Rate-limit/IP retention
The previous limiter keyed every client bucket by (revision, vhost String, IP), duplicating the vhost allocation for each IP. Expired buckets were normally retained until the global 65,536-entry bound caused cleanup.
0.27.3 groups client-IP buckets below a single vhost key, stores the active revision once and removes buckets whose fixed window has expired. Cleanup runs opportunistically during requests and from the existing periodic runtime-maintenance loop, so idle traffic no longer leaves expired IP state resident indefinitely. Sparse client/vhost hash tables are shrunk after reclamation and the allocator trim follows the periodic sweep. Removing an expired bucket is behavior-equivalent to the existing next-request reset: the next request starts a new fixed window with count zero. The global client-bucket bound remains 65,536.
Compression RAM assessment
Downstream compression remains streaming through tower_http::compression::Compression. It does not collect the complete response body before encoding, and CompressionLevel::Fastest remains selected. The long-lived cache does not keep encoder state. Compression memory therefore scales primarily with the number and codec type of concurrently active compressed responses rather than the number of cached objects or historical requests.
No compression algorithm or negotiation behavior is changed in 0.27.3 because the existing implementation is already bounded by streaming response lifetime. Brotli and Zstandard can have larger per-stream codec state than gzip/deflate, so concurrency remains the main compression-RAM factor under high load.
Backup scope
acme-python is a managed/rebuildable Python virtual environment, not authoritative application state. Backups now skip both acme-python and the underscore spelling acme_python, while certificate/configuration/secret state remains covered by the existing backup logic.
Installation paths
The documented normal installation/update paths are:
- DEB/RPM packages:
https://rustproxy.linuxiarz.pl/ - OPNsense plugin:
https://rustproxy-opn.linuxiarz.pl/
The Debian 13+ source bootstrap remains available as an optional source/development path.
Managed ACME bootstrap diagnostics
The managed ACME runtime now distinguishes private-environment creation, pip preparation, Certbot package installation and final plugin validation failures instead of collapsing all of them into one generic setup error. The selected stage is also emitted as a warning when no distribution Certbot fallback is available.
On distributions enforcing PEP 668, the application-local virtualenv bootstrap first uses normal pip --target; if the distribution rejects that operation, RustProxy retries with --break-system-packages while still writing exclusively into the private bootstrap directory under the RustProxy data tree. This does not install or modify packages in the distribution Python environment.
Source-tool separation
0.27.3 also separates application-coupled scripts from auxiliary tooling without changing proxy behavior. scripts/runtime, scripts/build, scripts/deploy and scripts/migration contain code directly tied to application/build/deployment workflows. Compiler-independent checks, mocks, audit probes and the retired Python backup helper moved to tools/. OPNsense and RouterOS helpers moved to integrations/.
The shell entry points, build.rs, embedded ACME source path, source upgrader, deterministic source archive and release checks were updated together so the relocation is part of the same 0.27.3 development release.