kube_client/client/middleware/
base_uri.rs

1//! Set base URI of requests.
2use http::{uri, Request};
3use tower::{Layer, Service};
4
5/// Layer that applies [`BaseUri`] which makes all requests relative to the URI.
6///
7/// Path in the base URI is preseved.
8#[derive(Debug, Clone)]
9pub struct BaseUriLayer {
10    base_uri: http::Uri,
11}
12
13impl BaseUriLayer {
14    /// Set base URI of requests.
15    pub fn new(base_uri: http::Uri) -> Self {
16        Self { base_uri }
17    }
18}
19
20impl<S> Layer<S> for BaseUriLayer {
21    type Service = BaseUri<S>;
22
23    fn layer(&self, inner: S) -> Self::Service {
24        BaseUri {
25            base_uri: self.base_uri.clone(),
26            inner,
27        }
28    }
29}
30
31/// Middleware that sets base URI so that all requests are relative to it.
32#[derive(Debug, Clone)]
33pub struct BaseUri<S> {
34    base_uri: http::Uri,
35    inner: S,
36}
37
38impl<S, ReqBody> Service<Request<ReqBody>> for BaseUri<S>
39where
40    S: Service<Request<ReqBody>>,
41{
42    type Error = S::Error;
43    type Future = S::Future;
44    type Response = S::Response;
45
46    fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
47        self.inner.poll_ready(cx)
48    }
49
50    fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
51        let (mut parts, body) = req.into_parts();
52        let req_pandq = parts.uri.path_and_query();
53        parts.uri = set_base_uri(&self.base_uri, req_pandq);
54        self.inner.call(Request::from_parts(parts, body))
55    }
56}
57
58// Join base URI and Path+Query, preserving any path in the base.
59fn set_base_uri(base_uri: &http::Uri, req_pandq: Option<&uri::PathAndQuery>) -> http::Uri {
60    let mut builder = uri::Builder::new();
61    if let Some(scheme) = base_uri.scheme() {
62        builder = builder.scheme(scheme.as_str());
63    }
64    if let Some(authority) = base_uri.authority() {
65        builder = builder.authority(authority.as_str());
66    }
67
68    if let Some(pandq) = base_uri.path_and_query() {
69        builder = if let Some(req_pandq) = req_pandq {
70            // Remove any trailing slashes and join.
71            // `PathAndQuery` always starts with a slash.
72            let base_path = pandq.path().trim_end_matches('/');
73            builder.path_and_query(format!("{base_path}{req_pandq}"))
74        } else {
75            builder.path_and_query(pandq.as_str())
76        };
77    } else if let Some(req_pandq) = req_pandq {
78        builder = builder.path_and_query(req_pandq.as_str());
79    }
80
81    // Joining a valid Uri and valid PathAndQuery should result in a valid Uri.
82    builder.build().expect("Valid Uri")
83}
84
85#[cfg(test)]
86mod tests {
87    #[test]
88    fn normal_host() {
89        let base_uri = http::Uri::from_static("https://192.168.1.65:8443");
90        let apipath = http::Uri::from_static("/api/v1/nodes?hi=yes");
91        let pandq = apipath.path_and_query();
92        assert_eq!(
93            super::set_base_uri(&base_uri, pandq),
94            "https://192.168.1.65:8443/api/v1/nodes?hi=yes"
95        );
96    }
97
98    #[test]
99    fn rancher_host() {
100        // in rancher, kubernetes server names are not hostnames, but a host with a path:
101        let base_uri = http::Uri::from_static("https://example.com/foo/bar");
102        let api_path = http::Uri::from_static("/api/v1/nodes?hi=yes");
103        let pandq = api_path.path_and_query();
104        assert_eq!(
105            super::set_base_uri(&base_uri, pandq),
106            "https://example.com/foo/bar/api/v1/nodes?hi=yes"
107        );
108    }
109}