//! 有界 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}, }; // HTTP worker 与原生 Network 工具共享同一有界日志;持锁读取快照/更新记录,不在这里执行网络请求。 pub type Activity = Arc>; // 预览正文按 UTF-8 字节限制;截断标记会额外占用少量字节。 pub const BODY_PREVIEW: usize = 32 * 1024; // 只保留最近 80 次开始的请求,尚未完成的老请求也可能被淘汰。 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, // None 表示尚未记录响应;完成后保存 HTTP 状态码,不据此判断业务调用是否成功。 pub status: Option, pub elapsed_ms: u128, pub request_headers: String, pub response_headers: String, // 使用单调时钟计算耗时,不受系统时间调整影响;timestamp 则用于显示 UTC 时刻。 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; // 显示时间取 Unix 秒数的当日余数;系统时间早于纪元时回退到零。这里只显示时间,不保存日期。 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(), }); // 从队首淘汰最早开始的记录。后续 body/finish 若找不到其 ID 就忽略,不重新插入旧请求。 while self.records.len() > MAX_RECORDS { self.records.pop_front(); } id } /// 更新请求正文预览,内容长度仍由活动记录策略限制。 // 线性查找在最多 80 项范围内完成;本方法覆盖之前的请求预览,不拼接分块正文。 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); } } /// 为指定请求登记响应状态、正文/头部预览与单调时钟耗时;记录已淘汰时忽略。 // 仅更新仍保留的请求,响应正文和头部都重新截断;耗时从 begin 起计算。 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(); } // UTF-8 多字节字符不能从中间切开;从字节上限回退到合法边界,确保输出始终是有效字符串。 let mut end = BODY_PREVIEW; while !value.is_char_boundary(end) { end -= 1; } format!("{}\n[preview truncated at 32 KiB]", &value[..end]) }