use actix_utils::future::{ready, Ready}; use std::collections::HashMap; use actix_web::{ dev::{self, ServiceResponse}, error, http::{header::ContentType, StatusCode}, middleware::{ErrorHandlerResponse, ErrorHandlers, Logger}, web, App, FromRequest, HttpRequest, HttpResponse, HttpServer, Responder, Result, }; use actix_web_lab::respond::Html; #[derive(Debug)] struct MiniJinjaRenderer { tmpl_env: web::Data>, } impl MiniJinjaRenderer { fn render( &self, tmpl: &str, ctx: impl Into, ) -> actix_web::Result { self.tmpl_env .get_template(tmpl) .map_err(|_| error::ErrorInternalServerError("could not find template"))? .render(ctx.into()) .map(Html) .map_err(|err| { log::error!("{err}"); error::ErrorInternalServerError("template error") }) } } impl FromRequest for MiniJinjaRenderer { type Error = actix_web::Error; type Future = Ready>; fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { let tmpl_env = >>::from_request(req, payload) .into_inner() .unwrap(); ready(Ok(Self { tmpl_env })) } } async fn index( tmpl_env: MiniJinjaRenderer, query: web::Query>, ) -> actix_web::Result { if let Some(name) = query.get("name") { tmpl_env.render( "user.html", minijinja::context! { name, text => "Welcome!", }, ) } else { tmpl_env.render("index.html", ()) } } #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); log::info!("starting HTTP server at http://localhost:8080"); let mut env: minijinja::Environment<'static> = minijinja::Environment::new(); env.set_source(minijinja::Source::from_path(concat!( env!("CARGO_MANIFEST_DIR"), "/templates" ))); HttpServer::new(move || { App::new() .app_data(web::Data::new(env.clone())) .service(web::resource("/").route(web::get().to(index))) .wrap(ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)) .wrap(Logger::default()) }) .workers(2) .bind(("127.0.0.1", 8080))? .run() .await } /// Error handler for a 404 Page not found error. fn not_found(svc_res: ServiceResponse) -> Result> { let res = get_error_response(&svc_res, "Page not found"); Ok(ErrorHandlerResponse::Response(ServiceResponse::new( svc_res.into_parts().0, res.map_into_right_body(), ))) } /// Generic error handler. fn get_error_response(res: &ServiceResponse, error: &str) -> HttpResponse { let req = res.request(); let tmpl_env = MiniJinjaRenderer::extract(req).into_inner().unwrap(); // Provide a fallback to a simple plain text response in case an error occurs during the // rendering of the error page. let fallback = |err: &str| { HttpResponse::build(res.status()) .content_type(ContentType::plaintext()) .body(err.to_string()) }; let ctx = minijinja::context! { error => error, status_code => res.status().as_str(), }; match tmpl_env.render("error.html", ctx) { Ok(body) => body .customize() .with_status(res.status()) .respond_to(req) .map_into_boxed_body(), Err(_) => fallback(error), } }