use std::convert::Infallible;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use bytes::Bytes;
use http_body_util::Full;
use hyper::body::Incoming;
use hyper::service::service_fn;
use hyper::{Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
use tokio::sync::Semaphore;
use crate::ServerState;
const MAX_ADMIN_CONNECTIONS: usize = 64;
const ADMIN_HEADER_READ_TIMEOUT: Duration = Duration::from_secs(15);
const ADMIN_MAX_HEADERS: usize = 100;
pub(crate) async fn serve_admin(
state: Arc<ServerState>,
addr: SocketAddr,
mut drain: tokio::sync::watch::Receiver<bool>,
) {
let listener = match TcpListener::bind(addr).await {
Ok(listener) => listener,
Err(e) => {
tracing::error!(%addr, error = %e, "failed to bind admin endpoint; observability disabled");
return;
}
};
tracing::info!(%addr, "admin endpoint listening (/metrics /healthz /readyz)");
let conn_limit = Arc::new(Semaphore::new(MAX_ADMIN_CONNECTIONS));
loop {
let accepted = tokio::select! {
_ = crate::listener::drained(&mut drain) => return,
accepted = listener.accept() => accepted,
};
let (stream, _peer) = match accepted {
Ok(pair) => pair,
Err(e) => {
tracing::warn!(error = %e, "admin accept failed");
continue;
}
};
let Ok(permit) = conn_limit.clone().acquire_owned().await else {
continue;
};
let state = state.clone();
tokio::spawn(async move {
let _permit = permit; let io = TokioIo::new(stream);
let service = service_fn(move |req| admin_handle(state.clone(), req));
if let Err(e) = hyper::server::conn::http1::Builder::new()
.timer(hyper_util::rt::TokioTimer::new())
.header_read_timeout(ADMIN_HEADER_READ_TIMEOUT)
.max_headers(ADMIN_MAX_HEADERS)
.serve_connection(io, service)
.await
{
tracing::debug!(error = %e, "admin connection closed with error");
}
});
}
}
async fn admin_handle(
state: Arc<ServerState>,
req: Request<Incoming>,
) -> Result<Response<Full<Bytes>>, Infallible> {
let (status, content_type, body) = match req.uri().path() {
"/metrics" => (
StatusCode::OK,
"text/plain; version=0.0.4; charset=utf-8",
state.metrics.render(
&state.control.filter_metrics(),
state.otlp.as_ref().map(|b| (b.dropped_spans(), b.len())),
),
),
"/healthz" => (
StatusCode::OK,
"text/plain; charset=utf-8",
"ok\n".to_string(),
),
"/readyz" => {
if *state.ready.borrow() && !*state.drain.borrow() {
(
StatusCode::OK,
"text/plain; charset=utf-8",
"ready\n".to_string(),
)
} else {
(
StatusCode::SERVICE_UNAVAILABLE,
"text/plain; charset=utf-8",
"draining\n".to_string(),
)
}
}
_ => (
StatusCode::NOT_FOUND,
"text/plain; charset=utf-8",
"not found\n".to_string(),
),
};
let response = Response::builder()
.status(status)
.header(hyper::header::CONTENT_TYPE, content_type)
.body(Full::new(Bytes::from(body)))
.unwrap_or_else(|_| Response::new(Full::new(Bytes::from_static(b"admin error\n"))));
Ok(response)
}