diff --git a/AGENTS.md b/AGENTS.md index 99a54c1..e7da4cd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # RustProxy source and documentation maintenance -Current source version: **0.35.4**. The Cargo package and binary are named `rust-proxy`. +Current source version: **0.35.5**. The Cargo package and binary are named `rust-proxy`. Before editing, read [README.md](README.md), the relevant [documentation guide](docs/README.md) and the actual code and tests. Historical release experiments are not current specifications. Use the bundled `src/admin/openapi.json` for API routes and payloads; the source implementation is authoritative for runtime behavior. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0792b11..f772394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ Release history focused on **features, settings, default behavior, compatibility and fixes**. Changes to internal code structure and preliminary performance experiments are intentionally omitted. Historical descriptions reflect their respective releases; consult the current documentation for the settings available in the latest release. +## 0.35.5 — Overview polish and cache build fix + +- **Overview charts:** the cache-results heading now matches the other chart headings and its HIT/MISS/BYPASS legend stays on one line; system uptime is shown in the System load header; the backend count is aligned with Open connections; the redundant Requests per second footer was removed from the PHP requests tile. +- **Polish status labels:** the sidebar healthy-state label is now **SYSTEM OK**, and the application self-check uses the shorter **API** label. +- **Cache build fix:** restored the detached-header helper functions used when finalizing cache fills, fixing the `E0425` build failure for `detached_header_value` and `detach_header_map` while preserving the sensitive flag on copied header values. +- **Upgrade notes:** no configuration or database changes are required. + ## 0.35.4 — Dashboard status and control cleanup - **Overview samples:** the history sample selector is right-aligned on desktop and mobile and now saves immediately in the browser; the separate Remember choice button is removed. diff --git a/Cargo.lock b/Cargo.lock index 1eb7216..c198904 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1943,7 +1943,7 @@ dependencies = [ [[package]] name = "rust-proxy" -version = "0.35.4" +version = "0.35.5" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 6394ed6..9f8c845 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-proxy" -version = "0.35.4" +version = "0.35.5" edition = "2021" rust-version = "1.99.1" description = "RustProxy - independent reverse proxy with local administration" diff --git a/SHA256SUMS b/SHA256SUMS index 2858a8d..4795288 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,10 +1,10 @@ 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example e76e688874828e3743d03693b1d2872b9633b4db09909673786caf30f7cfe489 .gitignore -f8272b8b6a11d50811e2f25b5d43f4713dd7fc7d22c2b4892cb85757d481c10d AGENTS.md -9539278957c4d6dbbe613b7ba5388499e215b28aa153f099eefe4949b3eb4f78 CHANGELOG.md -c593deba438fb6f5acbc6868612ddfa30d4f1e15134e9654c0f399f49b2c32fd Cargo.lock -9795da5b826e58378910a45c3c9d9bb3a9cbdf9e009ad03ee82d1a59da1f4e83 Cargo.toml +140ddb17001c2c821dd763335c1cecbfd75b27166b2a3ab1e815a8d53471a856 AGENTS.md +2feb306d156b92773b6bf376d439c9f823840b745309a61b158ddc68a59186c3 CHANGELOG.md +f7c3fa3eb61e00a0ec0156229877a01d14bd3fa568dff0b04d0d73bb12cf6fae Cargo.lock +bf974faca111295d73a0c092be3536d70efeb222d5599f28814cba169cbeef25 Cargo.toml 25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile 0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0 LICENSE 82d1dee2fc68158f0ea73259151461e64c8b18d807cd1fecfb1ed99dc30cf5f4 README.md @@ -86,12 +86,12 @@ de6550ab8f48d556d752c252212c38ba65491fd224f972c10fbe552f91ed7520 integrations/o 45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 integrations/routeros-oci-build/.env.routeros.example 11aa102de1089ecc375f20c4bd5420fcfd3b2ef23fe0c8bdf04953fcfaf40ef0 integrations/routeros-oci-build/Dockerfile.routeros 7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e integrations/routeros-oci-build/Dockerfile.routeros.dockerignore -a68d5780f9eeb084db5ec465791b71332deb0456fdb245a253d464c5c76431c9 integrations/routeros-oci-build/README.md +70852c6f13ef6a287c8f815aab085cc53e1150461a88342ad83715a9c7b0bbef integrations/routeros-oci-build/README.md 3f092a9ab80221eddd67be4d1a776acbc6e04c1eb6011b588280c28e83b2582f integrations/routeros-oci-build/build-routeros-oci.sh 3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh 13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md cfe8fff42f98edc85a626f1d87521fa75d24f8b6b4573c46b1541820681a7080 langs/en_US.json -771b13ae8d77ec0348cb56a7093d328a8b3abcde184a65a81f5db8e44fb910b3 langs/pl_pl.json +34628bddc76c02eff482aa8300e0523e0edad87dfa4ebc3ef3285d68b6c46cfb langs/pl_pl.json 9664543e4d03422788a65e42f968c1f026c2235cb5be6fc3e0ad318997077538 make_zip.py e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql 20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql @@ -162,7 +162,7 @@ d58a2ed83319176de4e66790ed292447c9497ae031464ec55cfe27174acb639a src/admin/file 77b8c05645969834cc8a58e249a7b60a57d6c47ad1c5cd92ffa5e4a9b5a8e73c src/admin/file_logs_query.rs 444d7715406ffd2305a72decf7fdf633aa4c2752cd881c8fa3b95ed971d79820 src/admin/logging_api.rs 243f4a9c3fa99781e19c3952354d2632c0ce65e1dafac729567cbe3c177f94cb src/admin/mod.rs -ee4075f5f16bb0940fb785b2c527669a605d4352efe1030d4ca98bddb1ce051c src/admin/openapi.json +30b0ea16c6960e8efd682e95429c70c1b2ff178aef67e570e678d0dd82dce060 src/admin/openapi.json 1f4ec45d146e00ef664fae37517f8fcb5455d47daec496c02b2fc15ddec49131 src/admin/operations.rs 257d93a29d0bda96462bcf695aba8d0ec0f9412c379e066ec46ae8ee034e3a53 src/admin/panel_assets.rs f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/panel_content.rs @@ -198,7 +198,7 @@ bfd9fafb3112197e8f875dbf87a9a66c81aef0fe83687601a84a66f5e13f69bc src/admin/web/ 46c7cd36397375eb7e8c230c8aa97b1dbea0a63800beb977efe1ec67a14b237e src/admin/web/app/preferences.js 9e2b12d34ab1fb29716645720577ad49b16c03b46c482a03695e8f645d170e79 src/admin/web/app/rate-sampling.js d34f9e25a04cc4eafdbc48daa4e69aab18944e8d34e555a50d9c122c680595f8 src/admin/web/app/refresh.js -564c8d81c9164e3a63f230579facd3c75281a806477e97ef9c05145807e08c94 src/admin/web/app/runtime.js +c6ae53c0e95e67b9165822813efc622001be5354c19bb221a52514221d1df23e src/admin/web/app/runtime.js 31201586925e6f4792071d7d03fb05dcc4a69a03c02a4e85eb0746f45af68074 src/admin/web/app/session-resume.js c7a18238ac426de4d1cf84a395ee4dc66410d60a57a3822695dc10adc668d956 src/admin/web/app/settings.js b7ead49224a4ec67181d3f24f9d9634bd6d76756f826a4d5d993300637d1d8f3 src/admin/web/app/stream-dashboard.js @@ -208,7 +208,7 @@ e9788a9d1e0bce8775f612a5c842afea8a0dcf01301acdef4b69ff6a52d2ef45 src/admin/web/ a09f060b47771c56e9b9e57f1b896a32a2f87f1fb33401ac76932a91d247c67e src/admin/web/css/00-tokens.css 77b8b748f31ef383c64ba5ca1740e9b6073f0de1e77c2c8c62d0f38bb52dd296 src/admin/web/css/10-base.css 6ed54b27a4a81b073f1d30cf61e366f3d5c1f6f8495f0d0a8af48e061937eb4e src/admin/web/css/20-shell.css -65c1bd6d0adf05de60738f5a557dff18ad0d4b0b531e58aab29b01519416b09c src/admin/web/css/30-overview.css +c6d5696f0cdf4149e3341d18f7fd955f2411d50aac49a307c3eb0de2db9f92cb src/admin/web/css/30-overview.css dd82b5e3ead91c20bb08cf8d71bbba31ea8e3d108c417913c63409f3e34da7e1 src/admin/web/css/40-editors.css 6af9fcb439823ec63abe2c1dd86c9ecba17c233262fd8ab80d321d64ae2758ab src/admin/web/css/50-settings.css f8b515c63080c71ee647cfd8c36628271e8357b31cd84f00cf591e9b752b44fe src/admin/web/css/60-monitoring.css @@ -245,8 +245,8 @@ cdd390e69ca4cd010262e8fddb653e4097fbcfdc678b081c422edcd68e974f85 src/admin/web/ ac9f667273918e18406df06cb2613c923f50a88897693a03a18f500e22dc6ec2 src/admin/web/login.html c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/login.js 734aeac4855f0913a84a82d7b5c17b37de4504b50a8701edeb775d6926929bce src/admin/web/management.js -4481a592fcce9a2aa1db5e4a25619e19fd459c39039b86c0549dfc427eec1b45 src/admin/web/partials/overview-charts.html -69296af507dbc8c211800f4841c3d7ad6a2490aad2e93ecb297246b7470e3be3 src/admin/web/partials/overview-metrics.html +1f3d10e389c9c20e6eb019509fa6e82b50c6b43bbf988c1210a4d42bb73985d0 src/admin/web/partials/overview-charts.html +2842346eb66569b0aba2938ff0f724b50d21254b2aa0d2c00f6380ad03449b61 src/admin/web/partials/overview-metrics.html 3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b src/admin/web/prometheus-token.js 2d491fe9a547fe109dc5db6a74806d50b08c3daca15a6622c79c9fea33b10c63 src/admin/web/redirects.js 2e891f34cad2a2950a47e529e15ed9dd41beedec71280b8035b19cfb91a5d317 src/admin/web/settings.js @@ -291,7 +291,7 @@ d789bf40cf9433e2fd16409d112210fe93154588b1ac9bddc9c7f78e1db14426 src/balance/po a361c341932ffd186c0b21356c91ab1fe8043f5020d40798266d15ae896a114e src/balance/tests.rs ae06d238b1451328c751a511476ef39c012f3a397393f939ca02f96ecd0119a6 src/basic_auth/mod.rs 9a8858b7ff461a4d961ba701ca3ed6eaec15c3f7f05eef8e0e30edc63617605a src/basic_auth/tests.rs -e35648537d5ac0bfb604997200d1be25652ee4e8c49bf8b720b68df087bd0249 src/cache/cache_regression_tests.rs +90f26bcd36720076f874cd0a21230316b7303f68e23839e987cf51e13c23352b src/cache/cache_regression_tests.rs 7def03ace07dc38a4806277d75b6ce673d33ddaaf1c4855fa0a3372be1cf9489 src/cache/cold.rs b75ff512a1c6344483e00d2c67578ceea6b5472c279da0246f3622daa68d7e16 src/cache/cold/cleanup.rs 5fde9e9f77277d557372e4614264512804192bb51f3d2eae785988c17b2e7253 src/cache/cold/cold_tier_tests.rs @@ -301,7 +301,7 @@ b75ff512a1c6344483e00d2c67578ceea6b5472c279da0246f3622daa68d7e16 src/cache/cold c6c54984cf3460e6227d40d8d6917d7ca5d809ac364c80effe354c64133c765d src/cache/collapse.rs 346fdb40d85627b583a01fa17c65e45c822dc7216198cdeadbb3d07904286fa3 src/cache/dashboard.rs be3d80cd340edb47755190a668e2f2a2776940665f95ae7110893b706eb94faa src/cache/dashboard_tests.rs -5b0a3dec613fa22a38237618ddaeeca9df5c79acb499bfd78a31d9e7ce104d61 src/cache/fill.rs +847c660e6cb7d752f9dac2807c5045a000172c25b0e437cbade06ad7b101dcba src/cache/fill.rs 675d18afb53f25c56cba49bbb431fa39ecd9d7bdd192c7b39b0698225de7a2a2 src/cache/invalidation.rs c1357a13658e1ed5158084d3a41ba9ea906512774a5863ce9c56cb508d3fa6c8 src/cache/invalidation/ban.rs bbd9f372648d89b5e14d5b58029582d37f66a89752caf2c14eb1cbf8f24bb7a0 src/cache/invalidation/http_invalidation_tests.rs diff --git a/integrations/routeros-oci-build/README.md b/integrations/routeros-oci-build/README.md index 57b149d..a22652b 100644 --- a/integrations/routeros-oci-build/README.md +++ b/integrations/routeros-oci-build/README.md @@ -77,10 +77,10 @@ ENV_FILE=integrations/routeros-oci-build/.env \ bash integrations/routeros-oci-build/build-routeros-oci.sh push ``` -For example, with RustProxy version `0.35.4` and the defaults this publishes: +For example, with RustProxy version `0.35.5` and the defaults this publishes: ```text -zot.example.net/network/rustproxy:0.35.4 +zot.example.net/network/rustproxy:0.35.5 zot.example.net/network/rustproxy:latest zot.example.net/network/rustproxy:routeros ``` diff --git a/langs/pl_pl.json b/langs/pl_pl.json index 7920022..0363a3b 100644 --- a/langs/pl_pl.json +++ b/langs/pl_pl.json @@ -2849,12 +2849,12 @@ "appHealthWarning": "Wymaga uwagi", "appHealthError": "Wykryto problem", "appHealthSidebarChecking": "SPRAWDZANIE", - "appHealthSidebarOk": "APLIKACJA OK", + "appHealthSidebarOk": "SYSTEM OK", "appHealthSidebarWarning": "APLIKACJA: UWAGA", "appHealthSidebarError": "APLIKACJA: BŁĄD", "appHealthUpdated": "Aktualizacja: {time}", "appHealthNoStatus": "Brak aktualnego statusu", - "appHealthApi": "API sterujące", + "appHealthApi": "API", "appHealthApiOk": "Status live aplikacji odpowiada.", "appHealthApiError": "Brak aktualnego statusu aplikacji.", "appHealthListeners": "Listenery", diff --git a/src/admin/openapi.json b/src/admin/openapi.json index 6071626..bb17b76 100644 --- a/src/admin/openapi.json +++ b/src/admin/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "RustProxy Administration API", - "version": "0.35.4", + "version": "0.35.5", "description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust. The API also permits explicitly scoped GeneralApiBearer tokens on a narrow route allowlist, with RO/RW and source CIDR checks; API-token management and Prometheus-token management are browser-session-only." }, "servers": [ diff --git a/src/admin/web/app/runtime.js b/src/admin/web/app/runtime.js index 399816f..a5620fc 100644 --- a/src/admin/web/app/runtime.js +++ b/src/admin/web/app/runtime.js @@ -435,8 +435,6 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam $("process-summary").hidden = d.process_threads == null; if (d.process_threads != null) $("process-summary").textContent = `${fmt(d.process_threads)} ${t("threads")}`; setOptionalMetric("system-cpu", d.cpu_percent != null, `${fmt(d.cpu_percent, 1)}%`); - const sourceSupported = !!d.source && d.source !== "unsupported"; - $("system-source").hidden = !sourceSupported; if (sourceSupported) $("system-source").textContent = t("realSample"); const systemMemory = knownPair(d.memory_used_bytes, d.memory_total_bytes, value => fmtBytes(value)); setOptionalMetric("system-memory", systemMemory != null, systemMemory); const ramMeter = $("memory-meter"), ramFill = $("memory-meter-fill"); @@ -457,7 +455,9 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam setOptionalMetric("system-swap", systemSwap != null, systemSwap); const loadSupported = Array.isArray(d.load_average) && d.load_average.length > 0; setOptionalMetric("system-load", loadSupported, loadSupported ? d.load_average.map(n => fmt(n, 2)).join(" / ") : ""); - setOptionalMetric("system-uptime", d.system_uptime_seconds != null, d.system_uptime_seconds == null ? "" : uptime(d.system_uptime_seconds)); + const systemUptime = $("system-uptime"), systemUptimeMeta = systemUptime?.closest(".system-uptime-meta"); + if (systemUptimeMeta) systemUptimeMeta.hidden = d.system_uptime_seconds == null; + if (systemUptime && d.system_uptime_seconds != null) systemUptime.textContent = uptime(d.system_uptime_seconds); } $("system-chart").hidden = live.cpu_percent == null; if (appendSample && live.cpu_percent != null) { systemSamples.push(Number(live.cpu_percent)); systemSamples = systemSamples.slice(-window.rustProxyOverviewSamples.getLimit()); } $("system-sparkline").setAttribute("points", systemSamples.map((n, i) => `${i * 300 / Math.max(1, systemSamples.length - 1)},${95 - Math.max(0, Math.min(100, n)) * .9}`).join(" ")); diff --git a/src/admin/web/css/30-overview.css b/src/admin/web/css/30-overview.css index 3b33a89..03cc117 100644 --- a/src/admin/web/css/30-overview.css +++ b/src/admin/web/css/30-overview.css @@ -1004,6 +1004,10 @@ html[data-dashboard-density=compact] #panel-overview .stat>span { line-height: 1.35; } +html[data-dashboard-density=compact] #panel-overview .stat-cache-outcomes .cache-mix-header>span { + font-size: 11px; +} + html[data-dashboard-density=compact] #panel-overview .stat strong { font-size: 22px; margin: 3px 0 2px; @@ -1028,6 +1032,10 @@ html[data-dashboard-density=ultra] #panel-overview .stat>span { line-height: 1.08; } +html[data-dashboard-density=ultra] #panel-overview .stat-cache-outcomes .cache-mix-header>span { + line-height: 1.08; +} + html[data-dashboard-density=ultra] #panel-overview .stat strong { font-size: 18px; margin: 2px 0 1px; @@ -2097,7 +2105,12 @@ html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .over flex: 1 1 auto; min-width: 0; color: var(--muted); - font-size: 11px; + font-family: var(--sans); + font-size: 12px; + font-weight: 600; + line-height: 1.35; + letter-spacing: normal; + text-transform: none; } #panel-overview .stat-cache-outcomes .cache-mix-caption { @@ -2132,16 +2145,18 @@ html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .over } #panel-overview .cache-mix-legend { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 5px 8px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 7px; line-height: 1.25; + white-space: nowrap; } #panel-overview .cache-mix-legend>span { - display: flex; + display: inline-flex; align-items: center; - gap: 4px; + gap: 3px; min-width: 0; } @@ -2164,19 +2179,49 @@ html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .over } #panel-overview .cache-mix-legend b { - display: flex; - flex-direction: column; - align-items: flex-end; - margin-left: auto; + display: inline-flex; + align-items: baseline; + gap: 2px; + margin-left: 1px; color: var(--text); - font: 500 10px var(--mono); - overflow-wrap: anywhere; - text-align: right; + font: 500 9px var(--mono); } #panel-overview .cache-mix-legend .cache-mix-percent { color: var(--muted); - font: 400 10px var(--mono); + font: 400 8px var(--mono); +} + +#panel-overview .cache-mix-legend .cache-mix-percent::before { + content: "· "; +} + +#panel-overview .system-card .system-uptime-meta, +#panel-overview .connection-card .connection-backends-meta { + white-space: nowrap; +} + +#panel-overview .system-card .system-uptime-meta b, +#panel-overview .connection-card .connection-backends-meta b { + color: var(--text); + font-weight: 650; +} + +#panel-overview .connection-card .connection-heading-main { + width: 100%; + min-width: 0; +} + +#panel-overview .connection-card .connection-current-main { + display: inline-flex; + align-items: baseline; + gap: 6px; + min-width: 0; +} + +#panel-overview .connection-card .connection-backends-meta { + margin-left: auto; + line-height: inherit; } /* Live values stay on one line so refreshes do not change card heights. */ diff --git a/src/admin/web/partials/overview-charts.html b/src/admin/web/partials/overview-charts.html index 171d232..17ddb7a 100644 --- a/src/admin/web/partials/overview-charts.html +++ b/src/admin/web/partials/overview-charts.html @@ -334,7 +334,8 @@

