webx_host/pipeline.rs
1//! Middleware pipeline —Sequential execution model.
2//!
3//! Middlewares are called in registration order. Each middleware can
4//! inspect or modify the request. The final handler (router) is called
5//! after all middlewares have passed.
6
7use std::future::Future;
8use std::pin::Pin;
9use std::sync::Arc;
10use webx_core::error::{Error, Result};
11use webx_core::http::IHttpContext;
12use webx_core::middleware::IMiddleware;
13
14/// Boxed final handler function type.
15pub type HandlerFn = Arc<
16 dyn for<'a> Fn(
17 &'a mut dyn IHttpContext,
18 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
19 + Send
20 + Sync,
21>;
22
23pub struct MiddlewarePipeline {
24 middlewares: Vec<Arc<dyn IMiddleware>>,
25}
26
27impl MiddlewarePipeline {
28 pub fn new() -> Self {
29 Self {
30 middlewares: Vec::new(),
31 }
32 }
33
34 pub fn add_middleware(&mut self, middleware: Arc<dyn IMiddleware>) {
35 self.middlewares.push(middleware);
36 }
37
38 /// Execute middleware onion: invoke forward, final handler, after hooks reverse.
39 ///
40 /// Supports short-circuit: if a middleware's `invoke` returns
41 /// `ControlFlow::Break(())`, remaining middleware and the final handler are
42 /// skipped.
43 ///
44 /// `after` hooks always run for the middleware whose `invoke` completed,
45 /// including when the request fails: an error raised by a middleware or by
46 /// the final handler must still let metrics, tracing, gzip and cleanup
47 /// observe the response. The first error is returned after the unwind; a
48 /// failure inside an `after` hook is logged and never masks the original
49 /// error.
50 pub async fn execute(
51 &self,
52 ctx: &mut dyn IHttpContext,
53 final_handler: HandlerFn,
54 ) -> Result<()> {
55 use std::ops::ControlFlow;
56
57 let mut executed: Vec<Arc<dyn IMiddleware>> = Vec::new();
58 let mut short_circuited = false;
59 let mut failure: Option<Error> = None;
60
61 // Forward pass: invoke each middleware, track executed for after hooks
62 for middleware in &self.middlewares {
63 match middleware.invoke(ctx).await {
64 Ok(ControlFlow::Continue(())) => executed.push(Arc::clone(middleware)),
65 Ok(ControlFlow::Break(())) => {
66 short_circuited = true;
67 break;
68 }
69 Err(err) => {
70 failure = Some(err);
71 break;
72 }
73 }
74 }
75
76 // Run the final handler (router) only if not short-circuited and the
77 // forward pass did not already fail.
78 if !short_circuited && failure.is_none() {
79 if let Err(err) = final_handler(ctx).await {
80 failure = Some(err);
81 }
82 }
83
84 // Reverse pass: after hooks on executed middleware only, error path
85 // included.
86 for middleware in executed.into_iter().rev() {
87 if let Err(err) = middleware.after(ctx).await {
88 if failure.is_none() {
89 failure = Some(err);
90 } else {
91 tracing::error!(error = %err, "middleware after hook failed");
92 }
93 }
94 }
95
96 match failure {
97 Some(err) => Err(err),
98 None => Ok(()),
99 }
100 }
101}
102
103impl Default for MiddlewarePipeline {
104 fn default() -> Self {
105 Self::new()
106 }
107}