v0.34.0_7

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-18 07:48:59 +02:00
1 parent 47ce1f18c2
commit b6a3e12225
21 files changed
+471 -84

No files matched your search

+20 -2
View File
@@ -5,6 +5,24 @@
## 0.34.0 ## 0.34.0
- DEV 1 KiB OPT5 multi-site follow-up: use direct CSPRNG 256-bit request ID
bytes mixed with the existing per-process sequence rather than SHA-256 per
request, retaining 64 lowercase hex digits and protected `X-Request-ID`.
Construct the absolute original URL only when the frontend enables Tinyauth,
without changing trusted/untrusted forwarding or IPv6 validation. Borrow the
healthy LB response detail instead of allocating the string `ok`.
- Keep traffic analytics non-blocking and bounded at 2,048 queued samples;
process up to 64 samples per dashboard mutex acquisition in arrival order,
allocate vhost keys only on first observation, classify User-Agents without
a lowercase string, reuse static common status-family labels and render
one-vhost views without cloning every top table. Skip response compression
policy resolution when no codec is accepted by the global ceiling (e.g.
`identity`), retaining all enabled codec, scope and exclusion behavior.
- Source-only until OPT5 compilation and Rust tests. The compiled OPT4 input
passed 151 live HTTP checks (0 failed, 1 optional Zstandard skipped).
Brief shared-host OPT4/OPT3/nginx tests are mixed at 16/64 connections;
see `docs/PROXY_1KB_OPT5_DEV_0.34.0.md`. No version increment.
- DEV 1 KiB follow-up (OPT4): keep ordinary unmodified public request targets - DEV 1 KiB follow-up (OPT4): keep ordinary unmodified public request targets
borrowed through URI rewriting, avoid creating a metering body adapter for borrowed through URI rewriting, avoid creating a metering body adapter for
request bodies with no frames/trailers, and consume already-validated upstream request bodies with no frames/trailers, and consume already-validated upstream
@@ -15,8 +33,8 @@
refresh owned targets, streamed upload metering, full hop-by-hop filtering refresh owned targets, streamed upload metering, full hop-by-hop filtering
for complex values, TLS/H2/H3 authority handling and request limits. for complex values, TLS/H2/H3 authority handling and request limits.
- OPT3 compiled binary passed 151 live HTTP checks, 0 failed, 1 skipped - OPT3 compiled binary passed 151 live HTTP checks, 0 failed, 1 skipped
(optional Zstandard). OPT4 Rust sources must still be compiled and benchmarked; (optional Zstandard). OPT4 was subsequently compiled and tested similarly;
see `docs/PROXY_1KB_OPT4_DEV_0.34.0.md`. see `docs/PROXY_1KB_OPT4_DEV_0.34.0.md` and newer OPT5 analysis.
- DEV 1 KiB throughput follow-up (OPT3): keep normalized safe routing - DEV 1 KiB throughput follow-up (OPT3): keep normalized safe routing
+17 -4
View File
@@ -1,12 +1,25 @@
# RustProxy 0.34.0 # RustProxy 0.34.0
## DEV: OPT4 small-response proxy hot path (uncompiled sources) ## DEV: OPT5 multi-hot-path tuning (source candidate; uncompiled)
The compiled OPT3 binary passed 151 real HTTP acceptance checks (1 optional OPT5 builds on the **compiled OPT4** (151/151 executed live HTTP checks passed;
1 optional Zstandard check skipped). The shared-host 1,040-byte HTTP/1.1
comparison shows OPT4 at about 39,054 RPS with 16 connections and 36,425 RPS
with 64; mixed results versus OPT3 mean there is no proven broad RPS gain.
OPT5 removes unnecessary ID hashing and unauthenticated original-URL
construction, avoids per-success health-detail allocation, batches bounded
traffic analytics, avoids routine status/User-Agent allocations, and fast-paths
unoffered response compression. All policies, observability, caches and
protocol support remain enabled; no version bump. **OPT5 has not been built
or measured.** See [`docs/PROXY_1KB_OPT5_DEV_0.34.0.md`](docs/PROXY_1KB_OPT5_DEV_0.34.0.md).
## DEV: OPT4 small-response proxy hot path (subsequently compiled)
The compiled OPT4 binary passed 151 real HTTP acceptance checks (1 optional
Zstandard case skipped). OPT4 removes avoidable allocations and string work Zstandard case skipped). OPT4 removes avoidable allocations and string work
for ordinary uncached HTTP/1.1 requests, with regressions for empty request for ordinary uncached HTTP/1.1 requests, with regressions for empty request
bodies, URI/Host/Connection semantics and Unicode log truncation. OPT4 has bodies, URI/Host/Connection semantics and Unicode log truncation. Its brief
**not been compiled or RPS-measured**. No settings or functionality disabled; shared-host RPS comparison is mixed, so no stable improvement is claimed. No settings or functionality disabled;
version remains 0.34.0. Details: version remains 0.34.0. Details:
[`docs/PROXY_1KB_OPT4_DEV_0.34.0.md`](docs/PROXY_1KB_OPT4_DEV_0.34.0.md). [`docs/PROXY_1KB_OPT4_DEV_0.34.0.md`](docs/PROXY_1KB_OPT4_DEV_0.34.0.md).
+13 -2
View File
@@ -2,14 +2,25 @@
## 0.34.0 - Scoped cache correctness and proxy body hot path ## 0.34.0 - Scoped cache correctness and proxy body hot path
- [x] Compile supplied OPT4 binary; run real HTTP regression (151 passing,
0 failed, 1 skipped) and a short OPT3/OPT4/nginx 1,040-byte diagnostic run.
- [x] OPT5 source: remove per-request SHA-256 while retaining CSPRNG entropy,
skip Tinyauth-only original URL work when Tinyauth is absent, avoid a pooled
backend success-detail allocation, batch analytics processing, use borrowed
status/UA labels, avoid scoped compression resolution for unaccepted codecs.
- [x] Preserve exact forwarding and analytics semantics with new Rust tests;
retain 0.34.0, TLS/H2/H3, WebSocket, cache, ACL, logs and bounded queues.
- [ ] Compile and test OPT5; run full real-protocol regression and >=7
30-second CPU-isolated A/B/nginx runs before accepting a throughput gain.
- [x] OPT4 source candidate: borrow original no-rewrite public target, - [x] OPT4 source candidate: borrow original no-rewrite public target,
bypass the request body adapter only for bodies reporting end-of-stream, bypass the request body adapter only for bodies reporting end-of-stream,
avoid redundant upstream URI refcount operations, and simplify common avoid redundant upstream URI refcount operations, and simplify common
forwarding/header/log branches without weakening validation. forwarding/header/log branches without weakening validation.
- [x] Add OPT4 regression tests for body accounting and permits, Unicode, - [x] Add OPT4 regression tests for body accounting and permits, Unicode,
URI identity, and safe Connection header filtering. URI identity, and safe Connection header filtering.
- [ ] Compile and run OPT4 Rust tests, re-test ACL/TLS/H2/H3/WebSocket/cache; - [ ] Complete OPT4/OPT5 Rust unit suites and extended TLS/H2/H3/WS/LB
collect A/B-nginx RPS and profiling evidence before accepting a gain. acceptance; the short OPT4 RPS comparison did not prove a stable gain.
- [x] DEV 1 KiB: remove shared HOT store mutex from cache outcome/reason - [x] DEV 1 KiB: remove shared HOT store mutex from cache outcome/reason
+20 -18
View File
@@ -2,12 +2,12 @@
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore 9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
46bab1d2c2d6e0a80d57a6b232b1ba4c220884b95cd45ddccbafce2aa160cddb AGENTS.md 46bab1d2c2d6e0a80d57a6b232b1ba4c220884b95cd45ddccbafce2aa160cddb AGENTS.md
16b657a20824fd310c1eab6498b0b0db6f250a56b09c556d9c149ce1f61f0c0a CHANGELOG.md 3e46648fd8c91548ea77dcac4f6cbdbc5741c64b33240c44a50c138597880e99 CHANGELOG.md
ca201e3cd3f9842eae0006d0b40a0a72bcd3d4da6e4e5ecaf200d1b9a1390db2 Cargo.lock ca201e3cd3f9842eae0006d0b40a0a72bcd3d4da6e4e5ecaf200d1b9a1390db2 Cargo.lock
fec5f1bae9a59ef1fabd27c5e45d31448dd8f811736eebfa8826b3806a7e3349 Cargo.toml fec5f1bae9a59ef1fabd27c5e45d31448dd8f811736eebfa8826b3806a7e3349 Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile 25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
73194a4738e42a3c36623e750022bd2ef6dc9483c287c583a0a172d815962454 README.md d48bcdcda6d9261d7b358c39b8bdf93d39b722b3751af3b5ebe192e16bd8b4e6 README.md
2c387b854dc5433c3dd4a8cd1e70272f2fd8c5d5a483cd9f7fe9b4ceeed95c36 ROADMAP.md 7bba42713fa944908f5f3611841bf6b36377240ae4b77ec692ff48ea662b44f9 ROADMAP.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
@@ -27,7 +27,7 @@ e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/n
5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go 5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go
875faa3969df9409d79f050a84b4183496f557edda3d34aaec19fe556cf8c471 docs/ACME_PROVIDERS.md 875faa3969df9409d79f050a84b4183496f557edda3d34aaec19fe556cf8c471 docs/ACME_PROVIDERS.md
ec15163f5f51c9212003b7edd1fc380321311c2496fbaa9b45c838281b9af475 docs/ACME_RFC2136.md ec15163f5f51c9212003b7edd1fc380321311c2496fbaa9b45c838281b9af475 docs/ACME_RFC2136.md
e21a5c3cea78386fcb3ebd6516a66eed2ecd1966481ae0d3b2f57fa01010542e docs/API.md 00aef5eea37a8f56d7ea95e63ddcfdf39bac041168af29582b5ab2ab62d4c81e docs/API.md
f27053f8a93d7883025a04922d015e0b697cad653e714be632b757e167297de1 docs/API_ACCESS.md f27053f8a93d7883025a04922d015e0b697cad653e714be632b757e167297de1 docs/API_ACCESS.md
d30551a52ffd34c5963094d2826ba39795210a4e6a4e4d7333066dae7456fc50 docs/ARCHITECTURE.md d30551a52ffd34c5963094d2826ba39795210a4e6a4e4d7333066dae7456fc50 docs/ARCHITECTURE.md
fb99fac0a490af262a0ecdad593ffe5164004c2166fe7ddd607476bcc56a1623 docs/CACHE.md fb99fac0a490af262a0ecdad593ffe5164004c2166fe7ddd607476bcc56a1623 docs/CACHE.md
@@ -43,10 +43,10 @@ f749f7bd2eb4ae9c484f77a3cd8cd5ff21445319a3938e0384a3414134fe713a docs/FILE_LOGS
36599e0b59164e662442f3e8203c16b3123d09b7684d8ecdbffce42d22cc221b docs/HTTP3.md 36599e0b59164e662442f3e8203c16b3123d09b7684d8ecdbffce42d22cc221b docs/HTTP3.md
a00ec3c1d3b28ba10677435128e031e5f18fe8c5cef2ac90eb0f6779cefeb434 docs/INSTALL.md a00ec3c1d3b28ba10677435128e031e5f18fe8c5cef2ac90eb0f6779cefeb434 docs/INSTALL.md
3b5c31c805e5aee0dc2100b81246a9b631b5abb996faa598ae94f87ba39305f3 docs/INTEGRATIONS_040.md 3b5c31c805e5aee0dc2100b81246a9b631b5abb996faa598ae94f87ba39305f3 docs/INTEGRATIONS_040.md
4bb56add544f15d54695cf797cd16a51c09024360cdeef803a88988f4a54b3a5 docs/LIVE_LOGS.md bd85d4040fb7d213d7983596ad6f207cecbb03bfcfc399a4333f1e3fdf9211b9 docs/LIVE_LOGS.md
7c35c4cdbe7bbb81d94f3bf6aeb22b8a866af3e7f0d62bbedc299e4d6f264e0d docs/MANAGEMENT.md 7c35c4cdbe7bbb81d94f3bf6aeb22b8a866af3e7f0d62bbedc299e4d6f264e0d docs/MANAGEMENT.md
d8829cc68d982c933d04207cf7bc78cc918044c72e024e7ecad5220953bb3a79 docs/MEMORY_STRESS.md d8829cc68d982c933d04207cf7bc78cc918044c72e024e7ecad5220953bb3a79 docs/MEMORY_STRESS.md
e36247774051aa9c83c1a2fda91c1f7857991dcaa4fa90320ca5b492e31dbcf8 docs/MIGRATION.md 5551dc403f1b450a5d4bfd48b1130971751ec0ec51e4fc9b2f5cff1971a212ab docs/MIGRATION.md
5f090a190000c6badab2a74e5d4968e70c34de43b59c1452f5b05503b5c5c18a docs/MOCK_AUTH.md 5f090a190000c6badab2a74e5d4968e70c34de43b59c1452f5b05503b5c5c18a docs/MOCK_AUTH.md
1f85fac0e06e432ef6f744f0b5f9c1b7a27a1e9e1ad30dc1a218eab96b1caffe docs/MOCK_SERVER.md 1f85fac0e06e432ef6f744f0b5f9c1b7a27a1e9e1ad30dc1a218eab96b1caffe docs/MOCK_SERVER.md
6cd54c6e2c3e5c23ae0104e09e88ab6d93bde26bd92b23c1a078fe7c4ee04b55 docs/NGINX_GAP_0.34.0.json 6cd54c6e2c3e5c23ae0104e09e88ab6d93bde26bd92b23c1a078fe7c4ee04b55 docs/NGINX_GAP_0.34.0.json
@@ -60,6 +60,7 @@ ad6e3f003b820a4f45e30e39577d120a69d167c70910bda4891971759458c3dd docs/PROMETHEU
f13ee0005d57d2576ccc970745f17069096df4afa23d712ea0f748da85433f7d docs/PROXY_1KB_HOTPATH_DEV_0.34.0.md f13ee0005d57d2576ccc970745f17069096df4afa23d712ea0f748da85433f7d docs/PROXY_1KB_HOTPATH_DEV_0.34.0.md
c9bbdd1cd8df02b2a19016b7bbeee1cf55917614ac672f1f94e0a05db7d309ef docs/PROXY_1KB_OPT3_DEV_0.34.0.md c9bbdd1cd8df02b2a19016b7bbeee1cf55917614ac672f1f94e0a05db7d309ef docs/PROXY_1KB_OPT3_DEV_0.34.0.md
27002c4d4e19bb7d58521b8ba4cd5faf180d2917b6ac200dd7fc3664c2060145 docs/PROXY_1KB_OPT4_DEV_0.34.0.md 27002c4d4e19bb7d58521b8ba4cd5faf180d2917b6ac200dd7fc3664c2060145 docs/PROXY_1KB_OPT4_DEV_0.34.0.md
8c26aa9ebebf54d912749e3336e548b59d805997366be2324157c3e56b887f5f docs/PROXY_1KB_OPT5_DEV_0.34.0.md
deea89aed410c0619bdbdb12437edc7aff9dca2fec7fdc059e918c649fc36b51 docs/PROXY_HOTPATH_0.34.0.md deea89aed410c0619bdbdb12437edc7aff9dca2fec7fdc059e918c649fc36b51 docs/PROXY_HOTPATH_0.34.0.md
a7d6189b0e8cb854918456e0063101ee77bfb2419a9ba0430eb0e3f8678c0f09 docs/PROXY_RPS_0.34.0.md a7d6189b0e8cb854918456e0063101ee77bfb2419a9ba0430eb0e3f8678c0f09 docs/PROXY_RPS_0.34.0.md
887d1fdcc3b546b024afcd18b1716b8e3a74a3800012bf4bda7a122d2eabe233 docs/README.md 887d1fdcc3b546b024afcd18b1716b8e3a74a3800012bf4bda7a122d2eabe233 docs/README.md
@@ -187,8 +188,9 @@ b0136544af717b75df2cb107900ea65c3f945fdd0040369dbb93919008fb954c scripts/tests/
8d1903e2b17b3d59cd0a5bec3e298e468b3d88ed2dda8b724239cc45d8353c59 scripts/tests/progressbar_0332_browser_test.py 8d1903e2b17b3d59cd0a5bec3e298e468b3d88ed2dda8b724239cc45d8353c59 scripts/tests/progressbar_0332_browser_test.py
7fc86a34a47c6e1f0f9fe2748d0ea7f59e455f8bf4e5ad081e185356e43721b0 scripts/tests/proxy_1kb_opt3_static_test.py 7fc86a34a47c6e1f0f9fe2748d0ea7f59e455f8bf4e5ad081e185356e43721b0 scripts/tests/proxy_1kb_opt3_static_test.py
ec05272ad8c14c7a0265a84cc7b6ebc0dca0651413106d655681681873b88fc8 scripts/tests/proxy_1kb_opt4_static_test.py ec05272ad8c14c7a0265a84cc7b6ebc0dca0651413106d655681681873b88fc8 scripts/tests/proxy_1kb_opt4_static_test.py
37306b175190aa72b143d263060fa6441244eb40b2bcb54c82071cc87d73b54d scripts/tests/proxy_1kb_opt5_static_test.py
2a6e75c05f3860acf65ea8132f0083904478b690145115ec017c120d70f26cca scripts/tests/proxy_hotpath_034_static_test.py 2a6e75c05f3860acf65ea8132f0083904478b690145115ec017c120d70f26cca scripts/tests/proxy_hotpath_034_static_test.py
6fa2f22816996e989dad9dc7884adbf24f02f0ca6a6634b06e219eb08526c7fe scripts/tests/rps_hotpath_034_static_test.py 0c50def966cbc95c22339d1c506763fab4d9e5c176a161205ab88d79af5be920 scripts/tests/rps_hotpath_034_static_test.py
5be5cdc60324f4f0f6638640000b888a5babb0a9bbf29511c824a0ba38878c2a scripts/tests/sendfile_034_panel_browser_test.py 5be5cdc60324f4f0f6638640000b888a5babb0a9bbf29511c824a0ba38878c2a scripts/tests/sendfile_034_panel_browser_test.py
978789bfe89d43034dfebddba49b9b17daedb1be031732734f95375e56c78306 scripts/tests/sendfile_034_static_test.py 978789bfe89d43034dfebddba49b9b17daedb1be031732734f95375e56c78306 scripts/tests/sendfile_034_static_test.py
1bb99b148d0297235b707f16865ca077329246fc19ecbb20a143bdeb56a23f37 scripts/tests/sidebar_nav_0333_browser_test.py 1bb99b148d0297235b707f16865ca077329246fc19ecbb20a143bdeb56a23f37 scripts/tests/sidebar_nav_0333_browser_test.py
@@ -326,12 +328,12 @@ c96a87e82ff0c1c9ce3d0cd5135903bb53af78d1c4fb6ebfe5fea503bf5644ea src/auth/store
f5afae218f3a6f91bc2f1f5a90094630ce5b7ca574de61807b5a317b13937ec2 src/backup/validation.rs f5afae218f3a6f91bc2f1f5a90094630ce5b7ca574de61807b5a317b13937ec2 src/backup/validation.rs
426a946b2c9e244f09f6cbc5f9664b8ad0737529a9bb5a3d549d27196a1f8043 src/balance.rs 426a946b2c9e244f09f6cbc5f9664b8ad0737529a9bb5a3d549d27196a1f8043 src/balance.rs
ca1e03b56a872dca37ed9d05ead41c1538a808e80b09fb9494823c443cd51c61 src/balance/health.rs ca1e03b56a872dca37ed9d05ead41c1538a808e80b09fb9494823c443cd51c61 src/balance/health.rs
109c55f9af8918d4f314f0796e19de06f3129eefee3ef405770b99fc368f2aee src/balance/lease_runtime.rs 70c44291960b835c7a456cf066469b0002e1e35ba497663361d659d452a52e5e src/balance/lease_runtime.rs
aba0f4ae46f05b07bfc434b7174553ce20e5187e1b40a6cf946eb2aefbe1840b src/balance/model.rs aba0f4ae46f05b07bfc434b7174553ce20e5187e1b40a6cf946eb2aefbe1840b src/balance/model.rs
8550e5d64aa6fee442cea3e8e243fc4cfc1e8aa39726bc5cc6b2afa9307e3d05 src/balance/pool_core.rs 8550e5d64aa6fee442cea3e8e243fc4cfc1e8aa39726bc5cc6b2afa9307e3d05 src/balance/pool_core.rs
477132dd381e23f1be197c32ff58a0925f41d3e6aa63fe77c10973cefb2b76f6 src/balance/pool_selection.rs 477132dd381e23f1be197c32ff58a0925f41d3e6aa63fe77c10973cefb2b76f6 src/balance/pool_selection.rs
8abb6048303ee8c022546a528d5693167766e7450efd8e20f37ec6fd0fa9ef5e src/balance/pool_view.rs 8abb6048303ee8c022546a528d5693167766e7450efd8e20f37ec6fd0fa9ef5e src/balance/pool_view.rs
2ae82db4e3ff2d8df1f6fb3786b54ff61ed4a0e8d725d50fbb080b4fa0e641c6 src/balance/tests.rs 814be4263c35b84de2b3c3bb3c60d7ead9f593154ac85aa6c8ae35700ee591c7 src/balance/tests.rs
5049d2bd01a9c866f3bad2288d8d594df96bba6045dfe15cd22b4173562b7388 src/basic_auth.rs 5049d2bd01a9c866f3bad2288d8d594df96bba6045dfe15cd22b4173562b7388 src/basic_auth.rs
9a8858b7ff461a4d961ba701ca3ed6eaec15c3f7f05eef8e0e30edc63617605a src/basic_auth/tests.rs 9a8858b7ff461a4d961ba701ca3ed6eaec15c3f7f05eef8e0e30edc63617605a src/basic_auth/tests.rs
7def03ace07dc38a4806277d75b6ce673d33ddaaf1c4855fa0a3372be1cf9489 src/cache/cold.rs 7def03ace07dc38a4806277d75b6ce673d33ddaaf1c4855fa0a3372be1cf9489 src/cache/cold.rs
@@ -374,7 +376,7 @@ d4429cf4f35fd8d0082108fb62602fde72c9c2fc6edfadfe3268c6a8214a8f89 src/cache/test
af28534625859ff66011e10c54cff8f82c2cfa1c173c4d99b89c27c1437f0659 src/cache/tests/v022_cache_tests.rs af28534625859ff66011e10c54cff8f82c2cfa1c173c4d99b89c27c1437f0659 src/cache/tests/v022_cache_tests.rs
f76b41a2b0ffa5699a456cc2cfa0b063098c90552fe01e0d05e6ee91a01cbe57 src/cache/tickets.rs f76b41a2b0ffa5699a456cc2cfa0b063098c90552fe01e0d05e6ee91a01cbe57 src/cache/tickets.rs
e0146bf5d2e2e33aedcccd9205e5bd457559c9d77dc7ce266f8c165d1b867f0f src/compression.rs e0146bf5d2e2e33aedcccd9205e5bd457559c9d77dc7ce266f8c165d1b867f0f src/compression.rs
4bdbde197d135fee8be20e93427eba8ca7b60b801717ed7ca734319bedb6ef05 src/compression/tests.rs 2bb1ea392dc18553a03a6ae1c53f52712ae86571ea31b1bcd93c8fdec2cb60ec src/compression/tests.rs
7bc8285cb2012506fd1a38a71dffa561f885f4ab65a9b2a7c8344319e3cd1cf6 src/config.rs 7bc8285cb2012506fd1a38a71dffa561f885f4ab65a9b2a7c8344319e3cd1cf6 src/config.rs
56b3d37e7447995bec2fdda1c17dcc74b60958a63ca88b88ff57e56347d8b0b9 src/config/backend.rs 56b3d37e7447995bec2fdda1c17dcc74b60958a63ca88b88ff57e56347d8b0b9 src/config/backend.rs
beb831a208c4e5f7ebe8327686b1e0edc66608c7032f895cb4fc3366b189aae0 src/config/compile.rs beb831a208c4e5f7ebe8327686b1e0edc66608c7032f895cb4fc3366b189aae0 src/config/compile.rs
@@ -395,8 +397,8 @@ b45d6040810c93ae2acb04ef6b0a59c189751edf9008cd81a4521c9e04e7f07d src/docker.rs
4c2c988191a97f81568a8df64af772463d010844f1abf327871901cdb32d4336 src/docker/model.rs 4c2c988191a97f81568a8df64af772463d010844f1abf327871901cdb32d4336 src/docker/model.rs
5f4610c59b2d4ff6a205a2d9ae11bc84be689b2a68302459f546b3f43e53593c src/docker/reconcile.rs 5f4610c59b2d4ff6a205a2d9ae11bc84be689b2a68302459f546b3f43e53593c src/docker/reconcile.rs
a12c47df4b90b6fc36c978a42477e6ddffbbbf69aed7252101c2fd829709bd6e src/docker/tests.rs a12c47df4b90b6fc36c978a42477e6ddffbbbf69aed7252101c2fd829709bd6e src/docker/tests.rs
2cb42ac0a5444fdcfea34c616dd6fe5f5ea1d06c84bcd64714ddf096830ec99f src/forwarding.rs ae46f4a971119d300d79fa506ed116054313334484546ba2f411e893e4e7586c src/forwarding.rs
b25fbb00de0e37ad5fa1a049839265e5f7e9f83b6214c40a95f57ea4807685bc src/forwarding/tests.rs 4622bf34a316fca5dd524d229652a1fef8b1828f34e2c3e9bc0e35fe03b6a393 src/forwarding/tests.rs
af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.rs af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.rs
80850f7acd1ed4c0048b065e9e81740869a8efbb0d9b5d2e83d8ec7fbc494703 src/geoip/mmdb.rs 80850f7acd1ed4c0048b065e9e81740869a8efbb0d9b5d2e83d8ec7fbc494703 src/geoip/mmdb.rs
06aa056d789742cc11c8826c338d985e6a6fb22da6c3530937eec38e35478034 src/geoip/mmdb_tests.rs 06aa056d789742cc11c8826c338d985e6a6fb22da6c3530937eec38e35478034 src/geoip/mmdb_tests.rs
@@ -456,20 +458,20 @@ cfa941b60b5d167ec9d8e2566ea3342b588d1a9078a6ca711ad6062ddf00fec9 src/proxy/forw
0a921474ef2b93eabdaf3888651cf29130eace5ccb1e0f5d6bc25e6aa69280be src/proxy/forward/success_prepare.rs 0a921474ef2b93eabdaf3888651cf29130eace5ccb1e0f5d6bc25e6aa69280be src/proxy/forward/success_prepare.rs
3f21439bbe4966fff43b3ebce8b59fa76ecce376197b4c56a2dce3a6d98f5556 src/proxy/forward/upstream.rs 3f21439bbe4966fff43b3ebce8b59fa76ecce376197b4c56a2dce3a6d98f5556 src/proxy/forward/upstream.rs
bb8c9117e05b0cb4bbfe029ef4d1a031fc755cdce255b15dad072afd1c845737 src/proxy/forward/web_source.rs bb8c9117e05b0cb4bbfe029ef4d1a031fc755cdce255b15dad072afd1c845737 src/proxy/forward/web_source.rs
2b6ae70b84fd1eba0dba6d8d7e2db62227193dc5b77e4a6111ceab1b219800c2 src/proxy/forward_entry.rs 2c681e48d6be7db784b65cc186bced40f1b6c777d97a4d222c653008b8406a31 src/proxy/forward_entry.rs
9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs 9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs
cc7fe83f42acb19d79abe83107427cdc6b0ed14083702f4fe66af22f7cdfc4cc src/proxy/headers.rs cc7fe83f42acb19d79abe83107427cdc6b0ed14083702f4fe66af22f7cdfc4cc src/proxy/headers.rs
0f9314d1380febe436def36b5f381731e97cbac90625637575f38f3d72eaaace src/proxy/headers/tests.rs 0f9314d1380febe436def36b5f381731e97cbac90625637575f38f3d72eaaace src/proxy/headers/tests.rs
3f25429418941eb72c3847125340db74684ad316eb278ecf658953026fd41f4f src/proxy/mod.rs 3f25429418941eb72c3847125340db74684ad316eb278ecf658953026fd41f4f src/proxy/mod.rs
55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs 55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs
01b3027cfe92512f71be056a1ae26749b3ad3417d43388f1c05dcf5884d83966 src/proxy/request_id.rs 2280841494c3e14ccc5e3f02b026fe3c4328a3dab04ab449b9286bb69e39c028 src/proxy/request_id.rs
d2df55b6ccfc7ceb8a3d2c49f664acd183448e3d932d5399d6692e87f79a094b src/proxy/request_id/tests.rs b9a2307c6aa7f16613d935508a61574dfee964020b1b5be86293df2d684615d8 src/proxy/request_id/tests.rs
e8d1210a6b8e8c6a388d016d22e7c18255cf737c5583124b35cd50bd2592f23c src/proxy/tests/authority_tests.rs e8d1210a6b8e8c6a388d016d22e7c18255cf737c5583124b35cd50bd2592f23c src/proxy/tests/authority_tests.rs
1aa9bbbfb2659facdaeaf185e11221b1c8cd5fe6e23c7c1dacd3aa55e3db4fcb src/proxy/tests/cache_fast_path_tests.rs 1aa9bbbfb2659facdaeaf185e11221b1c8cd5fe6e23c7c1dacd3aa55e3db4fcb src/proxy/tests/cache_fast_path_tests.rs
0f2eacadf1447db8aa1c1a78005aa262e0f747096bfe7e0a0e41a2cd31a0d17f src/proxy/tests/h3_navigation_tests.rs 0f2eacadf1447db8aa1c1a78005aa262e0f747096bfe7e0a0e41a2cd31a0d17f src/proxy/tests/h3_navigation_tests.rs
d134b0eb617f82f9971b3f3461379ea37c57bd0079bf54748d0c434b2ef1b475 src/proxy/tests/http_invalidation_tests.rs d134b0eb617f82f9971b3f3461379ea37c57bd0079bf54748d0c434b2ef1b475 src/proxy/tests/http_invalidation_tests.rs
a74815a0b46d1c94ffbf8cc3a8bbbc967daa4aec0981139d2d92624c10553026 src/proxy/types.rs a74815a0b46d1c94ffbf8cc3a8bbbc967daa4aec0981139d2d92624c10553026 src/proxy/types.rs
9a8e38e88bff389e448f54dc07e63fef9652acc72e05833363668c28949daccf src/proxy/upstream_helpers.rs b45cf5baf45e37915c8cfac43e845976f874f0aa5474f167fa943c0f38decade src/proxy/upstream_helpers.rs
2d9142e3ae95895b5271b4009ab48236a3a2074ce51179c021193b35777560e4 src/proxy/web_cache_revalidation.rs 2d9142e3ae95895b5271b4009ab48236a3a2074ce51179c021193b35777560e4 src/proxy/web_cache_revalidation.rs
d7b4b809b12ee89293df45e9a64b1c9981fb3f89deaab3762f4a3ad3992dd7b1 src/proxy/web_cache_revalidation/tests.rs d7b4b809b12ee89293df45e9a64b1c9981fb3f89deaab3762f4a3ad3992dd7b1 src/proxy/web_cache_revalidation/tests.rs
69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs 69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs
@@ -523,8 +525,8 @@ d9123c623e2a97d09e41edaa29993b4b2c8cba39a3abdf2b48a205aa9a9c2939 src/system_sta
2af2164ecdf2b7fd9eef7c6e358dec053dfac9aedaa501dbb75df742d18638b2 src/tls/tests/acme.rs 2af2164ecdf2b7fd9eef7c6e358dec053dfac9aedaa501dbb75df742d18638b2 src/tls/tests/acme.rs
93b359e9defd62506729bc90e56c96783e2f14dd477dd67f337ca83149da7168 src/tls/tests/external_paths.rs 93b359e9defd62506729bc90e56c96783e2f14dd477dd67f337ca83149da7168 src/tls/tests/external_paths.rs
10c4a187e3aeb0c72a8c8968ea0cc8bfae4e3cb2f3d834ebd3da1056c4171ec5 src/tls/types.rs 10c4a187e3aeb0c72a8c8968ea0cc8bfae4e3cb2f3d834ebd3da1056c4171ec5 src/tls/types.rs
0d2ce0a7f5200437a1f965c62f72c91a681949a0bdec8ef4ccdb837a55626ed7 src/traffic.rs 6ae584944343b83ee3a2772cd93c985ddeaece7a9e6467b7cdbf5da22ee88028 src/traffic.rs
aa6cea1b22cb5805934e1f819e9c5254a7b4f745025842144f6e6ae671eb170d src/traffic/tests.rs e796efe9d94e2e79220287196fe2d2d9f4a1c7640b9f71652d409478e25d3c09 src/traffic/tests.rs
d88c92f5a2a7773bc3e715d24039db35da4df53881f1a0859eec819b96f06d3b src/transform/apply.rs d88c92f5a2a7773bc3e715d24039db35da4df53881f1a0859eec819b96f06d3b src/transform/apply.rs
87f8af5261d79072f8215f1fb8a90715a58e7626544fd8176ce840e3111bdb92 src/transform/compile.rs 87f8af5261d79072f8215f1fb8a90715a58e7626544fd8176ce840e3111bdb92 src/transform/compile.rs
b2c5236589093454ec60984b785ad6a4af6149d47f6db37d8e08c17fec7a2c1b src/transform/css.rs b2c5236589093454ec60984b785ad6a4af6149d47f6db37d8e08c17fec7a2c1b src/transform/css.rs
+1 -1
View File
@@ -585,7 +585,7 @@ when enabled. Transformed cached representations add the `transformed` Cache-Sta
extension parameter. `X-Cache-Status` is stripped if supplied by an origin but is not extension parameter. `X-Cache-Status` is stripped if supplied by an origin but is not
emitted by RustProxy. emitted by RustProxy.
`diagnostic_headers.hide_mock_origin` is removed and old documents containing it are rejected on `diagnostic_headers.hide_mock_origin` is removed and old documents containing it are rejected on
export; `request_id` is the only diagnostic-header switch. The generated request ID is a 64-hex-character SHA-256 value and the same protected value is forwarded to the origin as `X-Request-ID`. No revision response-header behavior remains. TLS listener export; `request_id` is the only diagnostic-header switch. The generated request ID is a cryptographically unpredictable 64-hex-character value and the same protected value is forwarded to the origin as `X-Request-ID`. (The OPT5 DEV implementation replaces the extra SHA-256 digest with CSPRNG output mixed with the per-process sequence, retaining the length and unpredictability.) No revision response-header behavior remains. TLS listener
http3 omission is default-on; explicit null is persisted off. Legacy listeners use http3 omission is default-on; explicit null is persisted off. Legacy listeners use
https_http3 with identical default/null behavior. https_http3 with identical default/null behavior.
+1 -1
View File
@@ -15,7 +15,7 @@ The CLI first probes the loopback administration `/healthz` endpoint over HTTP.
This fixes the previous reconnect loop where `--livelogs` treated the TLS listener's expected HTTP-to-HTTPS `308` response as a stream failure. The same local transport selection is used by CLI config and certificate API operations. This fixes the previous reconnect loop where `--livelogs` treated the TLS listener's expected HTTP-to-HTTPS `308` response as a stream failure. The same local transport selection is used by CLI config and certificate API operations.
The live path is separate from configured file/syslog sinks and from the `/logs` stored-log explorer. Request processing publishes ordinary routed request records into a bounded Tokio broadcast channel without waiting for viewers. Low-level malformed HTTP/TLS/HTTP2/HTTP3 protocol failures introduced in 0.26.0 are intentionally persistent-sink-only and do not enter this Live Logs ring, so scanner noise cannot evict normal live request history. A slow viewer receives an explicit `gap` event with the number of entries lost from that viewer's live queue. The CLI also consults the retained history after connecting/reconnecting and de-duplicates entries by request id. Each request now carries a fresh 64-hex-character SHA-256 `request_id`; the same protected value is forwarded to the backend as `X-Request-ID` for correlation. The live path is separate from configured file/syslog sinks and from the `/logs` stored-log explorer. Request processing publishes ordinary routed request records into a bounded Tokio broadcast channel without waiting for viewers. Low-level malformed HTTP/TLS/HTTP2/HTTP3 protocol failures introduced in 0.26.0 are intentionally persistent-sink-only and do not enter this Live Logs ring, so scanner noise cannot evict normal live request history. A slow viewer receives an explicit `gap` event with the number of entries lost from that viewer's live queue. The CLI also consults the retained history after connecting/reconnecting and de-duplicates entries by request id. Each request carries a fresh, cryptographically unpredictable 256-bit (64-hex-character) `request_id`, generated by a thread-local CSPRNG and mixed with a per-process sequence; the same protected value is forwarded to the backend as `X-Request-ID` for correlation. From the OPT5 DEV revision onward, this format no longer requires a separate per-request SHA-256 operation.
## User-Agent, country and Traffic analytics ## User-Agent, country and Traffic analytics
+1 -1
View File
@@ -70,7 +70,7 @@ There is no SQLite schema or saved-configuration migration. Schema 8 and the exi
SQLite schema 7 is upgraded automatically to schema 8. The migration adds `configuration_history`; on first open the current revision is seeded into history, and subsequent successful configuration saves retain the newest 512 revisions for the dashboard revision-history view. No manual SQL is required. SQLite schema 7 is upgraded automatically to schema 8. The migration adds `configuration_history`; on first open the current revision is seeded into history, and subsequent successful configuration saves retain the newest 512 revisions for the dashboard revision-history view. No manual SQL is required.
New vhost/location access fields are additive. Missing `method_policy`, location `acl_profile_id`, location country policy and location inline ACL fields retain the prior behavior. RustProxy-generated request IDs are now SHA-256 values and are forwarded to origins as protected `X-Request-ID`; custom request-header rules cannot replace that correlation identifier. New vhost/location access fields are additive. Missing `method_policy`, location `acl_profile_id`, location country policy and location inline ACL fields retain the prior behavior. In that release RustProxy-generated request IDs became SHA-256 values forwarded to origins as protected `X-Request-ID`; custom request-header rules cannot replace that correlation identifier. OPT5 DEV 0.34.0 retains the same 64-hex-character protected identifier but produces it directly from the CSPRNG mixed with a sequence, without the additional SHA-256 digest.
## 0.25.1 -> 0.25.2 ## 0.25.1 -> 0.25.2
+93
View File
@@ -0,0 +1,93 @@
# RustProxy 0.34.0 DEV — OPT5, 1,040-byte reverse-proxy optimization
## Acceptance boundary
The source package preserves **0.34.0** and all existing configurable services,
logs, dashboards, cache/ACL/auth policies, streaming, TLS/H2/H3/WebSocket,
failover, retry and bounded-resource behavior. OPT5 is a **source-only** candidate:
no Rust compiler is available in the editing environment. The new code's
compilation, Rust unit tests, binary throughput and full-protocol interoperability
are unverified until the developer builds it. The compiled OPT4 executable is
the known test input, **not** a benchmark of OPT5.
## Compiled OPT4 input verification
Input: `/mnt/data/rust-proxy(4)`, SHA-256
`1c4c701f11b57b1293020061c8af21d5a9df0c86f03290018a90edc007667b24`.
- The real HTTP regression runner completed **151 passing**, **0 failing** cases;
**1 skipped** because Python `zstandard` is not installed. Protocol tests for
TLS/H2/H3 are a separate acceptance gate and were **not** passed by this run.
- Same-host ApacheBench HTTP/1.1 keep-alive, 1,040-byte body verified by exact
SHA-256, cache OFF, 4 Tokio workers, 64 idle HTTP1 upstream connections,
3-second warm-and-measure samples, two repeats per variant/concurrency:
| RPS, median of 2 samples | 16 clients | 64 clients |
| --- | ---: | ---: |
| OPT3 | 37,017 | 40,668 |
| OPT4 | 39,054 | 36,425 |
| nginx | 99,808 | 110,783 |
| OPT4 vs OPT3 | +5.5% | -10.4% |
| OPT4 / nginx | 39.1% | 32.9% |
There were zero HTTP failures and the response body was verified before/after
measurements. Shared CPU and only two short samples are diagnostic, NOT a stable
speedup or production proof. The 100k RPS and 80%-of-nginx goals remain unmet.
## OPT5 changes (8 coherent hot-path optimizations)
1. `src/proxy/request_id.rs`: eliminate per-request SHA-256. Keep the existing
OS-seeded/reseeded thread-local CSPRNG and 256-bit ID; XOR a monotonic
sequence into the first 8 nonce bytes. Output remains uniformly random,
64 lowercase hex characters and the protected `X-Request-ID` correlation
remains. Random IDs continue to be unique with overwhelming probability.
2. `src/forwarding.rs` and `src/proxy/forward_entry.rs`: only construct
`Context.original_url` for a frontend with configured Tinyauth. This is the
*only* runtime consumer of that absolute URL. Public `context(...)` still
constructs it by default. Trusted/untrusted forwarding and all validation
still execute identically; IPv4/IPv6 and `x-original-url` for Tinyauth remain.
3. `src/balance/lease_runtime.rs`: borrow `"ok"` for successful backend health
observations instead of allocating a `String`. 5xx failures still record the
exact original `status=NNN` detail and slow/passive health counters are kept.
4. `src/traffic.rs`: receive 64 samples at a time (Tokio `recv_many`) and apply
them under a single dashboard-state mutex. Order within the existing bounded
2,048-sample queue is preserved; waiting is outside the mutex and producer
backpressure and drop accounting are unchanged.
5. `src/traffic.rs`: reuse map entries with borrowed vhost names. New keys are
cloned only when a domain first appears, preserving the max 64-vhost bound.
6. `src/traffic.rs`: classify status 1xx–5xx using static labels; preserve
unknown extension status labels such as `7xx`.
7. `src/traffic.rs`: replace per-sample lowercase allocation for User-Agent
family detection with ASCII-insensitive byte-window comparisons, and avoid
copying all TopTable maps when displaying a single vhost.
8. `src/proxy/upstream_helpers.rs`: skip resolving inherited/vhost/web-service
compression settings when no encoding is accepted even under the global
codec ceiling (for example `Accept-Encoding: identity`). Existing compression
handling is retained for every accepted codec and request method.
The caller's front-end policies remain evaluated on every request. No log,
metric, access control or authentication feature was disabled or made optional.
## Verification and outstanding gates
Static source, SQL migration and managed-upgrade tests are executed and their
outcomes supplied in the external evidence archive. New Rust test fixtures cover
request IDs, forwarding/trusted IPv6, compression, healthy/5xx backend detail,
analytics batching, top classes, country-independent User-Agent matching,
reset and the bounded queue.
To verify the new build on the developer's system:
```bash
./dev.sh check
CARGO_INCREMENTAL=0 ./dev.sh test
./dev.sh build
```
After building, repeat the actual HTTP regression, LB regression and TLS/H2/H3
transport tests and collect at least seven 30-second CPU-isolated, warmed,
interleaved OPT4/OPT5/nginx measurements with `wrk`, p95/p99 and CPU/RSS.
Treat the non-tested binaries, short shared-host tests and simulated Python
checks as distinct types of evidence. Measure the secondary Web Server path
separately. Do not alter the version or silently relax cache, auth or timeout
policies to reach a RPS target.
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""OPT5 invariants, static-only. Does not compile or benchmark Rust."""
from pathlib import Path
root = Path(__file__).resolve().parents[2]
def read(path):
return (root / path).read_text(encoding='utf-8')
assert 'version = "0.34.0"' in read('Cargo.toml')
identifier = read('src/proxy/request_id.rs')
assert 'rand::random()' in identifier
assert 'let mut token: [u8; 32]' in identifier
assert 'sequence.to_be_bytes()' in identifier
assert '*byte ^= sequence_byte;' in identifier
assert 'request_id_hex(&token)' in identifier
assert 'let mut encoded = vec![0u8; digest.len() * 2]' in identifier
assert 'Sha256::new()' not in identifier
forwarding = read('src/forwarding.rs')
assert 'self.context_with_original_url(incoming, peer, local, tls, authority, uri, true)' in forwarding
assert forwarding.count('if include_original_url {') == 2
assert 'let mut ips = chain(incoming).unwrap_or_default();' in forwarding
assert 'crate::config::request_host(authority_text)?;' in forwarding
assert 'frontend.is_some_and(|frontend| frontend.tinyauth.is_some())' in read('src/proxy/forward_entry.rs')
assert '&context.original_url' in read('src/tinyauth.rs')
lease = read('src/balance/lease_runtime.rs')
assert 'let failure_detail = is_5xx.then(|| format!("status={status}"));' in lease
assert 'let detail = failure_detail.as_deref().unwrap_or(reason);' in lease
assert 'observe_passive_with_detail(' in lease
assert 'lease.response(' in read('src/proxy/forward/upstream.rs') or 'lease.response(' in read('src/proxy/forward/success_prepare.rs')
analytics = read('src/traffic.rs')
for literal in ['mpsc::channel::<Sample>(QUEUE_CAPACITY)', 'recv_many(&mut batch, 64)',
'for sample in batch.drain(..)', 'get_mut(sample.vhost.as_str())',
'state.vhosts.len() < MAX_VHOSTS', 'try_reserve()',
'self.dropped.fetch_add(1, Ordering::Relaxed)',
'candidate.eq_ignore_ascii_case(needle.as_bytes())',
'2 => self.statuses.add("2xx", 1)', 'map(Counters::view)']:
assert literal in analytics, literal
assert 'let lower = value.to_ascii_lowercase();' not in analytics
helpers = read('src/proxy/upstream_helpers.rs')
assert 'preferred_encoding(&original_headers, global_compression).is_some()' in helpers
assert 'compression.resolve(' not in helpers
assert 'compression::encode(' in helpers
assert 'wrap_observed_request(body, &exchange' in helpers
for rel, case in [
('src/traffic/tests.rs', 'batched_worker_keeps_exact_counts_and_bounded_vhosts'),
('src/traffic/tests.rs', 'status_top_list_preserves_common_and_extension_family_names'),
('src/forwarding/tests.rs', 'original_url_is_omitted_only_when_unneeded_and_forwarding_is_identical'),
('src/compression/tests.rs', 'unaccepted_codecs_cannot_be_reenabled_by_scoped_overrides'),
('src/balance/tests.rs', 'successful_lease_response_keeps_passive_observability_and_failure_detail'),
('src/proxy/request_id/tests.rs', 'independent_sequential_ids_keep_full_length_and_unique_values'),
]:
assert case in read(rel), (rel, case)
for path in (root / 'src').rglob('*.rs'):
if path.name == 'tests.rs' or '/tests/' in path.as_posix():
continue
assert len(path.read_text(encoding='utf-8').splitlines()) <= 500, path
assert not read('src/cache/stats.rs').startswith('//!')
print('PASS OPT5 static source checks; Rust build and runtime acceptance still required')
+6 -4
View File
@@ -14,12 +14,14 @@ def source(path):
return (ROOT / path).read_text(encoding="utf-8") return (ROOT / path).read_text(encoding="utf-8")
# Random request identifiers retain CSPRNG entropy, SHA-256, format and sequence. # Random request identifiers still use the OS-seeded thread-local CSPRNG,
# preserve a 256-bit unpredictable 64-hex token and mix the per-process sequence.
# OPT5 removes the extra SHA-256 operation, not the entropy or correlation ID.
identifier = source("src/proxy/request_id.rs") identifier = source("src/proxy/request_id.rs")
assert "rand::random()" in identifier assert "rand::random()" in identifier
assert "hash.update(sequence.to_be_bytes());" in identifier assert "sequence.to_be_bytes()" in identifier
assert "hash.update(nonce);" in identifier assert "*byte ^= sequence_byte;" in identifier
assert "request_id_hex(&hash.finalize())" in identifier assert "request_id_hex(&token)" in identifier
assert "SysRng.try_fill_bytes" not in identifier assert "SysRng.try_fill_bytes" not in identifier
# Compiled/validated target authority survives request URI construction. # Compiled/validated target authority survives request URI construction.
+4 -7
View File
@@ -40,24 +40,21 @@ impl Lease {
"ok" "ok"
}; };
let before = self.member.state.healthy(); let before = self.member.state.healthy();
let failure_detail = if is_5xx { let failure_detail = is_5xx.then(|| format!("status={status}"));
format!("status={status}") let detail = failure_detail.as_deref().unwrap_or(reason);
} else {
reason.into()
};
let ejected = self.member.state.observe_passive_with_detail( let ejected = self.member.state.observe_passive_with_detail(
&self.health, &self.health,
&self.passive_health, &self.passive_health,
reason, reason,
reason, reason,
&failure_detail, detail,
elapsed, elapsed,
); );
if let Some((first, failures)) = ejected { if let Some((first, failures)) = ejected {
tracing::warn!(backend = %self.member.definition.id, origin = %self.member.backend.origin, source = "passive", tracing::warn!(backend = %self.member.definition.id, origin = %self.member.backend.origin, source = "passive",
reason, first_error_class = first.class, reason, first_error_class = first.class,
first_error_at_ms = first.at_ms, first_error_detail = %first.detail, first_error_at_ms = first.at_ms, first_error_detail = %first.detail,
trigger_error_detail = %failure_detail, failures, trigger_error_detail = %detail, failures,
"backend excluded from traffic"); "backend excluded from traffic");
} }
let after = self.member.state.healthy(); let after = self.member.state.healthy();
+15
View File
@@ -807,4 +807,19 @@ mod tests {
state.observe_passive(&active, &passive, "ok", Duration::from_millis(2)); state.observe_passive(&active, &passive, "ok", Duration::from_millis(2));
assert!(state.healthy()); assert!(state.healthy());
} }
#[test]
fn successful_lease_response_keeps_passive_observability_and_failure_detail() {
let pool = capacity_pool(0, 0);
let selection = select(&pool);
selection.lease.response(200, Duration::from_millis(5), true);
let state = &selection.lease.member.state;
assert_eq!(state.last_status.load(Ordering::Relaxed), 200);
assert_eq!(lock(&state.health).passive_result, "ok");
assert!(lock(&state.health).latency_us > 0);
selection.lease.response(503, Duration::from_millis(5), true);
assert_eq!(state.last_status.load(Ordering::Relaxed), 503);
assert_eq!(lock(&state.last_failure).detail, "status=503");
}
} // mod tests } // mod tests
+13
View File
@@ -56,6 +56,19 @@ mod tests {
assert!(request_headers(&HeaderMap::new()).is_empty()); assert!(request_headers(&HeaderMap::new()).is_empty());
} }
#[test]
fn unaccepted_codecs_cannot_be_reenabled_by_scoped_overrides() {
let global = Settings::default();
let force_on = Overrides { enabled: Some(true), ..Default::default() };
for offer in ["identity", "gzip;q=0, br;q=0, zstd;q=0", "deflate", "*;q=0"] {
let mut headers = HeaderMap::new();
headers.insert("accept-encoding", offer.parse().unwrap());
assert!(preferred_encoding(&headers, &global).is_none());
let effective = force_on.resolve(&global, &global);
assert!(preferred_encoding(&headers, &effective).is_none());
}
}
#[test] #[test]
fn scoped_codec_overrides_keep_global_codecs_authoritative() { fn scoped_codec_overrides_keep_global_codecs_authoritative() {
let global = Settings::default(); let global = Settings::default();
+26 -2
View File
@@ -214,6 +214,22 @@ impl Compiled {
tls: bool, tls: bool,
authority: &HeaderValue, authority: &HeaderValue,
uri: &Uri, uri: &Uri,
) -> Result<Context> {
self.context_with_original_url(incoming, peer, local, tls, authority, uri, true)
}
/// The absolute original URL is only consumed by the optional Tinyauth
/// verifier. Ordinary proxy requests need the exact forwarding metadata,
/// but do not need to allocate this extra diagnostic URL.
pub fn context_with_original_url(
&self,
incoming: &HeaderMap,
peer: SocketAddr,
local: SocketAddr,
tls: bool,
authority: &HeaderValue,
uri: &Uri,
include_original_url: bool,
) -> Result<Context> { ) -> Result<Context> {
let peer_ip = canonical(peer.ip()); let peer_ip = canonical(peer.ip());
let trusted_peer = self.trusted(peer_ip); let trusted_peer = self.trusted(peer_ip);
@@ -222,7 +238,11 @@ impl Compiled {
let authority_text = authority.to_str()?; let authority_text = authority.to_str()?;
crate::config::request_host(authority_text)?; crate::config::request_host(authority_text)?;
let target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/"); let target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/");
let original_url = format!("{scheme}://{authority_text}{target}"); let original_url = if include_original_url {
format!("{scheme}://{authority_text}{target}")
} else {
String::new()
};
let peer_text = peer_ip.to_string(); let peer_text = peer_ip.to_string();
let peer_header = HeaderValue::from_str(&peer_text)?; let peer_header = HeaderValue::from_str(&peer_text)?;
let peer_node = if peer_ip.is_ipv4() { None } else { Some(node(peer_ip)) }; let peer_node = if peer_ip.is_ipv4() { None } else { Some(node(peer_ip)) };
@@ -269,7 +289,11 @@ impl Compiled {
// Validate before inserting authority into Forwarded's quoted host field. // Validate before inserting authority into Forwarded's quoted host field.
crate::config::request_host(authority_text)?; crate::config::request_host(authority_text)?;
let original_target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/"); let original_target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/");
let original_url = format!("{scheme}://{authority_text}{original_target}"); let original_url = if include_original_url {
format!("{scheme}://{authority_text}{original_target}")
} else {
String::new()
};
let mut forwarded: Vec<_> = ips.iter().map(|ip| format!("for={}", node(*ip))).collect(); let mut forwarded: Vec<_> = ips.iter().map(|ip| format!("for={}", node(*ip))).collect();
let last = forwarded.last_mut().expect("actual peer is always present"); let last = forwarded.last_mut().expect("actual peer is always present");
last.push_str(&format!( last.push_str(&format!(
+35
View File
@@ -229,4 +229,39 @@ mod tests {
assert_eq!(context.values["x-forwarded-port"], "8080"); assert_eq!(context.values["x-forwarded-port"], "8080");
assert_eq!(context.values["x-forwarded-uri"], "/s?q=1"); assert_eq!(context.values["x-forwarded-uri"], "/s?q=1");
} }
#[test]
fn original_url_is_omitted_only_when_unneeded_and_forwarding_is_identical() {
for (trusted, peer, local) in [
(false, "192.0.2.9:5000", "10.0.0.1:8080"),
(true, "10.0.0.4:5000", "10.0.0.1:8080"),
(false, "[2001:db8::a]:5010", "[2001:db8::b]:8080"),
] {
let settings = Settings {
trusted_proxies: if trusted { vec!["10.0.0.0/8".into()] } else { vec![] },
trust_forwarded_proto: true,
}.compile().unwrap();
let mut incoming = HeaderMap::new();
incoming.insert("x-forwarded-for", "192.0.2.15".parse().unwrap());
incoming.insert("x-forwarded-proto", "http".parse().unwrap());
let peer: SocketAddr = peer.parse().unwrap();
let local: SocketAddr = local.parse().unwrap();
let host: HeaderValue = "app.example:8443".parse().unwrap();
let uri: Uri = "/route?q=hello".parse().unwrap();
let full = settings.context(&incoming, peer, local, true, &host, &uri).unwrap();
let compact = settings.context_with_original_url(
&incoming, peer, local, true, &host, &uri, false,
).unwrap();
assert!(!full.original_url.is_empty());
assert!(compact.original_url.is_empty());
assert_eq!(full.client_ip, compact.client_ip);
assert_eq!(full.scheme, compact.scheme);
assert_eq!(full.values, compact.values);
let mut full_headers = incoming.clone();
let mut compact_headers = incoming.clone();
full.apply(&mut full_headers);
compact.apply(&mut compact_headers);
assert_eq!(full_headers, compact_headers);
}
}
} }
+2 -1
View File
@@ -193,13 +193,14 @@ async fn forward_request(
} }
} }
} }
let mut context = match snapshot.forwarding.context( let mut context = match snapshot.forwarding.context_with_original_url(
request.headers(), request.headers(),
peer.0, peer.0,
local.0, local.0,
tls, tls,
&original_host, &original_host,
request.uri(), request.uri(),
frontend.is_some_and(|frontend| frontend.tinyauth.is_some()),
) { ) {
Ok(context) => context, Ok(context) => context,
Err(_) => { Err(_) => {
+9 -10
View File
@@ -1,14 +1,13 @@
fn request_id_hash(sequence: u64) -> String { fn request_id_hash(sequence: u64) -> String {
use sha2::{Digest, Sha256}; // Same thread-local CSPRNG as before, now with no per-request SHA-256.
// XORing an increasing sequence into independent uniformly random bytes
// Thread-local CSPRNG is seeded (and periodically reseeded) from the OS. // preserves the full 256-bit unpredictability and 64-hex-character shape.
// Do not make a getrandom syscall on the hot path for every HTTP request. // The sequence is still mixed in; this is not a predictable counter ID.
// Keep hashing the per-process sequence as before for the same ID format. let mut token: [u8; 32] = rand::random();
let nonce: [u8; 32] = rand::random(); for (byte, sequence_byte) in token[..8].iter_mut().zip(sequence.to_be_bytes()) {
let mut hash = Sha256::new(); *byte ^= sequence_byte;
hash.update(sequence.to_be_bytes()); }
hash.update(nonce); request_id_hex(&token)
request_id_hex(&hash.finalize())
} }
fn request_id_hex(digest: &[u8]) -> String { fn request_id_hex(digest: &[u8]) -> String {
+9
View File
@@ -30,6 +30,15 @@ mod request_id_tests {
} }
} }
#[test]
fn independent_sequential_ids_keep_full_length_and_unique_values() {
let ids: std::collections::HashSet<_> = (0..4096u64)
.map(request_id_hash)
.collect();
assert_eq!(ids.len(), 4096);
assert!(ids.iter().all(|id| id.len() == 64));
}
#[test] #[test]
#[ignore = "explicit release-mode microbenchmark; not an acceptance threshold"] #[ignore = "explicit release-mode microbenchmark; not an acceptance threshold"]
fn benchmark_request_id_formatting() { fn benchmark_request_id_formatting() {
+19 -11
View File
@@ -312,21 +312,29 @@ async fn observe(State(state): State<Arc<App>>, mut request: Request, next: Next
response.headers_mut().remove("alt-svc"); response.headers_mut().remove("alt-svc");
} }
let global_compression = &snapshot.envelope.config.compression; let global_compression = &snapshot.envelope.config.compression;
let mut compression = global_compression.clone(); // All scoped policies have the global codec ceiling. With no accepted
if let Some(frontend) = frontend_context.as_ref() { // global encoding (e.g. Accept-Encoding: identity), encode is a no-op:
compression = frontend // avoid even constructing/resolving the per-request settings.
.compression if method != Method::HEAD
.resolve(global_compression, &compression); && crate::compression::preferred_encoding(&original_headers, global_compression).is_some()
} {
if let Some(route) = effective_route_context { let mut compression = global_compression.clone();
if let crate::config::CompiledSource::WebServer(service) = &route.source { if let Some(frontend) = frontend_context.as_ref() {
compression = service compression = frontend
.compression .compression
.resolve(global_compression, &compression); .resolve(global_compression, &compression);
} }
if let Some(route) = effective_route_context {
if let crate::config::CompiledSource::WebServer(service) = &route.source {
compression = service
.compression
.resolve(global_compression, &compression);
}
}
response = crate::compression::encode(
response, &original_headers, &method, &compression,
).await;
} }
response =
crate::compression::encode(response, &original_headers, &method, &compression).await;
if let Some(encoding) = response if let Some(encoding) = response
.extensions() .extensions()
.get::<crate::compression::AppliedEncoding>() .get::<crate::compression::AppliedEncoding>()
+45 -20
View File
@@ -122,7 +122,16 @@ impl Counters {
self.methods.add(&sample.method, 1); self.methods.add(&sample.method, 1);
self.method_families.add(method_family(&sample.method), 1); self.method_families.add(method_family(&sample.method), 1);
self.sources.add(sample.source, 1); self.sources.add(sample.source, 1);
self.statuses.add(&format!("{}xx", sample.status / 100), 1); // Standard HTTP status families are static; the unusual 6xx-9xx
// extension codes still keep their exact original dashboard label.
match sample.status / 100 {
1 => self.statuses.add("1xx", 1),
2 => self.statuses.add("2xx", 1),
3 => self.statuses.add("3xx", 1),
4 => self.statuses.add("4xx", 1),
5 => self.statuses.add("5xx", 1),
family => self.statuses.add(&format!("{family}xx"), 1),
}
self.routes.add(sample.route.as_deref().unwrap_or("(main)"), 1); self.routes.add(sample.route.as_deref().unwrap_or("(main)"), 1);
self.paths.add(&sample.path, 1); self.paths.add(&sample.path, 1);
self.cache.add(sample.cache, 1); self.cache.add(sample.cache, 1);
@@ -225,16 +234,27 @@ impl Analytics {
let (tx, mut rx) = mpsc::channel::<Sample>(QUEUE_CAPACITY); let (tx, mut rx) = mpsc::channel::<Sample>(QUEUE_CAPACITY);
let worker_state = state.clone(); let worker_state = state.clone();
tokio::spawn(async move { tokio::spawn(async move {
// One bounded receiver and one ordered aggregator as before.
// Batch up to 64 ready messages under one dashboard-state lock;
// never hold the lock while waiting for new messages.
let mut batch = Vec::with_capacity(64);
loop { loop {
tokio::select! { tokio::select! {
_ = shutdown.cancelled() => break, _ = shutdown.cancelled() => break,
sample = rx.recv() => { count = rx.recv_many(&mut batch, 64) => {
let Some(sample) = sample else { break; }; if count == 0 { break; }
let mut state = worker_state.lock().unwrap_or_else(|e| e.into_inner()); let mut state = worker_state.lock().unwrap_or_else(|e| e.into_inner());
if !state.vhosts.contains_key(&sample.vhost) && state.vhosts.len() >= MAX_VHOSTS { for sample in batch.drain(..) {
continue; if let Some(counters) = state.vhosts.get_mut(sample.vhost.as_str()) {
counters.record(sample);
} else if state.vhosts.len() < MAX_VHOSTS {
// Allocate the map key only for a previously unseen vhost.
let name = sample.vhost.clone();
let mut counters = Counters::default();
counters.record(sample);
state.vhosts.insert(name, counters);
}
} }
state.vhosts.entry(sample.vhost.clone()).or_default().record(sample);
} }
} }
} }
@@ -268,7 +288,9 @@ impl Analytics {
} }
total.view() total.view()
} else { } else {
state.vhosts.get(selected).cloned().unwrap_or_default().view() // No need to copy a dozen bounded top lists just to render one
// vhost's read-only dashboard view while holding the state lock.
state.vhosts.get(selected).map(Counters::view).unwrap_or_else(|| Counters::default().view())
}; };
View { View {
scope: selected.to_owned(), scope: selected.to_owned(),
@@ -320,30 +342,33 @@ fn method_family(method: &str) -> &'static str {
} }
fn user_agent_family(value: &str) -> &'static str { fn user_agent_family(value: &str) -> &'static str {
let lower = value.to_ascii_lowercase(); // ASCII-insensitive classification without copying and lowercasing a
if lower.contains("bot") || lower.contains("spider") || lower.contains("crawler") || lower.contains("slurp") { // potentially 512-character User-Agent for every sampled request.
let has = |needle: &str| value.as_bytes().windows(needle.len())
.any(|candidate| candidate.eq_ignore_ascii_case(needle.as_bytes()));
if has("bot") || has("spider") || has("crawler") || has("slurp") {
"bot" "bot"
} else if lower.contains("curl/") { } else if has("curl/") {
"curl" "curl"
} else if lower.contains("wget/") { } else if has("wget/") {
"wget" "wget"
} else if lower.contains("python-requests") || lower.contains("python/") || lower.contains("aiohttp/") { } else if has("python-requests") || has("python/") || has("aiohttp/") {
"python" "python"
} else if lower.contains("go-http-client") { } else if has("go-http-client") {
"go" "go"
} else if lower.contains("java/") || lower.contains("okhttp/") { } else if has("java/") || has("okhttp/") {
"java" "java"
} else if lower.contains("postmanruntime/") { } else if has("postmanruntime/") {
"postman" "postman"
} else if lower.contains("edg/") || lower.contains("edge/") { } else if has("edg/") || has("edge/") {
"edge" "edge"
} else if lower.contains("opr/") || lower.contains("opera") { } else if has("opr/") || has("opera") {
"opera" "opera"
} else if lower.contains("chrome/") || lower.contains("crios/") { } else if has("chrome/") || has("crios/") {
"chrome" "chrome"
} else if lower.contains("firefox/") || lower.contains("fxios/") { } else if has("firefox/") || has("fxios/") {
"firefox" "firefox"
} else if lower.contains("safari/") { } else if has("safari/") {
"safari" "safari"
} else { } else {
"other" "other"
+64
View File
@@ -75,4 +75,68 @@ mod tests {
assert_eq!(user_agent_family("curl/8.12.1"), "curl"); assert_eq!(user_agent_family("curl/8.12.1"), "curl");
assert_eq!(user_agent_family("ExampleBot/1.0"), "bot"); assert_eq!(user_agent_family("ExampleBot/1.0"), "bot");
} }
#[test]
fn status_top_list_preserves_common_and_extension_family_names() {
let mut counters = Counters::default();
for status in [100, 200, 302, 404, 503, 701] {
counters.record(Sample {
vhost: "test".into(), route: None, method: "GET".into(),
path: "/".into(), client_ip: None, status,
duration_ms: 1, request_bytes: 0, response_bytes: 1,
cache: "BYPASS", source: "reverse_proxy",
user_agent: None, country: None,
});
}
let labels: std::collections::HashSet<_> = counters.view().statuses.into_iter()
.map(|entry| entry.value).collect();
for label in ["1xx", "2xx", "3xx", "4xx", "5xx", "7xx"] {
assert!(labels.contains(label));
}
}
#[test]
fn user_agent_matching_is_ascii_case_insensitive_even_with_unicode() {
for (agent, expected) in [
("MOZILLA/5.0 ChRoMe/120.0", "chrome"),
("żółw / uSER-BOT", "bot"),
("CURL/8.0", "curl"),
("wGeT/1.24", "wget"),
("Ąęć PYTHON-Requests", "python"),
("Mozilla/5.0 EdG/110", "edge"),
("Random ∂x/42", "other"),
] {
assert_eq!(user_agent_family(agent), expected);
}
}
#[tokio::test]
async fn batched_worker_keeps_exact_counts_and_bounded_vhosts() {
let shutdown = CancellationToken::new();
let analytics = Analytics::start(shutdown.clone());
for id in 0..130 {
analytics.push(Sample {
vhost: if id % 2 == 0 { "left".into() } else { "right".into() },
route: None, method: "GET".into(), path: "/small".into(),
client_ip: None, status: 200, duration_ms: 1,
request_bytes: 0, response_bytes: 1040,
cache: "BYPASS", source: "reverse_proxy",
user_agent: None, country: None,
});
}
tokio::time::timeout(std::time::Duration::from_secs(2), async {
while analytics.view(Some("left")).data.requests != 65
|| analytics.view(Some("right")).data.requests != 65
{
tokio::task::yield_now().await;
}
}).await.expect("analytics should drain two batches");
let all = analytics.view(None).data;
assert_eq!(all.requests, 130);
assert_eq!(all.response_bytes, 130 * 1040);
assert_eq!(analytics.dropped.load(Ordering::Relaxed), 0);
assert_eq!(analytics.reset(Some("left")), 1);
assert_eq!(analytics.view(None).data.requests, 65);
shutdown.cancel();
}
} }