Bounded Debugging + Websocket Carriers + Carriers Negotiation

Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
Alexey
2026-08-26 17:00:20 +03:00
parent 43cd84aaa5
commit 923c79796a
52 changed files with 3450 additions and 980 deletions
+7 -20
View File
@@ -18,7 +18,7 @@ mod details;
// Query parsing and matching remain independent from bounded HTML rendering.
mod query;
use details::{push_body, push_frames, push_headers};
use details::{push_body, push_frames, push_headers, push_lifecycle};
use query::{GroupBy, StatusQuery, client_ip, parse_query, record_matches};
struct GroupSummary {
@@ -56,11 +56,11 @@ pub(super) async fn render(
);
};
let now_millis = crate::web::trace::store_epoch_millis();
let since_millis = query
.record
.is_none()
.then(|| now_millis.saturating_sub(query.window_secs.saturating_mul(1000)))
.unwrap_or(0);
let since_millis = if query.record.is_none() {
now_millis.saturating_sub(query.window_secs.saturating_mul(1000))
} else {
0
};
let records = store.snapshot_matching(|record| record_matches(record, &query, since_millis));
let status = store.status();
let mut html = String::with_capacity(MAX_PAGE_BYTES);
@@ -367,20 +367,7 @@ fn push_record(html: &mut String, record: &TraceRecord) {
push_body(html, "message body", message.body.as_ref());
push_frames(html, &message.frames);
}
TraceRecordKind::Lifecycle(event) => {
html.push_str("<pre>event: ");
html.push_str(event.event.as_str());
html.push_str("\nstream: ");
html.push_str(
&event
.stream_id
.map(|v| v.to_string())
.unwrap_or_else(|| "-".to_string()),
);
html.push_str("\nreason: ");
html.push_str(event.reason.unwrap_or("-"));
html.push_str("</pre>");
}
TraceRecordKind::Lifecycle(event) => push_lifecycle(html, event),
}
html.push_str("</details></td></tr>");
}
+34
View File
@@ -84,3 +84,37 @@ pub(super) fn push_body(
}
html.push_str("</pre>");
}
pub(super) fn push_lifecycle(
html: &mut String,
event: &crate::web::trace::TraceLifecycleRecord,
) {
html.push_str("<pre>event: ");
html.push_str(event.event.as_str());
html.push_str("\nstream: ");
html.push_str(
&event
.stream_id
.map(|value| value.to_string())
.unwrap_or_else(|| "-".to_string()),
);
html.push_str("\nreason: ");
html.push_str(event.reason.unwrap_or("-"));
if let Some(carrier) = &event.carrier {
html.push_str("\nclient class: ");
html.push_str(carrier.client_class);
html.push_str("\ncarrier: ");
html.push_str(carrier.carrier.as_str());
html.push_str("\nattempt: ");
html.push_str(&carrier.attempt.to_string());
html.push_str("\nscores: https=");
html.push_str(&carrier.scores[0].to_string());
html.push_str(" https-lanes=");
html.push_str(&carrier.scores[1].to_string());
html.push_str(" websocket=");
html.push_str(&carrier.scores[2].to_string());
html.push_str(" websocket-lanes=");
html.push_str(&carrier.scores[3].to_string());
}
html.push_str("</pre>");
}
+10 -6
View File
@@ -20,11 +20,15 @@ fn html_escaping_covers_active_markup_characters() {
#[tokio::test]
async fn renderer_filters_groups_and_sets_control_plane_security_headers() {
let mut policy = WebDebugConfig::default();
policy.enabled = true;
let mut limits = crate::config::WebLimitsConfig::default();
limits.debug_records_capacity = 8;
limits.debug_bytes_global = 16 * 1024;
let policy = WebDebugConfig {
enabled: true,
..Default::default()
};
let limits = crate::config::WebLimitsConfig {
debug_records_capacity: 8,
debug_bytes_global: 16 * 1024,
..Default::default()
};
let store = WebTraceStore::new(policy.clone(), &limits);
store.record_lifecycle(
None,
@@ -79,7 +83,7 @@ async fn render_permits_remain_owned_by_inflight_response_bodies() {
#[test]
fn page_truncation_preserves_utf8_boundary_and_cap() {
let mut html = "я".repeat(MAX_PAGE_BYTES);
let mut html = "\u{044f}".repeat(MAX_PAGE_BYTES);
truncate_page(&mut html);
assert!(html.len() <= MAX_PAGE_BYTES);
assert!(html.ends_with("[page output truncated]"));