use axum::{ extract::Request, http::{header::HeaderName, HeaderValue, Request as HttpRequest}, middleware::Next, response::Response, }; use uuid::Uuid; pub const X_REQUEST_ID_HEADER: &str = "x-request-id"; // 当前阶段先把请求级元信息统一挂到 extensions,后续响应头、envelope 与错误处理中间件继续复用。 #[derive(Clone, Debug)] pub struct RequestContext { request_id: String, } impl RequestContext { pub fn new(request_id: String) -> Self { Self { request_id } } pub fn request_id(&self) -> &str { &self.request_id } } pub async fn attach_request_context(mut request: Request, next: Next) -> Response { let request_id = request .headers() .get(X_REQUEST_ID_HEADER) .and_then(|value| value.to_str().ok()) .map(str::trim) .filter(|value| !value.is_empty()) .map(ToOwned::to_owned) .unwrap_or_else(|| Uuid::new_v4().to_string()); request .extensions_mut() .insert(RequestContext::new(request_id.clone())); // 统一把 request_id 写回请求头,方便后续 tracing、响应头与 envelope 层读取同一来源。 if let Ok(header_value) = HeaderValue::from_str(&request_id) { request .headers_mut() .insert(HeaderName::from_static(X_REQUEST_ID_HEADER), header_value); } next.run(request).await } pub fn resolve_request_id(request: &HttpRequest) -> Option { request .extensions() .get::() .map(|context| context.request_id().to_string()) .or_else(|| { request .headers() .get(X_REQUEST_ID_HEADER) .and_then(|value| value.to_str().ok()) .map(str::trim) .filter(|value| !value.is_empty()) .map(ToOwned::to_owned) }) }