arcly_http/web/
dynamic.rs1use std::collections::HashMap;
17use std::sync::Arc;
18
19use arc_swap::ArcSwap;
20use axum::http::Method;
21use axum::routing::any;
22
23use crate::core::engine::FrozenDiContainer;
24use crate::core::plugins::PluginHandler;
25use crate::http::Response;
26use crate::web::boundary::BoundaryFilter;
27use crate::web::context::RequestContext;
28
29pub const DYNAMIC_PREFIX: &str = "/_plugins";
31
32type Table = HashMap<(Method, String), PluginHandler>;
33
34pub struct DynamicRouteTable {
38 table: ArcSwap<Table>,
39}
40
41impl DynamicRouteTable {
42 pub(crate) fn new() -> Self {
43 Self {
44 table: ArcSwap::from_pointee(Table::new()),
45 }
46 }
47
48 pub fn mount<F, Fut>(&self, method: Method, path: &str, handler: F) -> Option<PluginHandler>
52 where
53 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
54 Fut: std::future::Future<Output = Response> + Send + 'static,
55 {
56 let full = format!("{DYNAMIC_PREFIX}{path}");
57 let h: PluginHandler = Arc::new(move |ctx| Box::pin(handler(ctx)));
58 let key = (method, full);
59 let mut replaced = None;
60 self.table.rcu(|cur| {
61 let mut next: Table = (**cur).clone();
62 replaced = next.insert(key.clone(), h.clone());
63 next
64 });
65 replaced
66 }
67
68 pub fn unmount(&self, method: Method, path: &str) -> bool {
70 let key = (method, format!("{DYNAMIC_PREFIX}{path}"));
71 let mut removed = false;
72 self.table.rcu(|cur| {
73 let mut next: Table = (**cur).clone();
74 removed = next.remove(&key).is_some();
75 next
76 });
77 removed
78 }
79
80 fn lookup(&self, method: &Method, path: &str) -> Option<PluginHandler> {
82 self.table
83 .load()
84 .get(&(method.clone(), path.to_owned()))
85 .cloned()
86 }
87}
88
89pub(crate) fn dynamic_dispatch_route(
92 container: &'static FrozenDiContainer,
93 globals: &'static [&'static dyn crate::web::interceptors::Interceptor],
94 filters: &'static [&'static dyn BoundaryFilter],
95) -> axum::routing::MethodRouter {
96 let handler = move |req: axum::extract::Request| async move {
97 let (parts, body) = req.into_parts();
98 let table = container.get::<DynamicRouteTable>();
99 let Some(h) = table.lookup(&parts.method, parts.uri.path()) else {
100 return Response::builder()
101 .status(404)
102 .body(axum::body::Body::from("dynamic route not found"))
103 .expect("static 404");
104 };
105 let chain = crate::web::interceptors::compose_chain(globals, h);
106 crate::web::boundary::run_entry(
109 parts,
110 body,
111 Default::default(),
112 container,
113 "/_plugins/*",
114 None,
115 filters,
116 &chain,
117 )
118 .await
119 };
120
121 any(handler)
122}