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
11pub 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}