use axum::extract::{MatchedPath, Request};
use axum::http::{HeaderValue, Method};
use axum::middleware::Next;
use axum::response::Response;
use notedthat_core::metrics::{ROUTE_UNMATCHED, label, name, surface};
use std::time::Instant;
use crate::router::helpers::SOURCE_HEADER;
const WEBDAV_PREFIX: &str = "/webdav";
const MCP_ROUTE: &str = "/mcp";
const SSE_PREFIX: &str = "/sse";
const BROWSE_PREFIX: &str = "/browse";
const API_PREFIX: &str = "/api/v1";
fn method_label(method: &Method) -> &'static str {
match *method {
Method::GET => "GET",
Method::HEAD => "HEAD",
Method::POST => "POST",
Method::PUT => "PUT",
Method::DELETE => "DELETE",
Method::PATCH => "PATCH",
Method::OPTIONS => "OPTIONS",
Method::TRACE => "TRACE",
Method::CONNECT => "CONNECT",
_ => match method.as_str() {
"PROPFIND" => "PROPFIND",
"PROPPATCH" => "PROPPATCH",
"MKCOL" => "MKCOL",
"COPY" => "COPY",
"MOVE" => "MOVE",
"LOCK" => "LOCK",
"UNLOCK" => "UNLOCK",
_ => "other",
},
}
}
fn surface_of(route: &str, source: Option<&HeaderValue>) -> &'static str {
if route == ROUTE_UNMATCHED {
return surface::ROOT;
}
if route.starts_with(WEBDAV_PREFIX) {
return surface::WEBDAV;
}
if route == MCP_ROUTE || route.starts_with(SSE_PREFIX) {
return surface::MCP;
}
if route.starts_with(BROWSE_PREFIX) {
return surface::BROWSE;
}
if route.starts_with(API_PREFIX) {
return if source.map(HeaderValue::as_bytes) == Some(b"mcp") {
surface::MCP
} else {
surface::API
};
}
surface::ROOT
}
struct InFlight(&'static str);
impl InFlight {
fn enter(surface: &'static str) -> Self {
metrics::gauge!(name::HTTP_IN_FLIGHT, label::SURFACE => surface).increment(1.0);
Self(surface)
}
}
impl Drop for InFlight {
fn drop(&mut self) {
metrics::gauge!(name::HTTP_IN_FLIGHT, label::SURFACE => self.0).decrement(1.0);
}
}
pub async fn track_requests(req: Request, next: Next) -> Response {
let route = req
.extensions()
.get::<MatchedPath>()
.map_or_else(|| ROUTE_UNMATCHED.to_string(), |m| m.as_str().to_string());
let surface = surface_of(&route, req.headers().get(SOURCE_HEADER));
let method = method_label(req.method());
let _in_flight = InFlight::enter(surface);
let started = Instant::now();
let response = next.run(req).await;
let elapsed = started.elapsed().as_secs_f64();
let status = response.status().as_u16().to_string();
metrics::counter!(
name::HTTP_REQUESTS,
label::SURFACE => surface,
label::ROUTE => route.clone(),
label::METHOD => method,
label::STATUS => status,
)
.increment(1);
metrics::histogram!(
name::HTTP_REQUEST_DURATION,
label::SURFACE => surface,
label::ROUTE => route,
label::METHOD => method,
)
.record(elapsed);
response
}
#[cfg(test)]
mod tests {
use super::{method_label, surface_of};
use axum::http::{HeaderValue, Method};
use notedthat_core::metrics::{ROUTE_UNMATCHED, surface};
#[test]
fn a_route_pattern_decides_the_surface() {
for (route, expected) in [
("/webdav/{*path}", surface::WEBDAV),
("/webdav", surface::WEBDAV),
("/mcp", surface::MCP),
("/sse/{*path}", surface::MCP),
("/browse/{*path}", surface::BROWSE),
(
"/api/v1/knowledgebases/{kb_slug}/{*object_path}",
surface::API,
),
("/healthz", surface::ROOT),
("/readyz", surface::ROOT),
("/llms.txt", surface::ROOT),
(ROUTE_UNMATCHED, surface::ROOT),
] {
assert_eq!(surface_of(route, None), expected, "for {route}");
}
}
#[test]
fn an_api_call_the_mcp_server_made_is_attributed_to_mcp() {
let mcp = HeaderValue::from_static("mcp");
assert_eq!(
surface_of("/api/v1/knowledgebases", Some(&mcp)),
surface::MCP
);
}
#[test]
fn the_source_header_cannot_move_another_surface_or_invent_one() {
let mcp = HeaderValue::from_static("mcp");
assert_eq!(surface_of("/webdav/{*path}", Some(&mcp)), surface::WEBDAV);
assert_eq!(surface_of("/browse", Some(&mcp)), surface::BROWSE);
let nonsense = HeaderValue::from_static("../../etc/passwd");
assert_eq!(
surface_of("/api/v1/knowledgebases", Some(&nonsense)),
surface::API,
"an unrecognised source is the default surface, never a label of its own"
);
}
#[test]
fn an_invented_verb_shares_one_series() {
let invented =
Method::from_bytes(b"BREWCOFFEE").expect("an extension method is a valid token");
assert_eq!(method_label(&invented), "other");
assert_eq!(method_label(&Method::GET), "GET");
assert_eq!(
method_label(&Method::from_bytes(b"PROPFIND").expect("PROPFIND is a valid token")),
"PROPFIND"
);
}
}