Updated website to use axum

+ Its better than warp and rocket so far...
+ Also switched to 2021 Rust
+ Needs more refactoring but I wanted to get it to a "working" state
+ clippy + fmt
This commit is contained in:
Joey Hines 2022-12-26 17:17:40 -06:00
parent 36f78e2678
commit 33a6056445
11 changed files with 815 additions and 1112 deletions

1626
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,14 +2,23 @@
name = "jsite" name = "jsite"
version = "0.1.0" version = "0.1.0"
authors = ["Joey Hines <joey@ahines.net>"] authors = ["Joey Hines <joey@ahines.net>"]
edition = "2018" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
rocket = "0.5.0-rc.1" serde = {version = "1", features = ["derive"]}
serde = "1.0" serde_json = "1"
serde_derive = "1.0" regex = "1"
serde_json = "1.0" tokio = { version = "1", features = ["full"] }
regex = "1.3.4" axum = "0.6.1"
rocket_dyn_templates = { version="0.1.0-rc.1", features=["tera"] } tera = "1.17.1"
tower = { version = "0.4", features = ["util", "timeout", "load-shed", "limit"] }
tower-http = { version = "0.3.0", features = [
"add-extension",
"compression-full",
"limit",
"trace",
] }
tower-layer = "0.3.2"
axum-extra = { version = "0.4.2", features = ["spa"]}

46
src/error.rs Normal file
View File

@ -0,0 +1,46 @@
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use std::fmt;
use std::fmt::{Display, Formatter};
use std::path::PathBuf;
use tera::Error;
impl IntoResponse for JSiteError {
fn into_response(self) -> Response {
println!("Error occurred on route: {}", self);
(
StatusCode::INTERNAL_SERVER_ERROR,
"Internal error, try again later".to_string(),
)
.into_response()
}
}
#[derive(Debug)]
pub enum JSiteError {
PageNotFound(PathBuf),
IOError(std::io::Error),
TemplateError(tera::Error),
}
impl From<std::io::Error> for JSiteError {
fn from(e: std::io::Error) -> Self {
JSiteError::IOError(e)
}
}
impl From<tera::Error> for JSiteError {
fn from(e: Error) -> Self {
Self::TemplateError(e)
}
}
impl Display for JSiteError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
JSiteError::PageNotFound(e) => write!(f, "Page not found: {}", e.display()),
JSiteError::IOError(e) => write!(f, "IO Error: {}", e),
JSiteError::TemplateError(e) => write!(f, "Template rendering error: {}", e),
}
}
}

View File

