2022-08-31 23:25:17 +02:00
|
|
|
use axum::{
|
2023-03-29 18:20:51 +02:00
|
|
|
body,
|
|
|
|
extract::State,
|
|
|
|
middleware::Next,
|
2022-08-31 23:25:17 +02:00
|
|
|
response::{Html, IntoResponse, Response},
|
2023-03-29 18:20:51 +02:00
|
|
|
routing::get,
|
|
|
|
Router,
|
2022-08-31 23:25:17 +02:00
|
|
|
};
|
2023-03-29 18:20:51 +02:00
|
|
|
use hyper::{header::CONTENT_TYPE, Request, StatusCode};
|
2023-03-25 22:12:49 +01:00
|
|
|
use lazy_static::lazy_static;
|
2023-03-29 18:20:51 +02:00
|
|
|
use prometheus::{opts, Encoder, IntCounterVec, TextEncoder};
|
2023-03-25 22:12:49 +01:00
|
|
|
use std::sync::Arc;
|
|
|
|
use tracing::{instrument, log::*};
|
2022-08-31 23:20:59 +02:00
|
|
|
|
2023-03-29 18:03:54 +02:00
|
|
|
use crate::{AppState, WebsiteError};
|
2022-08-31 23:20:59 +02:00
|
|
|
|
2023-03-25 22:12:49 +01:00
|
|
|
pub mod posts;
|
|
|
|
|
|
|
|
lazy_static! {
|
2023-03-26 12:01:59 +02:00
|
|
|
pub static ref HIT_COUNTER: IntCounterVec = prometheus::register_int_counter_vec!(
|
2023-03-29 18:20:51 +02:00
|
|
|
opts!(
|
|
|
|
"http_requests_total",
|
|
|
|
"Total amount of http requests received"
|
|
|
|
),
|
2023-03-29 18:03:54 +02:00
|
|
|
&["route", "method", "status"]
|
2023-03-26 12:01:59 +02:00
|
|
|
)
|
|
|
|
.unwrap();
|
2023-03-25 22:12:49 +01:00
|
|
|
}
|
|
|
|
|
2023-03-29 18:19:39 +02:00
|
|
|
pub fn routes(state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
2023-03-29 18:03:54 +02:00
|
|
|
Router::new()
|
|
|
|
.route("/", get(index))
|
|
|
|
.nest("/posts", posts::router())
|
2023-03-29 18:19:39 +02:00
|
|
|
.merge(posts::alias_router(state.posts.values()))
|
2023-03-29 18:03:54 +02:00
|
|
|
.route("/healthcheck", get(healthcheck))
|
|
|
|
.route("/metrics", get(metrics))
|
|
|
|
.nest_service("/static", tower_http::services::ServeDir::new("./static"))
|
|
|
|
.layer(axum::middleware::from_fn(metrics_middleware))
|
|
|
|
}
|
|
|
|
|
2023-03-25 16:14:53 +01:00
|
|
|
#[instrument(skip(state))]
|
2023-03-25 22:12:49 +01:00
|
|
|
pub async fn index(
|
2023-03-29 18:03:54 +02:00
|
|
|
State(state): State<Arc<AppState>>,
|
|
|
|
) -> std::result::Result<Html<String>, WebsiteError> {
|
2022-08-31 23:20:59 +02:00
|
|
|
let ctx = tera::Context::new();
|
2022-08-31 23:25:17 +02:00
|
|
|
let res = state.tera.render("index.html", &ctx).map_err(|e| {
|
|
|
|
error!("Failed rendering index: {}", e);
|
2023-03-25 21:38:16 +01:00
|
|
|
WebsiteError::NotFound
|
2022-08-31 23:25:17 +02:00
|
|
|
})?;
|
2023-03-29 18:03:54 +02:00
|
|
|
Ok(Html(res))
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn healthcheck() -> &'static str {
|
|
|
|
"OK"
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn metrics() -> impl IntoResponse {
|
|
|
|
let encoder = TextEncoder::new();
|
|
|
|
let metric_families = prometheus::gather();
|
|
|
|
let mut buffer = vec![];
|
|
|
|
encoder.encode(&metric_families, &mut buffer).unwrap();
|
|
|
|
|
|
|
|
Response::builder()
|
|
|
|
.status(200)
|
|
|
|
.header(CONTENT_TYPE, encoder.format_type())
|
|
|
|
.body(body::boxed(body::Full::from(buffer)))
|
|
|
|
.unwrap()
|
2022-08-31 23:20:59 +02:00
|
|
|
}
|
|
|
|
|
2023-03-26 12:40:25 +02:00
|
|
|
pub async fn not_found() -> Response {
|
|
|
|
(StatusCode::NOT_FOUND, ()).into_response()
|
|
|
|
}
|
|
|
|
|
2023-03-29 18:03:54 +02:00
|
|
|
pub async fn metrics_middleware<B>(request: Request<B>, next: Next<B>) -> Response {
|
|
|
|
let path = request.uri().path().to_string();
|
|
|
|
let method = request.method().to_string();
|
|
|
|
let response = next.run(request).await;
|
2023-03-29 18:20:51 +02:00
|
|
|
HIT_COUNTER
|
|
|
|
.with_label_values(&[&path, &method, response.status().as_str()])
|
|
|
|
.inc();
|
2023-03-29 18:03:54 +02:00
|
|
|
response
|
|
|
|
}
|
|
|
|
|
2023-03-25 21:38:16 +01:00
|
|
|
impl IntoResponse for WebsiteError {
|
2022-08-31 23:20:59 +02:00
|
|
|
fn into_response(self) -> Response {
|
|
|
|
match self {
|
2023-03-25 21:38:16 +01:00
|
|
|
WebsiteError::NotFound => {
|
2023-03-25 16:14:53 +01:00
|
|
|
info!("not found");
|
|
|
|
(StatusCode::NOT_FOUND, ()).into_response()
|
|
|
|
}
|
2023-03-25 21:38:16 +01:00
|
|
|
WebsiteError::InternalError(e) => {
|
2023-03-25 16:14:53 +01:00
|
|
|
error!("internal error: {e}");
|
|
|
|
(StatusCode::INTERNAL_SERVER_ERROR, ()).into_response()
|
|
|
|
}
|
2022-08-31 23:20:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-26 13:05:39 +02:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
#[test]
|
|
|
|
fn render_index() {
|
|
|
|
let tera = tera::Tera::new("templates/**/*").unwrap();
|
|
|
|
let ctx = tera::Context::new();
|
|
|
|
let _res = tera.render("index.html", &ctx).unwrap();
|
|
|
|
}
|
|
|
|
}
|