Skip to content

Commit

Permalink
Add SetBaseUriLayer to set base URI of requests
Browse files Browse the repository at this point in the history
  • Loading branch information
kazk committed May 31, 2021
1 parent b21812b commit 357938b
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 66 deletions.
3 changes: 2 additions & 1 deletion examples/custom_client.rs
Expand Up @@ -11,6 +11,7 @@ use tower::ServiceBuilder;

use kube::{
api::{Api, DeleteParams, ListParams, PostParams, ResourceExt, WatchEvent},
service::SetBaseUriLayer,
Client, Config,
};

Expand All @@ -22,7 +23,7 @@ async fn main() -> anyhow::Result<()> {
let config = Config::infer().await?;
let cluster_url = config.cluster_url.clone();
let common = ServiceBuilder::new()
.map_request(move |r| kube::set_cluster_url(r, &cluster_url))
.layer(SetBaseUriLayer::new(cluster_url))
.into_inner();
let mut http = HttpConnector::new();
http.enforce_http(false);
Expand Down
4 changes: 2 additions & 2 deletions kube/src/client/mod.rs
Expand Up @@ -33,7 +33,7 @@ use crate::service::{accept_compressed, maybe_decompress};
use crate::{
api::WatchEvent,
error::{ConfigError, ErrorResponse},
service::{set_cluster_url, set_default_headers, AuthLayer, Authentication, LogRequest},
service::{set_default_headers, AuthLayer, Authentication, LogRequest, SetBaseUriLayer},
Config, Error, Result,
};

Expand Down Expand Up @@ -411,7 +411,7 @@ impl TryFrom<Config> for Client {
};

let common = ServiceBuilder::new()
.map_request(move |r| set_cluster_url(r, &cluster_url))
.layer(SetBaseUriLayer::new(cluster_url))
.map_request(move |r| set_default_headers(r, default_headers.clone()))
.into_inner();

Expand Down
5 changes: 1 addition & 4 deletions kube/src/lib.rs
Expand Up @@ -107,10 +107,7 @@ cfg_client! {
pub mod api;
pub mod discovery;
pub mod client;
pub(crate) mod service;
// Export this for examples for now.
#[doc(hidden)]
pub use service::set_cluster_url;
pub mod service;

#[doc(inline)]
pub use api::Api;
Expand Down
109 changes: 109 additions & 0 deletions kube/src/service/base_uri.rs
@@ -0,0 +1,109 @@
//! Set base URI of requests.
use http::{uri, Request};
use tower::{Layer, Service};

/// Layer that applies [`SetBaseUri`] which makes all requests relative to the base URI.
///
/// Path in `base_uri` is preseved.
#[derive(Debug, Clone)]
pub struct SetBaseUriLayer {
base_uri: http::Uri,
}

impl SetBaseUriLayer {
/// Set base URI of requests.
pub fn new(base_uri: http::Uri) -> Self {
Self { base_uri }
}
}

impl<S> Layer<S> for SetBaseUriLayer {
type Service = SetBaseUri<S>;

fn layer(&self, inner: S) -> Self::Service {
SetBaseUri {
base_uri: self.base_uri.clone(),
inner,
}
}
}

/// Middleware that sets base URI so that all requests are relative to it.
#[derive(Debug, Clone)]
pub struct SetBaseUri<S> {
base_uri: http::Uri,
inner: S,
}

impl<S, ReqBody> Service<Request<ReqBody>> for SetBaseUri<S>
where
S: Service<Request<ReqBody>>,
{
type Error = S::Error;
type Future = S::Future;
type Response = S::Response;

fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}

fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
let (mut parts, body) = req.into_parts();
let req_pandq = parts.uri.path_and_query();
parts.uri = set_base_uri(&self.base_uri, req_pandq);
self.inner.call(Request::from_parts(parts, body))
}
}

// Join base URI and Path+Query, preserving any path in the base.
fn set_base_uri(base_uri: &http::Uri, req_pandq: Option<&uri::PathAndQuery>) -> http::Uri {
let mut builder = uri::Builder::new();
if let Some(scheme) = base_uri.scheme() {
builder = builder.scheme(scheme.as_str());
}
if let Some(authority) = base_uri.authority() {
builder = builder.authority(authority.as_str());
}

if let Some(pandq) = base_uri.path_and_query() {
builder = if let Some(req_pandq) = req_pandq {
// Remove any trailing slashes and join.
// `PathAndQuery` always starts with a slash.
let base_path = pandq.path().trim_end_matches('/');
builder.path_and_query(format!("{}{}", base_path, req_pandq))
} else {
builder.path_and_query(pandq.as_str())
};
} else if let Some(req_pandq) = req_pandq {
builder = builder.path_and_query(req_pandq.as_str());
}

// Joining a valid Uri and valid PathAndQuery should result in a valid Uri.
builder.build().expect("Valid Uri")
}

#[cfg(test)]
mod tests {
#[test]
fn normal_host() {
let base_uri = http::Uri::from_static("https://192.168.1.65:8443");
let apipath = http::Uri::from_static("/api/v1/nodes?hi=yes");
let pandq = apipath.path_and_query();
assert_eq!(
super::set_base_uri(&base_uri, pandq),
"https://192.168.1.65:8443/api/v1/nodes?hi=yes"
);
}

#[test]
fn rancher_host() {
// in rancher, kubernetes server names are not hostnames, but a host with a path:
let base_uri = http::Uri::from_static("https://example.com/foo/bar");
let api_path = http::Uri::from_static("/api/v1/nodes?hi=yes");
let pandq = api_path.path_and_query();
assert_eq!(
super::set_base_uri(&base_uri, pandq),
"https://example.com/foo/bar/api/v1/nodes?hi=yes"
);
}
}
6 changes: 3 additions & 3 deletions kube/src/service/mod.rs
@@ -1,16 +1,16 @@
//! Abstracts the connection to Kubernetes API server.
//! Middleware for customizing client.

mod auth;
mod base_uri;
#[cfg(feature = "gzip")] mod compression;
mod headers;
mod log;
mod url;

#[cfg(feature = "gzip")]
pub(crate) use self::compression::{accept_compressed, maybe_decompress};
pub use self::url::set_cluster_url;
pub(crate) use self::{
auth::{AuthLayer, Authentication},
headers::set_default_headers,
log::LogRequest,
};
pub use base_uri::{SetBaseUri, SetBaseUriLayer};
56 changes: 0 additions & 56 deletions kube/src/service/url.rs

This file was deleted.

0 comments on commit 357938b

Please sign in to comment.