use crate::route::SharedTable;
use std::time::Duration;
fn health_url(uri: &http::Uri) -> String {
let base = uri.to_string();
format!("{}/health", base.trim_end_matches('/'))
}
pub async fn health_loop(table: SharedTable, interval: Duration) {
let client = match reqwest::Client::builder()
.timeout(Duration::from_secs(2))
.build()
{
Ok(c) => c,
Err(e) => {
tracing::error!(?e, "failed to build health-check reqwest client");
return;
}
};
let mut ticker = tokio::time::interval(interval);
ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
ticker.tick().await;
let current = table.load_full();
for (_, up) in ¤t.rules {
let url = health_url(&up.uri);
let host = up
.uri
.authority()
.map_or_else(String::new, |a| a.to_string());
match client.get(&url).send().await {
Ok(r) if r.status().is_success() => {
up.mark_success();
metrics::gauge!("ferryman_upstream_alive", "upstream" => host).set(1.0);
}
_ => {
up.mark_failed();
metrics::gauge!("ferryman_upstream_alive", "upstream" => host).set(0.0);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_double_slash_for_bare_authority() {
let uri: http::Uri = "http://localhost:8001".parse().unwrap();
assert_eq!(health_url(&uri), "http://localhost:8001/health");
}
#[test]
fn preserves_non_root_path() {
let uri: http::Uri = "http://localhost:8001/base".parse().unwrap();
assert_eq!(health_url(&uri), "http://localhost:8001/base/health");
}
}