spacegate_kernel/service/
http_gateway.rs

1pub mod builder;
2use std::{collections::HashMap, ops::Index, sync::Arc};
3
4use crate::{
5    backend_service::ArcHyperService,
6    extension::{GatewayName, MatchedSgRouter},
7    helper_layers::{
8        map_request::{add_extension::add_extension, MapRequestLayer},
9        reload::Reloader,
10        route::{Router, RouterService},
11    },
12    utils::fold_box_layers::fold_layers,
13    BoxLayer, SgBody,
14};
15
16use hyper::{header::HOST, Request};
17
18use tower_layer::Layer;
19use tracing::{debug, instrument};
20
21use super::http_route::{match_hostname::HostnameTree, match_request::MatchRequest, HttpRoute, HttpRouter};
22
23/****************************************************************************************
24
25                                          Gateway
26
27*****************************************************************************************/
28
29pub type HttpRouterService = RouterService<HttpRoutedService, GatewayRouter, ArcHyperService>;
30
31#[derive(Debug)]
32pub struct Gateway {
33    pub gateway_name: Arc<str>,
34    pub http_routes: HashMap<String, HttpRoute>,
35    pub http_plugins: Vec<BoxLayer>,
36    pub http_fallback: ArcHyperService,
37    pub http_route_reloader: Reloader<HttpRouterService>,
38    pub ext: hyper::http::Extensions,
39}
40
41impl Gateway {
42    /// Create a new gateway layer.
43    /// # Arguments
44    /// * `gateway_name` - The gateway name, this may be used by plugins.
45    pub fn builder(gateway_name: impl Into<Arc<str>>) -> builder::GatewayBuilder {
46        builder::GatewayBuilder::new(gateway_name)
47    }
48    pub fn as_service(&self) -> ArcHyperService {
49        let gateway_name = GatewayName::new(self.gateway_name.clone());
50        let add_gateway_name_layer = MapRequestLayer::new(add_extension(gateway_name, true));
51        let gateway_plugins = self.http_plugins.iter();
52        let http_routes = self.http_routes.values();
53        let route = create_http_router(http_routes, self.http_fallback.clone());
54        #[cfg(feature = "reload")]
55        let service = {
56            let reloader = self.http_route_reloader.clone();
57            reloader.into_layer().layer(route)
58        };
59        #[cfg(not(feature = "reload"))]
60        let service = route;
61        ArcHyperService::new(add_gateway_name_layer.layer(fold_layers(gateway_plugins, ArcHyperService::new(service))))
62    }
63}
64
65#[derive(Debug, Clone)]
66pub struct HttpRoutedService {
67    services: Arc<[Vec<ArcHyperService>]>,
68}
69
70#[derive(Debug, Clone)]
71pub struct GatewayRouter {
72    pub routers: Arc<[HttpRouter]>,
73    pub hostname_tree: Arc<HostnameTree<Vec<(usize, i16)>>>,
74}
75
76impl Index<(usize, usize)> for HttpRoutedService {
77    type Output = ArcHyperService;
78    fn index(&self, index: (usize, usize)) -> &Self::Output {
79        #[allow(clippy::indexing_slicing)]
80        &self.services.as_ref()[index.0][index.1]
81    }
82}
83impl Router for GatewayRouter {
84    type Index = (usize, usize);
85    #[instrument(skip_all, fields(http.host =? req.headers().get(HOST) ))]
86    /// Route the request to the corresponding service.
87    ///
88    /// (Maybe it will be radix tree in the future.)
89    fn route(&self, req: &mut Request<SgBody>) -> Option<Self::Index> {
90        let host = req.uri().host().or(req.headers().get(HOST).and_then(|x| x.to_str().ok()))?;
91        let indices = self.hostname_tree.get(host)?;
92        for (route_index, _p) in indices {
93            for (idx1, matches) in self.routers.as_ref().index(*route_index).rules.iter().enumerate() {
94                // tracing::trace!("try match {match:?} [{route_index},{idx1}:{_p}]");
95                let index = (*route_index, idx1);
96                if let Some(ref matches) = matches {
97                    for m in matches.as_ref() {
98                        if m.match_request(req) {
99                            req.extensions_mut().insert(MatchedSgRouter(m.clone()));
100                            tracing::trace!("matches {m:?} [{route_index},{idx1}:{_p}]");
101                            if let Err(e) = m.rewrite(req) {
102                                tracing::warn!("rewrite failed: {e:?}");
103                                return None;
104                            }
105                            return Some(index);
106                        }
107                    }
108                    continue;
109                } else {
110                    tracing::trace!("matches wildcard [{route_index},{idx1}:{_p}]");
111                    return Some(index);
112                }
113            }
114        }
115        tracing::trace!("no rule matched");
116
117        None
118    }
119}
120
121pub fn create_http_router<'a>(routes: impl Iterator<Item = &'a HttpRoute>, fallback: ArcHyperService) -> RouterService<HttpRoutedService, GatewayRouter, ArcHyperService> {
122    let mut services = Vec::new();
123    let mut routers = Vec::new();
124    let mut hostname_tree = HostnameTree::<Vec<_>>::new();
125    for (idx, route) in routes.enumerate() {
126        let priority = route.priority;
127        let idx_with_priority = (idx, priority);
128        // let route_plugins = route.plugins.iter().map(SgRefLayer::new).collect::<SgRefLayer>();
129        let mut rules_services = Vec::with_capacity(route.rules.len());
130        let mut rules_router = Vec::with_capacity(route.rules.len());
131        for rule in route.rules.iter() {
132            let rule_service = fold_layers(route.plugins.iter(), ArcHyperService::new(rule.as_service()));
133            rules_services.push(rule_service);
134            rules_router.push(rule.r#match.clone());
135        }
136        if route.hostnames.is_empty() {
137            if let Some(indices) = hostname_tree.get_mut("*") {
138                indices.push(idx_with_priority)
139            } else {
140                hostname_tree.set("*", vec![idx_with_priority]);
141            }
142        } else {
143            for hostname in route.hostnames.iter() {
144                if let Some(indices) = hostname_tree.get_mut(hostname) {
145                    indices.push(idx_with_priority)
146                } else {
147                    hostname_tree.set("*", vec![idx_with_priority]);
148                }
149            }
150        }
151        services.push(rules_services);
152        routers.push(HttpRouter {
153            hostnames: route.hostnames.clone().into(),
154            rules: rules_router.into_iter().map(|x| x.map(|v| v.into_iter().map(Arc::new).collect::<Arc<[_]>>())).collect(),
155            ext: route.ext.clone(),
156        });
157    }
158
159    // sort the indices by priority
160    // we put the highest priority at the front of the vector
161    hostname_tree.iter_mut().for_each(|indices| indices.sort_unstable_by_key(|(_, p)| -*p));
162    debug!("hostname_tree: {hostname_tree:?}");
163    RouterService::new(
164        HttpRoutedService { services: services.into() },
165        GatewayRouter {
166            routers: routers.into(),
167            hostname_tree: Arc::new(hostname_tree),
168        },
169        fallback,
170    )
171}