#[cfg(test)]
mod tests;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::application::Application;
use crate::header::Header;
use crate::middleware::Middleware;
use crate::request::Request;
use crate::response::Response;
use crate::server::ConnectionInfo;
pub const DEFAULT_HEADER: &str = "X-Request-Id";
static ID_COUNTER: AtomicU64 = AtomicU64::new(0);
pub fn generate_request_id() -> String {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let count = ID_COUNTER.fetch_add(1, Ordering::Relaxed);
let mut x = nanos ^ count.wrapping_mul(0x9e3779b97f4a7c15);
x ^= x >> 30;
x = x.wrapping_mul(0xbf58476d1ce4e5b9);
x ^= x >> 27;
x = x.wrapping_mul(0x94d049bb133111eb);
x ^= x >> 31;
let mut y = count ^ nanos.wrapping_mul(0x517cc1b727220a95);
y ^= y >> 30;
y = y.wrapping_mul(0xbf58476d1ce4e5b9);
y ^= y >> 27;
y = y.wrapping_mul(0x94d049bb133111eb);
y ^= y >> 31;
format!(
"{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
(x >> 32) as u32,
((x >> 16) & 0xffff) as u16,
(x & 0xffff) as u16,
((y >> 48) & 0xffff) as u16,
y & 0xffff_ffff_ffff,
)
}
pub struct RequestIdLayer {
header: String,
}
impl RequestIdLayer {
pub fn new() -> Self {
RequestIdLayer { header: DEFAULT_HEADER.to_string() }
}
pub fn header(mut self, name: impl Into<String>) -> Self {
self.header = name.into();
self
}
}
impl Default for RequestIdLayer {
fn default() -> Self {
Self::new()
}
}
impl Middleware for RequestIdLayer {
fn handle(&self, request: &Request, connection: &ConnectionInfo, next: &dyn Application) -> Result<Response, String> {
let existing = request.get_header(self.header.clone()).map(|h| h.value.clone());
let (id, mut response) = match existing {
Some(id) => (id, next.execute(request, connection)?),
None => {
let id = generate_request_id();
let mut req = request.clone();
req.headers.push(Header { name: self.header.clone(), value: id.clone() });
(id, next.execute(&req, connection)?)
}
};
response.headers.push(Header { name: self.header.clone(), value: id });
Ok(response)
}
}