extern crate serde_json;
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
#[cfg(feature = "hyper_server")]
use http::StatusCode;
#[cfg(feature = "hyper_server")]
use hyper::{
body,
service::{make_service_fn, service_fn},
Body, Client, Request, Response, Server,
};
#[cfg(feature = "hyper_server")]
use serde::de::DeserializeOwned;
#[cfg(feature = "hyper_server")]
use std::future::Future;
#[cfg(feature = "hyper_server")]
use std::sync::Arc;
mod prometheus_metric;
mod render_to_prometheus;
pub use prometheus_metric::PrometheusMetric;
pub mod prelude;
pub use render_to_prometheus::RenderToPrometheus;
mod metric_type;
mod prometheus_instance;
pub use metric_type::MetricType;
pub use prometheus_instance::{MissingValue, PrometheusInstance};
pub mod prometheus_metric_builder;
#[cfg(feature = "hyper_server")]
use hyper::http::header::CONTENT_TYPE;
#[cfg(feature = "hyper_server")]
use std::error::Error;
#[cfg(feature = "hyper_server")]
mod server_options;
#[cfg(feature = "hyper_server")]
use server_options::*;
pub trait ToAssign {}
#[derive(Debug, Clone, Copy)]
pub struct Yes {}
#[derive(Debug, Clone, Copy)]
pub struct No {}
impl ToAssign for Yes {}
impl ToAssign for No {}
#[inline]
#[cfg(feature = "hyper_server")]
async fn extract_body(
resp: hyper::client::ResponseFuture,
) -> Result<String, Box<dyn Error + Send + Sync>> {
let resp = resp.await?;
debug!("response == {:?}", resp);
let (_parts, body) = resp.into_parts();
let complete_body = body::to_bytes(body).await?;
let s = String::from_utf8(complete_body.to_vec())?;
trace!("extracted text == {}", s);
Ok(s)
}
#[cfg(feature = "hyper_server")]
pub async fn create_string_future_from_hyper_request(
request: hyper::Request<hyper::Body>,
) -> Result<String, Box<dyn Error + Send + Sync>> {
let https = hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()
.https_only()
.enable_http1()
.build();
let client = Client::builder().build::<_, hyper::Body>(https);
extract_body(client.request(request)).await
}
#[cfg(feature = "hyper_server")]
pub async fn create_deserialize_future_from_hyper_request<T>(
request: hyper::Request<hyper::Body>,
) -> Result<T, Box<dyn Error + Send + Sync>>
where
T: DeserializeOwned + std::fmt::Debug,
{
let text = create_string_future_from_hyper_request(request).await?;
let t = serde_json::from_str(&text)?;
debug!("deserialized object == {:?}", t);
Ok(t)
}
#[cfg(feature = "hyper_server")]
async fn serve_function<O, F, Fut>(
server_options: Arc<ServerOptions>,
req: Request<Body>,
f: F,
options: Arc<O>,
) -> Result<Response<Body>, hyper::Error>
where
F: FnOnce(Request<Body>, Arc<O>) -> Fut,
Fut: Future<Output = Result<String, Box<dyn Error + Send + Sync>>>,
O: std::fmt::Debug,
{
trace!(
"serve_function:: req.uri() == {}, req.method() == {}",
req.uri().path(),
req.method()
);
trace!(
"received headers ==> \n{}",
req.headers()
.iter()
.map(|(header_name, header_value)| {
format!("{} => {}", header_name, header_value.to_str().unwrap())
})
.collect::<Vec<_>>()
.join("\n")
);
let is_authorized = match &server_options.authorization {
Authorization::Basic(password) => req
.headers()
.iter()
.find(|(header_name, _)| header_name.as_str() == "authorization")
.map_or_else(
|| Ok::<_, Box<dyn Error + Send + Sync>>(false),
|(_header_name, header_value)| {
let header_value_as_str = header_value.to_str()?;
let tokens: Vec<_> = header_value_as_str.split(' ').collect();
if tokens.len() != 2 {
return Ok(false);
}
if tokens[0] != "Basic" {
return Ok(false);
}
trace!("Authorization tokens == {:?}", tokens);
let base64_decoded = base64::decode(tokens[1])?;
let password_from_header = std::str::from_utf8(&base64_decoded)?;
Ok(format!(":{}", password) == password_from_header)
},
)
.unwrap_or(false),
Authorization::None => true,
};
if !is_authorized {
Ok(Response::builder()
.status(StatusCode::UNAUTHORIZED)
.body(hyper::Body::empty())
.unwrap())
} else if req.uri().path() != "/metrics" {
Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(hyper::Body::empty())
.unwrap())
} else if req.method() != "GET" {
Ok(Response::builder()
.status(StatusCode::METHOD_NOT_ALLOWED)
.body(hyper::Body::empty())
.unwrap())
} else {
trace!("serve_function:: options == {:?}", options);
Ok(match f(req, options).await {
Ok(response) => Response::builder()
.status(StatusCode::OK)
.header(CONTENT_TYPE, "text/plain; version=0.0.4")
.body(Body::from(response))
.unwrap(),
Err(err) => {
warn!("internal server error == {:?}", err);
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from(err.to_string()))
.unwrap()
}
})
}
}
#[cfg(feature = "hyper_server")]
async fn run_server<O, F, Fut>(
server_options: ServerOptions,
options: Arc<O>,
f: F,
) -> Result<(), hyper::Error>
where
F: FnOnce(Request<Body>, Arc<O>) -> Fut + Send + Clone + Sync + 'static,
Fut: Future<Output = Result<String, Box<dyn Error + Send + Sync>>> + Send + 'static,
O: std::fmt::Debug + Sync + Send + 'static,
{
info!("Listening on http://{}/metrics", server_options.addr);
let f = f.clone();
let options = options.clone();
let addr = server_options.addr;
let server_options = Arc::new(server_options);
let make_service = make_service_fn(move |_| {
let f = f.clone();
let options = options.clone();
let server_options = server_options.clone();
async move {
Ok::<_, hyper::Error>(service_fn(move |req| {
serve_function(server_options.clone(), req, f.clone(), options.clone())
}))
}
});
let serve_future = Server::bind(&addr).serve(make_service);
serve_future.await
}
#[cfg(feature = "hyper_server")]
pub async fn render_prometheus<O, F, Fut>(server_options: ServerOptions, options: O, f: F)
where
F: FnOnce(Request<Body>, Arc<O>) -> Fut + Send + Clone + Sync + 'static,
Fut: Future<Output = Result<String, Box<dyn Error + Send + Sync>>> + Send + 'static,
O: std::fmt::Debug + Sync + Send + 'static,
{
let o = Arc::new(options);
let _ = run_server(server_options, o, f).await.map_err(|err| {
error!("{:?}", err);
eprintln!("Server failure: {:?}", err)
});
}