ops/
server.rs

1use std::net::SocketAddr;
2use std::sync::Arc;
3
4use crate::error::Error;
5use crate::status::Status;
6use crate::Result;
7
8use hyper::service::{make_service_fn, service_fn};
9use hyper::{header, Body, Method, Request, Response, Server, StatusCode};
10
11/// Starts a server and serves the ops endpoints.
12pub async fn server<S: Status + 'static>(addr: SocketAddr, status: S) -> Result<()> {
13    let status: Arc<S> = Arc::new(status);
14
15    let service = make_service_fn(move |_| {
16        let status = status.clone();
17
18        async { Ok::<_, Error>(service_fn(move |req| router(req, status.clone()))) }
19    });
20
21    Server::bind(&addr).serve(service).await.map_err(Into::into)
22}
23
24async fn router<S: Status + 'static>(req: Request<Body>, status: Arc<S>) -> Result<Response<Body>> {
25    match (req.method(), req.uri().path()) {
26        (&Method::GET, "/__/about") => about(status.clone()).await,
27        (&Method::GET, "/__/metrics") => metrics().await,
28        (&Method::GET, "/__/ready") => ready(status.clone()).await,
29        (&Method::GET, "/__/health") => health(status.clone()).await,
30        _ => Ok(Response::builder()
31            .status(StatusCode::NOT_FOUND)
32            .body(Body::from("not found"))?),
33    }
34}
35
36async fn ready<S: Status + 'static>(status: Arc<S>) -> Result<Response<Body>> {
37    let resp = match status.ready().await {
38        None => Response::builder()
39            .header(header::CONTENT_TYPE, "text/plain")
40            .status(StatusCode::NOT_FOUND)
41            .body(Body::from("not found"))?,
42        Some(is_ready) => {
43            if is_ready {
44                Response::builder()
45                    .header(header::CONTENT_TYPE, "text/plain")
46                    .status(StatusCode::OK)
47                    .body(Body::from("ready\n"))?
48            } else {
49                Response::builder()
50                    .header(header::CONTENT_TYPE, "text/plain")
51                    .status(StatusCode::SERVICE_UNAVAILABLE)
52                    .body(Body::from("Service unavailable"))?
53            }
54        }
55    };
56    Ok(resp)
57}
58
59async fn health<S: Status + 'static>(status: Arc<S>) -> Result<Response<Body>> {
60    let resp = match status.check().await {
61        None => Response::builder()
62            .status(StatusCode::NOT_FOUND)
63            .body(Body::from("No health checks"))?,
64        Some(resp) => match serde_json::to_string(&resp.to_json()) {
65            Ok(payload) => Response::builder()
66                .status(StatusCode::OK)
67                .header(header::CONTENT_TYPE, "application/json")
68                .body(Body::from(payload))?,
69            Err(err) => err_response(err)?,
70        },
71    };
72    Ok(resp)
73}
74
75async fn metrics() -> Result<Response<Body>> {
76    let resp = match render_metrics() {
77        Ok(rendered_metrics) => match String::from_utf8(rendered_metrics) {
78            Ok(rendered_metrics) => Response::builder()
79                .status(StatusCode::OK)
80                .header(
81                    header::CONTENT_TYPE,
82                    "text/plain; version=0.0.4; charset=utf-8",
83                )
84                .body(Body::from(rendered_metrics))?,
85            Err(err) => err_response(err)?,
86        },
87        Err(err) => err_response(err)?,
88    };
89    Ok(resp)
90}
91
92async fn about<S: Status + 'static>(status: Arc<S>) -> Result<Response<Body>> {
93    let resp = match serde_json::to_string(&status.about()) {
94        Ok(payload) => Response::builder()
95            .status(StatusCode::OK)
96            .header(header::CONTENT_TYPE, "application/json")
97            .body(Body::from(payload))?,
98        Err(err) => err_response(err)?,
99    };
100    Ok(resp)
101}
102
103fn err_response<I: Into<Error>>(err: I) -> Result<Response<Body>> {
104    let resp = Response::builder()
105        .status(StatusCode::INTERNAL_SERVER_ERROR)
106        .header(header::CONTENT_TYPE, "text/plain")
107        .body(Body::from(err.into().to_string()))?;
108    Ok(resp)
109}
110
111fn render_metrics() -> Result<Vec<u8>> {
112    use prometheus::{gather, Encoder, TextEncoder};
113
114    let metric_family = gather();
115
116    let mut writer = Vec::<u8>::new();
117    let encoder = TextEncoder::new();
118    encoder.encode(&metric_family, &mut writer)?;
119
120    Ok(writer)
121}