use std::net::SocketAddr; use askama::Template; use axum::{ async_trait, Extension, extract::{self, RequestParts, FromRequest}, http::{StatusCode, Request}, response::IntoResponse, routing::get, Router, middleware::{Next, self}, body::Body, }; use axum_extra::routing::SpaRouter; use cave::systemd; use crate::{ html_template::HtmlTemplate, RequestFactory, request_mux::RequestMux, trends::TrendsResults, }; type Mux = RequestMux; #[async_trait] impl FromRequest for Mux where B: Send, { type Rejection = (StatusCode, String); async fn from_request(req: &mut RequestParts) -> Result { let Extension(mux) = Extension::::from_request(req) .await .map_err(internal_error)?; Ok(mux) } } /// Utility function for mapping any error into a `500 Internal Server Error` /// response. fn internal_error(err: E) -> (StatusCode, String) where E: std::error::Error, { (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) } #[derive(Template)] #[template(path = "trends.html")] struct TrendsPage { language: Option, languages: Vec, results: TrendsResults, } impl TrendsPage { async fn generate(language: Option, mux: Mux) -> Self { let (results, languages) = mux.request(language.clone()).await; // redis queries done, data is ready for rendering, means the // service is very much alive: systemd::watchdog(); TrendsPage { language, languages, results, } } fn template(self) -> HtmlTemplate { HtmlTemplate(self) } } #[axum_macros::debug_handler] async fn home(Extension(mux): Extension) -> impl IntoResponse { TrendsPage::generate(None, mux) .await .template() } async fn in_language( Extension(mux): Extension, extract::Path(language): extract::Path, ) -> impl IntoResponse { TrendsPage::generate(Some(language), mux) .await .template() } async fn print_request( req: Request, next: Next, ) -> Result { log::info!( "{} {} {:?}", req.method(), req.uri(), req.headers().get("user-agent") .and_then(|ua| ua.to_str().ok()) .unwrap_or("-") ); let res = next.run(req).await; Ok(res) } pub async fn start(listen_port: u16, mux: Mux) { cave::systemd::status("Starting HTTP server"); // build our application with some routes let app = Router::new() .route("/", get(home)) .route("/in/:language", get(in_language)) .layer(Extension(mux)) .layer(middleware::from_fn(print_request)) .merge(SpaRouter::new("/assets", "assets")); // run it let addr = SocketAddr::from(([127, 0, 0, 1], listen_port)); axum::Server::bind(&addr) .serve(app.into_make_service()) .await .unwrap(); }