System load

- CPU

-
- + System uptime: - @@ -348,14 +349,15 @@
System swap-
-
-
+

Connection activity

-

0 Open - connections

+

0 Open connectionsBackends: 0

0Active exchanges 0
-
\ No newline at end of file diff --git a/src/admin/web/partials/overview-metrics.html b/src/admin/web/partials/overview-metrics.html index 3445159..7afd593 100644 --- a/src/admin/web/partials/overview-metrics.html +++ b/src/admin/web/partials/overview-metrics.html @@ -517,5 +517,4 @@ - Requests per second \ No newline at end of file diff --git a/src/cache/cache_regression_tests.rs b/src/cache/cache_regression_tests.rs index bfab5f9..920a767 100644 --- a/src/cache/cache_regression_tests.rs +++ b/src/cache/cache_regression_tests.rs @@ -6,7 +6,6 @@ mod tests { h.insert("cache-control", value.parse().unwrap()); h } - // Legacy policy; varnish-compatible behavior is tested separately. fn settings() -> Settings { Settings { enabled: true, @@ -1559,7 +1558,10 @@ mod tests { let second = cache .ticket(1, "http", "example.test", "app", "/second", &headers) .unwrap(); - fill(&cache, first.clone(), "first", &s).await; + // Stored bodies are trimmed to their real size, so the first object must + // itself be large for the next fill reservation to need its space. + let large: &'static str = Box::leak("f".repeat(60 * 1024).into_boxed_str()); + fill(&cache, first.clone(), large, &s).await; assert!(cache.get(&first).is_some()); let before = cache.view(); assert!(before.hot_allocated_bytes <= s.max_bytes); @@ -1821,4 +1823,39 @@ mod tests { assert_eq!(cache.view().entries, 0); assert_eq!(cache.view().fills_active, 0); } + #[tokio::test] + async fn stored_entries_do_not_pin_origin_buffers_or_spare_capacity() { + let cache = Cache::new(1); + let s = settings(); + let t = ticket(&cache, &HeaderMap::new()); + // Header values parsed by hyper are slices of a larger read buffer. + let read_buffer = Bytes::from(vec![b'x'; 64 * 1024]); + let mut h = headers("max-age=60"); + h.insert( + "x-origin", + HeaderValue::from_maybe_shared(read_buffer.slice(0..16)).unwrap(), + ); + h.insert( + ::http::header::ETAG, + HeaderValue::from_maybe_shared(read_buffer.slice(16..24)).unwrap(), + ); + let p = plan(StatusCode::OK, &h, &s, Duration::ZERO).unwrap(); + let mut response = Response::new(Body::from("small body")); + *response.headers_mut() = h; + cache + .wrap(response, t.clone(), p, &s) + .into_body() + .collect() + .await + .unwrap(); + assert!(cache.get(&t).is_some()); + assert!( + read_buffer.try_into_mut().is_ok(), + "cached headers must not keep the origin read buffer alive" + ); + let store = cache.store.lock().unwrap(); + let entry = store.entries.values().next().expect("stored object"); + assert_eq!(entry.body.len(), 10); + assert!(entry.body_allocated <= 10 + 10 / 8 + 1); + } } diff --git a/src/cache/fill.rs b/src/cache/fill.rs index a1304db..d54075c 100644 --- a/src/cache/fill.rs +++ b/src/cache/fill.rs @@ -1,3 +1,16 @@ +fn detached_header_value(value: &HeaderValue) -> HeaderValue { + let mut detached = HeaderValue::from_bytes(value.as_bytes()) + .expect("existing HTTP header value must remain valid when copied"); + detached.set_sensitive(value.is_sensitive()); + detached +} + +fn detach_header_map(headers: &mut HeaderMap) { + for value in headers.values_mut() { + *value = detached_header_value(value); + } +} + struct ByteGaugeGuard<'a> { counter: &'a AtomicU64, bytes: u64, @@ -102,6 +115,9 @@ impl Candidate { let (body, body_allocation) = if let Some(body) = self.shared.take() { (body, self.shared_allocated) } else { + if self.data.capacity() > self.data.len().saturating_add(self.data.len() / 8) { + self.data.shrink_to_fit(); + } let allocated = self.data.capacity().max(self.data.len()); (Bytes::from(std::mem::take(&mut self.data)), allocated) }; @@ -111,6 +127,12 @@ impl Candidate { let domain = self.ticket.domain.clone(); let path = self.ticket.path.clone(); let target = self.ticket.target.clone(); + detach_header_map(&mut self.headers); + self.origin_etag = self.origin_etag.as_ref().map(detached_header_value); + self.origin_last_modified = self + .origin_last_modified + .as_ref() + .map(detached_header_value); let tags = cache_tags(&self.headers); let cost = hot_entry_cost( body_allocation, @@ -226,7 +248,21 @@ impl HttpBody for FillBody { let needed = candidate.data.len().saturating_add(data.len()); if needed <= candidate.settings.max_object_bytes { let before = candidate.data.capacity(); - let required_growth = needed.saturating_sub(before); + let target_capacity = if needed <= before { + before + } else if let Some(length) = + candidate.plan.expected_length.filter(|length| { + *length >= needed + && *length <= candidate.settings.max_object_bytes + }) + { + length + } else { + needed + .max(before.saturating_mul(2)) + .min(candidate.settings.max_object_bytes) + }; + let required_growth = target_capacity.saturating_sub(before); if required_growth > 0 && !candidate .fill_gauge @@ -246,7 +282,9 @@ impl HttpBody for FillBody { } else if required_growth > 0 && candidate .data - .try_reserve_exact(needed.saturating_sub(candidate.data.len())) + .try_reserve_exact( + target_capacity.saturating_sub(candidate.data.len()), + ) .is_err() { candidate.cache.record_reason(