use std::future::Future; use actix_web::body::{BoxBody, MessageBody}; use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::http::header::{HeaderName, HeaderValue}; use actix_web::middleware::Next; use actix_web::{Error, HttpMessage}; use uuid::Uuid; pub const REQUEST_ID_HEADER: HeaderName = HeaderName::from_static("x-request-id"); const MAX_INBOUND_ID_LEN: usize = 128; #[derive(Debug, Clone)] pub struct RequestId(pub String); /// The request id of the request currently being handled, when one is set. pub fn request_id() -> Option { envelope::context::request_id() } /// Runs `future` with `request_id` available to nested code, so queue /// consumers keep the id of the request that produced the message. pub async fn with_request_id(request_id: Option<&str>, future: F) -> F::Output { match request_id { Some(request_id) => { envelope::context::with_request_id(request_id.to_string(), future).await } None => future.await, } } pub fn new_request_id() -> String { Uuid::now_v7().to_string() } pub async fn trace_request( request: ServiceRequest, next: Next, ) -> Result, Error> where B: MessageBody + 'static, { let request_id = request .extensions() .get::<::session::RequestId>() .map(|value| value.0.clone()) .or_else(|| inbound_id(request.headers().get(&REQUEST_ID_HEADER))) .unwrap_or_else(new_request_id); request .extensions_mut() .insert(RequestId(request_id.clone())); let response = envelope::context::with_request_id(request_id.clone(), next.call(request)).await?; let mut response = response.map_into_boxed_body(); if let Ok(value) = HeaderValue::try_from(request_id.as_str()) { response.headers_mut().insert(REQUEST_ID_HEADER, value); } Ok(response) } fn inbound_id(value: Option<&HeaderValue>) -> Option { let value = value?.to_str().ok()?.trim(); if value.is_empty() || value.len() > MAX_INBOUND_ID_LEN { return None; } Some(value.to_string()) }