#![forbid(unsafe_code)]
#![deny(missing_docs)]
use axum::body::Bytes;
use axum::routing;
use axum::Router;
use parking_lot::Mutex;
use std::net::SocketAddr;
use std::str::FromStr;
use std::sync::Arc;
use tower_http::trace::TraceLayer;
use tracing::{
debug,
info,
};
#[cfg(feature = "auth")]
use axum::middleware;
#[cfg(feature = "auth")]
pub mod auth;
mod collector;
mod errors;
mod handlers;
mod templates;
#[cfg(feature = "auth")]
pub use auth::BasicAuthConfig;
use handlers::{
index,
metrics,
};
use templates::render_index_page;
pub use collector::Collector;
pub use errors::HttpdError;
use super::Exporter;
pub struct AppState {
index_page: Bytes,
#[cfg(feature = "auth")]
basic_auth_config: BasicAuthConfig,
}
pub struct AppExporter {
exporter: Exporter,
}
#[derive(Debug)]
pub struct Server {
bind_address: String,
telemetry_path: String,
#[cfg(feature = "auth")]
basic_auth_config: Option<BasicAuthConfig>,
}
impl Default for Server {
fn default() -> Self {
Self {
bind_address: "127.0.0.1:9452".into(),
telemetry_path: "/metrics".into(),
#[cfg(feature = "auth")]
basic_auth_config: None,
}
}
}
impl Server {
pub fn new() -> Self {
Self::default()
}
#[cfg(feature = "auth")]
pub fn auth_config(mut self, config: BasicAuthConfig) -> Self {
debug!("Setting HTTP basic auth config");
self.basic_auth_config = Some(config);
self
}
pub fn bind_address(mut self, bind_address: String) -> Self {
debug!("Setting server bind_address to: {}", bind_address);
self.bind_address = bind_address;
self
}
pub fn telemetry_path(mut self, telemetry_path: String) -> Self {
debug!("Setting server telemetry_path to: {}", telemetry_path);
self.telemetry_path = telemetry_path;
self
}
pub async fn run(self, exporter: Exporter) -> Result<(), HttpdError> {
let index_page = render_index_page(&self.telemetry_path)?;
#[cfg(feature = "auth")]
let basic_auth_config = match self.basic_auth_config {
Some(config) => config,
None => BasicAuthConfig::default(),
};
#[cfg(feature = "auth")]
let enable_http_auth = basic_auth_config.has_users();
let app_exporter = AppExporter {
exporter: exporter,
};
let app_exporter = Arc::new(Mutex::new(app_exporter));
let state = AppState {
index_page: index_page,
#[cfg(feature = "auth")]
basic_auth_config: basic_auth_config,
};
let state = Arc::new(state);
#[cfg(feature = "auth")]
let auth_layer = middleware::from_fn_with_state(
state.clone(),
auth::validate_credentials,
);
debug!("Creating HTTP server app");
#[allow(unused_mut)]
let mut app = Router::new()
.route("/", routing::get(index))
.with_state(state)
.route(&self.telemetry_path, routing::get(metrics))
.with_state(app_exporter);
#[cfg(feature = "auth")]
if enable_http_auth {
app = app.route_layer(auth_layer);
}
let app = app
.layer(TraceLayer::new_for_http());
debug!("Attempting to bind to: {}", &self.bind_address);
let addr = SocketAddr::from_str(&self.bind_address).map_err(|e| {
let address = &self.bind_address;
HttpdError::BindAddress(format!("{address}: {e}"))
})?;
let server = axum::Server::bind(&addr)
.serve(app.into_make_service());
info!("Starting HTTP server on {}", &self.bind_address);
server.await?;
Ok(())
}
}