0.35.5
This commit is contained in:
1 parent
ff42a13dbf
commit
e7b4991316
14 files changed
+176
-50
No files matched your search
@@ -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": [
|
||||
|
||||
@@ -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(" "));
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -334,7 +334,8 @@
|
||||
<div>
|
||||
<h3 class="overview-chart-heading" data-i18n="systemLoad">System load</h3>
|
||||
<h2><span id="system-cpu">-</span> <small>CPU</small></h2>
|
||||
</div><span class="meta" id="system-source">-</span>
|
||||
</div><span class="meta system-uptime-meta"><span data-i18n="systemUptime">System uptime</span>: <b
|
||||
id="system-uptime">-</b></span>
|
||||
</div><svg preserveAspectRatio="none" aria-label="System CPU history" data-i18n-aria="systemChart" id="system-chart"
|
||||
role="img" viewbox="0 0 300 100">
|
||||
<path class="chart-grid" d="M0 25H300 M0 50H300 M0 75H300"></path>
|
||||
@@ -348,14 +349,15 @@
|
||||
<div class="memory-line"><span data-i18n="systemSwap">System swap</span><b id="system-swap">-</b></div>
|
||||
<div class="chart-footer"><span data-i18n="loadAverage">Load 1 / 5 / 15 min</span><span id="system-load">-</span>
|
||||
</div>
|
||||
<div class="chart-footer"><span data-i18n="systemUptime">System uptime</span><span id="system-uptime">-</span></div>
|
||||
</article>
|
||||
<article class="card connection-card" data-dashboard-widget="connection_chart">
|
||||
<div class="card-heading">
|
||||
<div>
|
||||
<div class="connection-heading-main">
|
||||
<h3 class="overview-chart-heading" data-i18n="connectionActivity">Connection activity</h3>
|
||||
<h2><span id="connection-current">0</span> <small data-i18n="connections">Open
|
||||
connections</small></h2>
|
||||
<h2><span class="connection-current-main"><span id="connection-current">0</span> <small
|
||||
data-i18n="connections">Open connections</small></span><small
|
||||
class="meta connection-backends-meta"><span data-i18n="backends">Backends</span>: <b
|
||||
id="connection-backends">0</b></small></h2>
|
||||
</div>
|
||||
</div>
|
||||
<svg preserveAspectRatio="none" aria-label="Connection activity history" data-i18n-aria="connectionActivity"
|
||||
@@ -368,6 +370,4 @@
|
||||
connections</span> <b id="connection-open-value">0</b></span><span
|
||||
class="connection-exchange-key"><i></i><span data-i18n="activeExchanges">Active
|
||||
exchanges</span> <b id="connection-exchange-value">0</b></span></div>
|
||||
<div class="chart-footer"><span><span data-i18n="backends">Backends</span>: <b id="connection-backends">0</b></span>
|
||||
</div>
|
||||
</article>
|
||||
@@ -517,5 +517,4 @@
|
||||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||
<polyline class="diagnostic-line proto-h2-line" id="www_php_requests-line" points="0,55 300,55"></polyline>
|
||||
</svg>
|
||||
<small data-i18n="webMetricPerSecond">Requests per second</small>
|
||||
</article>
|
||||
Vendored
+39
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+40
-2
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user