//! 有界 HTTP 活动记录。保存请求/响应的可显示预览供 Network 工具读取,不负责执行请求。 //! 正文预览和允许显示的头部受限制,避免活动面板成为无限日志或暴露不必要数据的通道。 //! //! Bounded observation of this process's local HTTP API, never external traffic. use std::{ collections::VecDeque, sync::{Arc, Mutex}, time::{Instant, SystemTime, UNIX_EPOCH}, }; pub type Activity = Arc>; pub const BODY_PREVIEW: usize = 32 * 1024; const MAX_RECORDS: usize = 80; #[derive(Clone, Debug)] /// 一次 HTTP 请求/响应的有界观察记录,供 Network 工具查看。 pub struct Exchange { pub id: u64, pub method: String, pub path: String, pub origin: String, pub timestamp: String, pub request: String, pub response: String, pub status: Option, pub elapsed_ms: u128, pub request_headers: String, pub response_headers: String, started: Instant, } #[derive(Debug, Default)] /// 按容量维护近期 HTTP 活动与请求身份,防止无限日志积累。 pub struct ActivityLog { pub records: VecDeque, next_id: u64, } impl ActivityLog { /// 为请求分配活动 ID 并登记其方法、来源和受限头部。 pub fn begin(&mut self, method: String, path: String, origin: String, headers: String) -> u64 { self.next_id += 1; let id = self.next_id; let seconds = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs() % 86400; self.records.push_back(Exchange { id, method, path: preview(&path), origin, timestamp: format!( "{:02}:{:02}:{:02} UTC", seconds / 3600, seconds / 60 % 60, seconds % 60 ), request: String::new(), response: String::new(), status: None, elapsed_ms: 0, request_headers: preview(&headers), response_headers: String::new(), started: Instant::now(), }); while self.records.len() > MAX_RECORDS { self.records.pop_front(); } id } /// 更新请求正文预览,内容长度仍由活动记录策略限制。 pub fn body(&mut self, id: u64, body: &str) { if let Some(record) = self.records.iter_mut().find(|r| r.id == id) { record.request = preview(body); } } /// 完成本模块当前阶段的结果整理,把后续工作交回调用方。 pub fn finish(&mut self, id: u64, status: u16, body: &str, headers: String) { if let Some(record) = self.records.iter_mut().find(|r| r.id == id) { record.status = Some(status); record.response = preview(body); record.elapsed_ms = record.started.elapsed().as_millis(); record.response_headers = preview(&headers); } } } /// 截取可显示的受限文本预览,避免日志无限保留完整大正文。 pub fn preview(value: &str) -> String { if value.len() <= BODY_PREVIEW { return value.to_owned(); } let mut end = BODY_PREVIEW; while !value.is_char_boundary(end) { end -= 1; } format!("{}\n[preview truncated at 32 KiB]", &value[..end]) }