Migrate to actix-web #8
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1215,9 +1215,11 @@ dependencies = [
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"clap",
|
"clap",
|
||||||
|
"derive_more",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"log",
|
"log",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -7,6 +7,8 @@ edition = "2021"
|
||||||
actix-files = "0.6.2"
|
actix-files = "0.6.2"
|
||||||
actix-web = "4.1.0"
|
actix-web = "4.1.0"
|
||||||
clap = { version = "3.2.17", features = ["derive"] }
|
clap = { version = "3.2.17", features = ["derive"] }
|
||||||
|
derive_more = "0.99.17"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
handlebars = { version = "4.3.3", features = ["dir_source"] }
|
handlebars = { version = "4.3.3", features = ["dir_source"] }
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
|
serde_json = "1.0.83"
|
||||||
|
|
76
src/errors.rs
Normal file
76
src/errors.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use actix_web::body::BoxBody;
|
||||||
|
use actix_web::dev::ServiceResponse;
|
||||||
|
use actix_web::http::header::ContentType;
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::middleware::ErrorHandlerResponse;
|
||||||
|
use actix_web::{web, HttpResponse, ResponseError};
|
||||||
|
use derive_more::{Display, Error};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use super::SharedData;
|
||||||
|
|
||||||
|
#[derive(Debug, Display, Error)]
|
||||||
|
pub enum UserError {
|
||||||
|
// #[display(fmt = "The page could not be found.")]
|
||||||
|
NotFound,
|
||||||
|
#[display(fmt = "Internal error. Try again later.")]
|
||||||
|
InternalError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for UserError {
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HttpResponse::build(self.status_code())
|
||||||
|
.insert_header(ContentType::html())
|
||||||
|
.body(self.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
match *self {
|
||||||
|
UserError::NotFound => StatusCode::NOT_FOUND,
|
||||||
|
UserError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generic_error<B>(
|
||||||
|
res: ServiceResponse<B>,
|
||||||
|
) -> actix_web::Result<ErrorHandlerResponse<BoxBody>> {
|
||||||
|
let data = res
|
||||||
|
.request()
|
||||||
|
.app_data::<web::Data<SharedData>>()
|
||||||
|
.map(|t| t.get_ref());
|
||||||
|
|
||||||
|
let status_code = res.response().status();
|
||||||
|
let message = if let Some(error) = status_code.canonical_reason() {
|
||||||
|
error
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = match data {
|
||||||
|
Some(SharedData {
|
||||||
|
handlebars,
|
||||||
|
config: _,
|
||||||
|
}) => {
|
||||||
|
let body = handlebars
|
||||||
|
.render(
|
||||||
|
"error",
|
||||||
|
&json!({
|
||||||
|
"message": message,
|
||||||
|
"status_code": status_code.as_u16()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.map_err(|_| UserError::InternalError)?;
|
||||||
|
|
||||||
|
HttpResponse::build(res.status())
|
||||||
|
.content_type(ContentType::html())
|
||||||
|
.body(body)
|
||||||
|
}
|
||||||
|
None => Err(UserError::InternalError)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
|
||||||
|
res.into_parts().0,
|
||||||
|
response.map_into_left_body(),
|
||||||
|
)))
|
||||||
|
}
|
46
src/main.rs
46
src/main.rs
|
@ -1,10 +1,17 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use actix_files::NamedFile;
|
use actix_files::NamedFile;
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::middleware::ErrorHandlers;
|
||||||
use actix_web::{get, web, App, HttpRequest, HttpResponse, HttpServer, Responder};
|
use actix_web::{get, web, App, HttpRequest, HttpResponse, HttpServer, Responder};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use std::path::PathBuf;
|
|
||||||
|
mod errors;
|
||||||
|
|
||||||
|
use errors::{generic_error, UserError};
|
||||||
|
|
||||||
#[derive(Parser, Debug, Clone)]
|
#[derive(Parser, Debug, Clone)]
|
||||||
struct Config {
|
struct Config {
|
||||||
|
@ -21,16 +28,17 @@ struct Config {
|
||||||
dev_mode: bool,
|
dev_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct SharedData<'a> {
|
struct SharedData<'a> {
|
||||||
handlebars: Handlebars<'a>,
|
handlebars: Handlebars<'a>,
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/{filename:.*.html}")]
|
#[get(r"/{filename:.*\.html}")]
|
||||||
async fn template(
|
async fn template(
|
||||||
shared: web::Data<SharedData<'_>>,
|
shared: web::Data<SharedData<'_>>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
) -> Result<impl Responder, Box<dyn std::error::Error>> {
|
) -> actix_web::Result<impl Responder> {
|
||||||
let path = req
|
let path = req
|
||||||
.match_info()
|
.match_info()
|
||||||
.query("filename")
|
.query("filename")
|
||||||
|
@ -38,22 +46,21 @@ async fn template(
|
||||||
.expect("only paths with this suffix should get here");
|
.expect("only paths with this suffix should get here");
|
||||||
|
|
||||||
if shared.handlebars.has_template(path) {
|
if shared.handlebars.has_template(path) {
|
||||||
let body = shared.handlebars.render(path, &())?;
|
|
||||||
Ok(HttpResponse::Ok().body(body))
|
|
||||||
} else {
|
|
||||||
let body = shared
|
let body = shared
|
||||||
.handlebars
|
.handlebars
|
||||||
.render("404", &())
|
.render(path, &())
|
||||||
.expect("404 template not found");
|
.map_err(|_| UserError::InternalError)?;
|
||||||
Ok(HttpResponse::NotFound().body(body))
|
Ok(HttpResponse::Ok().body(body))
|
||||||
|
} else {
|
||||||
|
Err(UserError::NotFound)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/{filename:.*}")]
|
#[get("/{filename:.*[^/]+}")]
|
||||||
async fn static_file(
|
async fn static_file(
|
||||||
shared: web::Data<SharedData<'_>>,
|
shared: web::Data<SharedData<'_>>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
) -> Result<impl Responder, Box<dyn std::error::Error>> {
|
) -> actix_web::Result<impl Responder> {
|
||||||
let requested = req.match_info().query("filename");
|
let requested = req.match_info().query("filename");
|
||||||
|
|
||||||
match shared
|
match shared
|
||||||
|
@ -67,17 +74,13 @@ async fn static_file(
|
||||||
//
|
//
|
||||||
// i.e., don't serve up /etc/passwd
|
// i.e., don't serve up /etc/passwd
|
||||||
Ok(path) if path.starts_with(&shared.config.template_directory) => {
|
Ok(path) if path.starts_with(&shared.config.template_directory) => {
|
||||||
let file = NamedFile::open_async(path).await?;
|
let file = NamedFile::open_async(path)
|
||||||
|
.await
|
||||||
|
.map_err(|_| UserError::NotFound)?;
|
||||||
Ok(file.use_last_modified(false).respond_to(&req))
|
Ok(file.use_last_modified(false).respond_to(&req))
|
||||||
}
|
}
|
||||||
// Any other cases should 404
|
// Any other cases should 404
|
||||||
_ => {
|
_ => Err(UserError::NotFound)?,
|
||||||
let body = shared
|
|
||||||
.handlebars
|
|
||||||
.render("404", &())
|
|
||||||
.expect("404 template not found");
|
|
||||||
Ok(HttpResponse::NotFound().body(body))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +104,11 @@ async fn main() -> Result<(), std::io::Error> {
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
|
.wrap(
|
||||||
|
ErrorHandlers::new()
|
||||||
|
.handler(StatusCode::NOT_FOUND, generic_error)
|
||||||
|
.handler(StatusCode::INTERNAL_SERVER_ERROR, generic_error),
|
||||||
|
)
|
||||||
.app_data(shared_data.clone())
|
.app_data(shared_data.clone())
|
||||||
.service(template)
|
.service(template)
|
||||||
.service(static_file)
|
.service(static_file)
|
||||||
|
|
Loading…
Reference in a new issue