WEB: websocket + websocket-lanes as Carrier

This commit is contained in:
Alexey
2026-08-26 09:16:06 +03:00
parent 8e577ec5ca
commit d2edd90479
50 changed files with 3642 additions and 350 deletions
+86
View File
@@ -0,0 +1,86 @@
use base64::Engine as _;
use super::{MAX_PAGE_BYTES, escape, yes_no};
pub(super) fn push_frames(html: &mut String, frames: &[crate::web::trace::TraceFrame]) {
if frames.is_empty() {
return;
}
html.push_str("<h3>frames</h3><table><tr><th>dir</th><th>type</th><th>stream/lane</th><th>payload</th><th>WINDOW</th><th>error</th></tr>");
for frame in frames {
html.push_str("<tr>");
for value in [
frame.direction.as_str().to_string(),
frame.frame_type.unwrap_or("-").to_string(),
frame
.stream_id
.map(|value| value.to_string())
.unwrap_or_else(|| "-".to_string()),
frame
.payload_len
.map(|value| value.to_string())
.unwrap_or_else(|| "-".to_string()),
frame
.window_delta
.map(|value| value.to_string())
.unwrap_or_else(|| "-".to_string()),
frame.parse_error.unwrap_or("-").to_string(),
] {
html.push_str("<td>");
escape(html, &value);
html.push_str("</td>");
}
html.push_str("</tr>");
}
html.push_str("</table>");
}
pub(super) fn push_headers(
html: &mut String,
title: &str,
headers: &[crate::web::trace::TraceHeader],
) {
html.push_str("<h3>");
escape(html, title);
html.push_str("</h3><pre>");
for header in headers {
escape(html, &header.name);
html.push_str(": ");
escape(html, header.value.as_deref().unwrap_or("[value omitted]"));
html.push('\n');
}
html.push_str("</pre>");
}
pub(super) fn push_body(
html: &mut String,
title: &str,
body: Option<&crate::web::trace::TraceBodySnapshot>,
) {
html.push_str("<h3>");
escape(html, title);
html.push_str("</h3>");
let Some(body) = body else {
html.push_str("<p class=\"muted\">capture off</p>");
return;
};
html.push_str("<p>observed=");
html.push_str(&body.observed_bytes.to_string());
html.push_str(" captured=");
html.push_str(&body.captured.len().to_string());
html.push_str(" state=");
html.push_str(body.state.as_str());
html.push_str(" truncated=");
html.push_str(yes_no(body.truncated));
html.push_str("</p><pre>");
let available = MAX_PAGE_BYTES
.saturating_sub(html.len())
.saturating_sub(4096);
let raw_limit = available.saturating_mul(3) / 4;
let shown = body.captured.len().min(raw_limit);
base64::engine::general_purpose::STANDARD.encode_string(&body.captured[..shown], html);
if shown < body.captured.len() {
html.push_str("\n[page output truncated]");
}
html.push_str("</pre>");
}
+4 -7
View File
@@ -101,8 +101,9 @@ pub(super) fn parse_query(
query.key = Some(value.to_string());
}
"group_by" => {
let group = GroupBy::parse(value)
.ok_or_else(|| "group_by must be ip, session, user_agent, or key".to_string())?;
let group = GroupBy::parse(value).ok_or_else(|| {
"group_by must be ip, session, user_agent, or key".to_string()
})?;
if query.group_by.contains(&group) {
return Err("group_by values must not repeat".to_string());
}
@@ -140,11 +141,7 @@ fn parse_positive_u64(value: &str, field: &str) -> Result<u64, String> {
}
/// Applies the complete filter predicate to one immutable record.
pub(super) fn record_matches(
record: &TraceRecord,
query: &StatusQuery,
since_millis: u64,
) -> bool {
pub(super) fn record_matches(record: &TraceRecord, query: &StatusQuery, since_millis: u64) -> bool {
!(record.epoch_millis < since_millis
|| query.before_seq.is_some_and(|before| record.seq >= before)
|| query.record.is_some_and(|seq| record.seq != seq)
+5 -1
View File
@@ -47,7 +47,11 @@ async fn renderer_filters_groups_and_sets_control_plane_security_headers() {
.await;
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.headers()[header::CACHE_CONTROL], "no-store");
assert!(response.headers().contains_key(header::CONTENT_SECURITY_POLICY));
assert!(
response
.headers()
.contains_key(header::CONTENT_SECURITY_POLICY)
);
let body = response.into_body().collect().await.unwrap().to_bytes();
let body = std::str::from_utf8(&body).unwrap();
assert!(body.contains("session_created"));