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:
parent
36f78e2678
commit
33a6056445
1626
Cargo.lock
generated
1626
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
23
Cargo.toml
23
Cargo.toml
@ -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
46
src/error.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
210
src/main.rs
210
src/main.rs
@ -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(" ", " ");
|
contents = contents.replace(" ", " ");
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -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%;\">")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends "base" %}
|
{% extends "base.html.tera" %}
|
||||||
|
|
||||||
{% block command %}
|
{% block command %}
|
||||||
./error.sh
|
./error.sh
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends "base" %}
|
{% extends "base.html.tera" %}
|
||||||
|
|
||||||
{% block command %}
|
{% block command %}
|
||||||
./about.sh
|
./about.sh
|
||||||
|
@ -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 }}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends "base" %}
|
{% extends "base.html.tera" %}
|
||||||
|
|
||||||
{% block command %}
|
{% block command %}
|
||||||
cat {{ page }}.rst
|
cat {{ page }}.rst
|
||||||
|
Loading…
x
Reference in New Issue
Block a user