#![forbid(unsafe_code)]
#![deny(missing_docs)]
use actix_web::{
middleware::Logger,
web,
HttpServer,
};
use log::{
debug,
info,
};
#[cfg(feature = "auth")]
use actix_web::middleware::Condition;
#[cfg(feature = "auth")]
use actix_web_httpauth::middleware::HttpAuthentication;
#[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;
pub(self) struct AppState {
exporter: Box<dyn Collector>,
index_page: String,
#[cfg(feature = "auth")]
basic_auth_config: BasicAuthConfig,
}
#[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 {
Default::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<C: Collector>(self, exporter: Box<C>) -> Result<(), HttpdError>
where C: 'static + Clone + Send + Sync {
let bind_address = self.bind_address;
let index_page = render_index_page(&self.telemetry_path)?;
let telemetry_path = self.telemetry_path.clone();
#[cfg(feature = "auth")]
let basic_auth_config = match self.basic_auth_config {
Some(config) => config,
None => Default::default(),
};
debug!("Creating HTTP server app");
let app = move || {
let state = AppState {
exporter: exporter.clone(),
index_page: index_page.clone(),
#[cfg(feature = "auth")]
basic_auth_config: basic_auth_config.clone(),
};
let app = actix_web::App::new()
.data(state)
.wrap(Logger::default());
#[cfg(feature = "auth")]
let app = {
app.wrap(Condition::new(
basic_auth_config.basic_auth_users.is_some(),
HttpAuthentication::basic(auth::validate_credentials),
))
};
app
.route("/", web::get().to(index))
.route(&telemetry_path, web::get().to(metrics))
};
debug!("Attempting to bind to: {}", bind_address);
let server = HttpServer::new(app)
.bind(&bind_address)
.map_err(|e| {
HttpdError::BindAddress(format!("{}: {}", bind_address, e))
})?;
info!("Starting HTTP server on {}", bind_address);
server.run().await?;
Ok(())
}
}