@ -1,59 +1,29 @@
#![feature(proc_macro_hygiene, decl_macro)] mod error;
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate serde_derive;
mod tests;
mod rst_parser; mod rst_parser;
mod tests;
use crate::rst_parser::parse_images; use crate::rst_parser::parse_images;
use axum::error_handling::HandleErrorLayer;
use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::response::{Html, IntoResponse};
use axum::routing::get;
use axum::{BoxError, Router};
use axum_extra::routing::SpaRouter;
use error::JSiteError;
use regex::Regex; use regex::Regex;
use rocket::Request;
use rocket_dyn_templates::Template;
use rst_parser::parse_links; use rst_parser::parse_links;
use std::collections::HashMap; use serde::Serialize;
use std::error; use std::borrow::Cow;
use std::fmt; use std::net::SocketAddr;
use std::io::Error; use std::path::PathBuf;
use std::path::{Path, PathBuf}; use std::sync::Arc;
use std::{fs, io}; use std::time::Duration;
use tera::{Context, Tera};
use tower::ServiceBuilder;
use tower_http::trace::TraceLayer;
type PageResult<T> = std::result::Result<T, JSiteError>; type PageResult<T> = Result<T, JSiteError>;
#[derive(Clone, Debug)]
struct PageNotFoundError;
impl fmt::Display for PageNotFoundError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Page not found")
}
}
impl error::Error for PageNotFoundError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
None
}
}
#[derive(Clone, Debug)]
enum JSiteError {
PageNotFound(PageNotFoundError),
IOError,
}
impl std::convert::From<PageNotFoundError> for JSiteError {
fn from(e: PageNotFoundError) -> Self {
JSiteError::PageNotFound(e)
}
}
impl std::convert::From<std::io::Error> for JSiteError {
fn from(_: Error) -> Self {
JSiteError::IOError
}
}
#[derive(Serialize)] #[derive(Serialize)]
struct SiteFile { struct SiteFile {
@ -71,16 +41,15 @@ struct PageData {
/// Returns the rendered template of the index page of the website. This includes links and rst /// Returns the rendered template of the index page of the website. This includes links and rst
/// pages included in `static/raw_rst` /// pages included in `static/raw_rst`
#[get("/")] async fn index(State(state): State<Arc<Tera>>) -> PageResult<impl IntoResponse> {
fn index() -> Template { let mut ctx = Context::new();
let mut map: HashMap<&str, Vec<SiteFile>> = HashMap::new();
let mut links: Vec<SiteFile> = Vec::new(); let mut links: Vec<SiteFile> = Vec::new();
// Get the links to display on the main page // Get the links to display on the main page
get_pages("static/raw_rst", &mut links).ok(); get_pages("static/raw_rst", &mut links)?;
map.insert("links", links); ctx.insert("links", &links);
Template::render("index", &map) Ok(Html(state.render("index.html.tera", &ctx)?))
} }
/// Gets all the raw rst pages contained in a directory /// Gets all the raw rst pages contained in a directory
@ -91,11 +60,11 @@ fn index() -> Template {
/// # Arguments /// # Arguments
/// * `path` - the path to look for pages in /// * `path` - the path to look for pages in
/// * `pages` - A vector where found pages will be inserted /// * `pages` - A vector where found pages will be inserted
fn get_pages(path: &str, pages: &mut Vec<SiteFile>) -> io::Result<()> { fn get_pages(path: &str, pages: &mut Vec<SiteFile>) -> PageResult<()> {
let re = Regex::new(r"(?P<rank>^\d*)(?P<link_name>.+)").unwrap(); let re = Regex::new(r"(?P<rank>^\d*)(?P<link_name>.+)").unwrap();
// Find all files in the directory // Find all files in the directory
for entry in fs::read_dir(path)? { for entry in std::fs::read_dir(path)? {
let entry = entry?; let entry = entry?;
let path = entry.path(); let path = entry.path();
let file_name = match path.file_stem() { let file_name = match path.file_stem() {
@ -142,9 +111,15 @@ fn get_pages(path: &str, pages: &mut Vec<SiteFile>) -> io::Result<()> {
/// # Arguments /// # Arguments
/// * `path` - path to search in /// * `path` - path to search in
/// * `page_name` - file to look for /// * `page_name` - file to look for
fn get_page(path: &Path) -> PageResult<SiteFile> { fn get_page(path: &std::path::Path) -> PageResult<SiteFile> {
let file_name = path.file_name().ok_or(PageNotFoundError)?; let file_name = path
let file_name = file_name.to_str().ok_or(PageNotFoundError)?.to_string(); .file_name()
.ok_or(JSiteError::PageNotFound(path.to_path_buf()))?;
let file_name = file_name
.to_str()
.ok_or(JSiteError::PageNotFound(path.to_path_buf()))?
.to_string();
if path.exists() { if path.exists() {
return Ok(SiteFile { return Ok(SiteFile {
rank: 0, rank: 0,
@ -171,21 +146,14 @@ fn get_page(path: &Path) -> PageResult<SiteFile> {
} }
} }
Err(JSiteError::from(PageNotFoundError)) Err(JSiteError::PageNotFound(path.to_path_buf()))
}
fn error_page(page: &str) -> Template {
let mut map = HashMap::new();
map.insert("error_page", page);
Template::render("404", map)
} }
/// Returns a rendered template of a raw rst page if it exists /// Returns a rendered template of a raw rst page if it exists
/// ///
/// # Arguments /// # Arguments
/// * `page` - path to page /// * `page` - path to page
#[get("/about/<page..>")] async fn rst_page(tera: State<Arc<Tera>>, Path(page): Path<PathBuf>) -> PageResult<Html<String>> {
fn rst_page(page: PathBuf) -> Template {
let mut path = PathBuf::from("static/raw_rst"); let mut path = PathBuf::from("static/raw_rst");
path.push(page); path.push(page);
@ -193,17 +161,17 @@ fn rst_page(page: PathBuf) -> Template {
let site_page = match get_page(path.as_path()) { let site_page = match get_page(path.as_path()) {
Ok(site_page) => site_page, Ok(site_page) => site_page,
Err(_) => { Err(_) => {
return error_page(path.to_str().unwrap()); return error_page(&tera, path.to_str().unwrap()).await;
} }
}; };
if site_page.path.is_dir() { let page = if site_page.path.is_dir() {
// If the file is a directory, list its contents instead // If the file is a directory, list its contents instead
let mut map = HashMap::new(); let mut map = Context::new();
let mut sub_files: Vec<SiteFile> = Vec::new(); let mut sub_files: Vec<SiteFile> = Vec::new();
match get_pages(site_page.path.to_str().unwrap(), &mut sub_files) { match get_pages(site_page.path.to_str().unwrap(), &mut sub_files) {
Ok(_) => (), Ok(_) => (),
Err(_) => return error_page(&site_page.link_name), Err(_) => return error_page(&tera, &site_page.link_name).await,
} }
let page_data = PageData { let page_data = PageData {
@ -211,18 +179,14 @@ fn rst_page(page: PathBuf) -> Template {
site_file: site_page, site_file: site_page,
}; };
map.insert("page_data", page_data); map.insert("page_data", &page_data);
Template::render("listing", &map) tera.render("listing.html.tera", &map)?
} else { } else {
// Else, render the RST page // Else, render the RST page
let mut map = HashMap::new(); let mut map = Context::new();
let contents = match fs::read_to_string(site_page.path) { let contents = match std::fs::read_to_string(site_page.path.clone()) {
Ok(contents) => contents, Ok(contents) => contents,
Err(_) => { Err(_) => return error_page(&tera, site_page.path.to_str().unwrap()).await,
let mut map = HashMap::new();
map.insert("error_page", site_page.link_name);
return Template::render("404", map);
}
}; };
// Render links // Render links
@ -230,34 +194,72 @@ fn rst_page(page: PathBuf) -> Template {
contents = parse_images(contents.as_str()).unwrap(); contents = parse_images(contents.as_str()).unwrap();
// Ensure render will look good // Ensure render will look good
contents = contents.replace("\n", "<br>"); contents = contents.replace('\n', "<br>");
contents = contents.replace(" ", "&nbsp;&nbsp;"); contents = contents.replace(" ", "&nbsp;&nbsp;");
map.insert("page", site_page.link_name); map.insert("page", &site_page.link_name);
map.insert("content", contents); map.insert("content", &contents);
Template::render("rst_page", &map) tera.render("rst_page.html.tera", &map)?
} };
Ok(Html(page))
} }
/// Catches 404 errors and displays an error message async fn error_page(tera: &Tera, page: &str) -> PageResult<Html<String>> {
/// let mut map = Context::new();
/// #Arguments map.insert("error_page", page);
/// * `req` - information on the original request Ok(Html(tera.render("404.html.tera", &map)?))
#[catch(404)] }
fn not_found(req: &Request<'_>) -> Template {
let mut map = HashMap::new();
map.insert("error_page", String::from(req.uri().path().as_str())); async fn handle_error(error: BoxError) -> impl IntoResponse {
if error.is::<tower::timeout::error::Elapsed>() {
return (StatusCode::REQUEST_TIMEOUT, Cow::from("request timed out"));
}
Template::render("404", &map) if error.is::<tower::load_shed::error::Overloaded>() {
return (
StatusCode::SERVICE_UNAVAILABLE,
Cow::from("service is overloaded, try again later"),
);
}
(
StatusCode::INTERNAL_SERVER_ERROR,
Cow::from(format!("Unhandled internal error: {}", error)),
)
} }
/// Launches website /// Launches website
#[launch] #[tokio::main]
fn rocket() -> _ { async fn main() {
rocket::build() // Use globbing
.mount("/", routes![index, rst_page]) let tera = match Tera::new("templates/*.tera") {
.mount("/static", rocket::fs::FileServer::from("static")) Ok(t) => Arc::new(t),
.attach(Template::fairing()) Err(e) => {
.register("/",catchers![not_found]) println!("Parsing error(s): {}", e);
return;
}
};
let app = Router::new()
.route("/", get(index))
.route("/about/*path", get(rst_page))
.merge(SpaRouter::new("/static", "static"))
.layer(
ServiceBuilder::new()
// Handle errors from middleware
.layer(HandleErrorLayer::new(handle_error))
.load_shed()
.concurrency_limit(1024)
.timeout(Duration::from_secs(10))
.layer(TraceLayer::new_for_http()),
)
.with_state(tera);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
} }

View File

@ -1,8 +1,8 @@
pub mod rst_error; pub mod rst_error;
use regex::{Captures, Regex}; use regex::{Captures, Regex};
use std::collections::HashMap;
use rst_error::RSTError; use rst_error::RSTError;
use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
/// RST Image /// RST Image
@ -106,6 +106,8 @@ pub fn parse_images(string: &str) -> Result<String, RSTError> {
)?; )?;
Ok(re_image Ok(re_image
.replace_all(string, |cap: &Captures| RSTImage::try_from(cap).unwrap().to_html()) .replace_all(string, |cap: &Captures| {
RSTImage::try_from(cap).unwrap().to_html()
})
.to_string()) .to_string())
} }

View File

@ -1,5 +1,5 @@
#[cfg(test)] #[cfg(test)]
use super::rst_parser::{parse_links, parse_images}; use super::rst_parser::{parse_images, parse_links};
#[test] #[test]
fn test_link_parser() { fn test_link_parser() {
@ -16,8 +16,7 @@ fn test_link_parser() {
#[test] #[test]
fn test_image_parser() { fn test_image_parser() {
let input = let input = ".. image:: cool/image/123.png
".. image:: cool/image/123.png
:width: 60% :width: 60%
:height: auto :height: auto
:alt: this is the alt text :alt: this is the alt text
@ -26,5 +25,4 @@ fn test_image_parser() {
let output = parse_images(input).unwrap(); let output = parse_images(input).unwrap();
assert_eq!(output.trim_end(), "<img src=\"cool/image/123.png\" alt=\"this is the alt text\" style=\"height;60%;width:60%;\">") assert_eq!(output.trim_end(), "<img src=\"cool/image/123.png\" alt=\"this is the alt text\" style=\"height;60%;width:60%;\">")
} }

View File

@ -1,4 +1,4 @@
{% extends "base" %} {% extends "base.html.tera" %}
{% block command %} {% block command %}
./error.sh ./error.sh

View File

@ -1,4 +1,4 @@
{% extends "base" %} {% extends "base.html.tera" %}
{% block command %} {% block command %}
./about.sh ./about.sh

View File

@ -1,4 +1,4 @@
{% extends "base" %} {% extends "base.html.tera" %}
{% block command %} {% block command %}
ls {{ page_data.site_file.link_name }} ls {{ page_data.site_file.link_name }}

View File

@ -1,4 +1,4 @@
{% extends "base" %} {% extends "base.html.tera" %}
{% block command %} {% block command %}
cat {{ page }}.rst cat {{ page }}.rst