1use crate::handler::{BoxFuture, Handler};
2use crate::request::Request;
3use crate::response::Response;
4use std::future::Future;
5use std::sync::Arc;
6
7pub type Next = Box<dyn FnOnce(Request) -> BoxFuture<Response> + Send>;
9
10pub type Middleware = Arc<dyn Fn(Request, Next) -> BoxFuture<Response> + Send + Sync>;
12
13#[derive(Clone)]
15pub struct MwEntry {
16 pub name: String,
17 pub mw: Middleware,
18}
19
20pub fn named(name: impl Into<String>, mw: impl IntoMiddleware) -> MwEntry {
22 MwEntry {
23 name: name.into(),
24 mw: mw.into_middleware(),
25 }
26}
27
28pub(crate) fn abbreviate_type_name(full: &str) -> String {
29 let trimmed = full
30 .trim_end_matches(">::{{closure}}")
31 .trim_end_matches("::{{closure}}")
32 .trim_end_matches("{{closure}}");
33 let base = trimmed.rsplit("::").next().unwrap_or(trimmed);
34 if base.is_empty() || base == "{{closure}}" {
35 "closure".into()
36 } else {
37 base.to_string()
38 }
39}
40
41pub trait IntoMiddleware {
42 fn into_middleware(self) -> Middleware;
43}
44
45pub trait IntoMwEntry {
47 fn into_mw_entry(self) -> MwEntry;
48}
49
50impl<F, Fut> IntoMiddleware for F
51where
52 F: Fn(Request, Next) -> Fut + Send + Sync + 'static,
53 Fut: Future<Output = Response> + Send + 'static,
54{
55 fn into_middleware(self) -> Middleware {
56 Arc::new(move |req, next| Box::pin(self(req, next)))
57 }
58}
59
60impl IntoMiddleware for Middleware {
61 fn into_middleware(self) -> Middleware {
62 self
63 }
64}
65
66impl IntoMwEntry for MwEntry {
67 fn into_mw_entry(self) -> MwEntry {
68 self
69 }
70}
71
72impl IntoMwEntry for Middleware {
73 fn into_mw_entry(self) -> MwEntry {
74 MwEntry {
75 name: "mw".into(),
76 mw: self,
77 }
78 }
79}
80
81impl<F, Fut> IntoMwEntry for F
82where
83 F: Fn(Request, Next) -> Fut + Send + Sync + 'static,
84 Fut: Future<Output = Response> + Send + 'static,
85{
86 fn into_mw_entry(self) -> MwEntry {
87 MwEntry {
88 name: abbreviate_type_name(std::any::type_name::<F>()),
89 mw: self.into_middleware(),
90 }
91 }
92}
93
94pub fn with_state<S, F, Fut>(state: S, f: F) -> Middleware
100where
101 S: Send + Sync + 'static,
102 F: Fn(Arc<S>, Request, Next) -> Fut + Send + Sync + 'static,
103 Fut: Future<Output = Response> + Send + 'static,
104{
105 let state = Arc::new(state);
106 Arc::new(move |req, next| Box::pin(f(Arc::clone(&state), req, next)))
107}
108
109pub fn with_leaked<S, F, Fut>(state: S, f: F) -> Middleware
114where
115 S: Send + Sync + 'static,
116 F: Fn(&'static S, Request, Next) -> Fut + Send + Sync + 'static,
117 Fut: Future<Output = Response> + Send + 'static,
118{
119 let state: &'static S = Box::leak(Box::new(state));
120 Arc::new(move |req, next| Box::pin(f(state, req, next)))
121}
122
123pub fn before<F, Fut>(name: impl Into<String>, f: F) -> MwEntry
125where
126 F: Fn(Request) -> Fut + Send + Sync + 'static,
127 Fut: Future<Output = Request> + Send + 'static,
128{
129 let f = Arc::new(f);
130 named(name, move |req, next: Next| {
131 let f = Arc::clone(&f);
132 async move {
133 let req = f(req).await;
134 next(req).await
135 }
136 })
137}
138
139pub fn after<F, Fut>(name: impl Into<String>, f: F) -> MwEntry
141where
142 F: Fn(Response) -> Fut + Send + Sync + 'static,
143 Fut: Future<Output = Response> + Send + 'static,
144{
145 let f = Arc::new(f);
146 named(name, move |req, next: Next| {
147 let f = Arc::clone(&f);
148 async move {
149 let res = next(req).await;
150 f(res).await
151 }
152 })
153}
154
155pub fn around<B, BF, A, AF>(name: impl Into<String>, before_fn: B, after_fn: A) -> MwEntry
157where
158 B: Fn(Request) -> BF + Send + Sync + 'static,
159 BF: Future<Output = Request> + Send + 'static,
160 A: Fn(Response) -> AF + Send + Sync + 'static,
161 AF: Future<Output = Response> + Send + 'static,
162{
163 let before_fn = Arc::new(before_fn);
164 let after_fn = Arc::new(after_fn);
165 named(name, move |req, next: Next| {
166 let before_fn = Arc::clone(&before_fn);
167 let after_fn = Arc::clone(&after_fn);
168 async move {
169 let req = before_fn(req).await;
170 let res = next(req).await;
171 after_fn(res).await
172 }
173 })
174}
175
176pub fn map_html<F>(name: impl Into<String>, transform: F) -> MwEntry
180where
181 F: Fn(&str) -> Option<String> + Send + Sync + 'static,
182{
183 let transform = Arc::new(transform);
184 after(name, move |mut res| {
185 let transform = Arc::clone(&transform);
186 async move {
187 res.map_buffered_html(|html| transform(html));
188 res
189 }
190 })
191}
192
193pub fn build_chain(middleware: &[Middleware], handler: Handler) -> Handler {
195 let mut next = handler;
196
197 for mw in middleware.iter().rev() {
198 let mw = Arc::clone(mw);
199 let inner = next;
200 next = Arc::new(move |req| {
201 let inner = Arc::clone(&inner);
202 let mw = Arc::clone(&mw);
203 mw(
204 req,
205 Box::new(move |r| {
206 let inner = Arc::clone(&inner);
207 inner(r)
208 }),
209 )
210 });
211 }
212
213 next
214}
215
216pub(crate) fn chain_from_entries(entries: &[MwEntry], handler: Handler) -> Handler {
217 let mws: Vec<Middleware> = entries.iter().map(|e| Arc::clone(&e.mw)).collect();
218 build_chain(&mws, handler)
219}
220
221pub fn logger() -> MwEntry {
224 named("logger", |req: Request, next: Next| async move {
225 let method = req.method.as_str().to_string();
226 let path = req.path.clone();
227 let quiet = logger_should_skip(&path);
228 let user_agent = req.header("user-agent").unwrap_or("-").to_string();
229 let peer = req
230 .get::<crate::server::ClientAddr>()
231 .map(|a| a.0.to_string())
232 .unwrap_or_else(|| "-".into());
233 let request_id = req
234 .get::<crate::request_id::RequestId>()
235 .map(|r| r.0.clone())
236 .unwrap_or_default();
237 let start = std::time::Instant::now();
238 let res = next(req).await;
239 if quiet {
240 return res;
241 }
242 let status = res.status_code().as_u16();
243 let latency_ms = start.elapsed().as_millis() as u64;
244 if request_id.is_empty() {
245 tracing::info!(
246 method = %method,
247 path = %path,
248 status,
249 latency_ms,
250 peer = %peer,
251 user_agent = %user_agent,
252 "request"
253 );
254 } else {
255 tracing::info!(
256 request_id = %request_id,
257 method = %method,
258 path = %path,
259 status,
260 latency_ms,
261 peer = %peer,
262 user_agent = %user_agent,
263 "request"
264 );
265 }
266 res
267 })
268}
269
270static LOGGER_SKIP_PREFIXES: std::sync::OnceLock<std::sync::Mutex<Vec<String>>> =
271 std::sync::OnceLock::new();
272
273fn logger_skip_list() -> &'static std::sync::Mutex<Vec<String>> {
274 LOGGER_SKIP_PREFIXES.get_or_init(|| {
275 std::sync::Mutex::new(vec!["/favicon.ico".into()])
277 })
278}
279
280pub fn logger_skip_path(prefix: impl Into<String>) {
283 let p = prefix.into();
284 if p.is_empty() {
285 return;
286 }
287 let mut g = logger_skip_list().lock().unwrap();
288 if !g.iter().any(|x| x == &p) {
289 g.push(p);
290 }
291}
292
293pub fn logger_skip_paths(prefixes: impl IntoIterator<Item = impl Into<String>>) {
295 for p in prefixes {
296 logger_skip_path(p);
297 }
298}
299
300pub fn logger_should_skip(path: &str) -> bool {
302 let g = logger_skip_list().lock().unwrap();
303 g.iter()
304 .any(|p| path == p.as_str() || path.starts_with(&format!("{p}/")))
305}
306
307#[cfg(any(test, feature = "testing"))]
308pub fn logger_clear_skip_paths() {
309 *logger_skip_list().lock().unwrap() = vec!["/favicon.ico".into()];
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use http::Method;
316
317 fn ok_handler() -> Handler {
318 Arc::new(|_req: Request| Box::pin(async { Response::text("ok") }))
319 }
320
321 #[tokio::test]
322 async fn onion_order() {
323 let leaf = ok_handler();
324 let outer = |req: Request, next: Next| async move {
325 let mut res = next(req).await;
326 res = res.header("x-outer", "1");
327 res
328 };
329 let inner = |req: Request, next: Next| async move {
330 let mut res = next(req).await;
331 res = res.header("x-inner", "1");
332 res
333 };
334 let chain = build_chain(&[outer.into_middleware(), inner.into_middleware()], leaf);
335 let res = chain(Request::new(Method::GET, "/")).await;
336 assert_eq!(res.body_bytes(), Some(b"ok".as_slice()));
337 assert_eq!(
338 res.headers.get("x-outer").map(|v| v.to_str().unwrap()),
339 Some("1")
340 );
341 assert_eq!(
342 res.headers.get("x-inner").map(|v| v.to_str().unwrap()),
343 Some("1")
344 );
345 }
346
347 #[tokio::test]
348 async fn with_state_runs() {
349 let leaf = ok_handler();
350 let mw = with_state(7u32, |n, req, next| async move {
351 assert_eq!(*n, 7);
352 next(req).await
353 });
354 let chain = build_chain(&[mw], leaf);
355 assert_eq!(
356 chain(Request::new(Method::GET, "/")).await.body_bytes(),
357 Some(b"ok".as_slice())
358 );
359 }
360
361 #[test]
362 fn logger_skip_favicon_by_default() {
363 logger_clear_skip_paths();
364 assert!(logger_should_skip("/favicon.ico"));
365 assert!(!logger_should_skip("/favicon"));
366 logger_clear_skip_paths();
367 }
368
369 #[test]
370 fn logger_skip_matches_prefixes() {
371 logger_clear_skip_paths();
372 logger_skip_path("/_devtools");
373 logger_skip_path("/healthz");
374 assert!(logger_should_skip("/_devtools"));
375 assert!(logger_should_skip("/_devtools/config"));
376 assert!(logger_should_skip("/_devtools/requests/dt-1"));
377 assert!(logger_should_skip("/healthz"));
378 assert!(!logger_should_skip("/api/users"));
379 assert!(!logger_should_skip("/health"));
380 logger_clear_skip_paths();
381 }
382
383 #[test]
384 fn abbreviate_closure() {
385 let n = abbreviate_type_name("foo::bar::{{closure}}");
386 assert_eq!(n, "bar");
387 }
388}