lmrc_proxy/
middleware.rs

1//! Proxy middleware
2//!
3//! Axum middleware for subdomain extraction and proxy routing.
4
5use axum::{
6    extract::{Request, State},
7    http::HeaderMap,
8    middleware::Next,
9    response::Response,
10};
11use std::sync::Arc;
12
13use crate::{client::{proxy_request, ProxyConfig}, error::ProxyError, routing::{extract_subdomain, RouteResolver}};
14
15/// Subdomain marker type for request extensions
16#[derive(Debug, Clone)]
17pub struct Subdomain(pub String);
18
19/// Extract subdomain from Host header and attach to request extensions
20///
21/// ## Example
22///
23/// ```rust,ignore
24/// use axum::{Router, middleware};
25/// use lmrc_proxy::middleware::subdomain_extractor;
26///
27/// let app = Router::new()
28///     .layer(middleware::from_fn(subdomain_extractor));
29/// ```
30pub async fn subdomain_extractor(
31    headers: HeaderMap,
32    mut request: Request,
33    next: Next,
34) -> Response {
35    let subdomain = headers
36        .get("host")
37        .and_then(|host| host.to_str().ok())
38        .and_then(extract_subdomain);
39
40    if let Some(subdomain) = subdomain {
41        request.extensions_mut().insert(Subdomain(subdomain));
42    }
43
44    next.run(request).await
45}
46
47/// Proxy middleware that routes based on subdomain
48///
49/// ## Example
50///
51/// ```rust,ignore
52/// use axum::{Router, middleware};
53/// use lmrc_proxy::{middleware::subdomain_proxy, routing::StaticRouteResolver, ProxyConfig};
54/// use std::sync::Arc;
55///
56/// let resolver = Arc::new(
57///     StaticRouteResolver::new()
58///         .add_route("api", "http://api-service:8080")
59/// );
60///
61/// let app = Router::new()
62///     .layer(middleware::from_fn_with_state(
63///         (resolver, ProxyConfig::default()),
64///         subdomain_proxy
65///     ));
66/// ```
67pub async fn subdomain_proxy<R>(
68    State((resolver, config)): State<(Arc<R>, ProxyConfig)>,
69    request: Request,
70) -> Result<Response, ProxyError>
71where
72    R: RouteResolver,
73{
74    // Extract subdomain from extensions (set by subdomain_extractor middleware)
75    let subdomain = request
76        .extensions()
77        .get::<Subdomain>()
78        .map(|s| s.0.clone());
79
80    let subdomain = subdomain.ok_or_else(|| ProxyError::RouteNotFound("No subdomain found".to_string()))?;
81
82    // Resolve backend URL
83    let backend_url = resolver
84        .resolve(&subdomain)
85        .await
86        .ok_or_else(|| ProxyError::RouteNotFound(format!("No route for subdomain: {}", subdomain)))?;
87
88    // Proxy request
89    proxy_request(request, &backend_url, config).await
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_subdomain_type() {
98        let subdomain = Subdomain("api".to_string());
99        assert_eq!(subdomain.0, "api");
100    }
101}