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"
|
||||
version = "0.1.0"
|
||||
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
|
||||
|
||||
[dependencies]
|
||||
rocket = "0.5.0-rc.1"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
regex = "1.3.4"
|
||||
rocket_dyn_templates = { version="0.1.0-rc.1", features=["tera"] }
|
||||
serde = {version = "1", features = ["derive"]}
|
||||
serde_json = "1"
|
||||
regex = "1"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
axum = "0.6.1"
|
||||
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)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
mod tests;
|
||||
mod error;
|
||||
mod rst_parser;
|
||||
mod tests;
|
||||
|
||||
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 rocket::Request;
|
||||
use rocket_dyn_templates::Template;
|
||||
use rst_parser::parse_links;
|
||||
use std::collections::HashMap;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::io::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
use serde::Serialize;
|
||||
use std::borrow::Cow;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tera::{Context, Tera};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::trace::TraceLayer;
|
||||
|
||||
type PageResult<T> = std::result::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
|
||||
}
|
||||
}
|
||||
type PageResult<T> = Result<T, JSiteError>;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SiteFile {
|
||||
@ -71,16 +41,15 @@ struct PageData {
|
||||
|
||||
/// Returns the rendered template of the index page of the website. This includes links and rst
|
||||
/// pages included in `static/raw_rst`
|
||||
#[get("/")]
|
||||
fn index() -> Template {
|
||||
let mut map: HashMap<&str, Vec<SiteFile>> = HashMap::new();
|
||||
async fn index(State(state): State<Arc<Tera>>) -> PageResult<impl IntoResponse> {
|
||||
let mut ctx = Context::new();
|
||||
let mut links: Vec<SiteFile> = Vec::new();
|
||||
|
||||
// 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);
|
||||
Template::render("index", &map)
|
||||
ctx.insert("links", &links);
|
||||
Ok(Html(state.render("index.html.tera", &ctx)?))
|
||||
}
|
||||
|
||||
/// Gets all the raw rst pages contained in a directory
|
||||
@ -91,11 +60,11 @@ fn index() -> Template {
|
||||
/// # Arguments
|
||||
/// * `path` - the path to look for pages in
|
||||
/// * `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();
|
||||
|
||||
// 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 path = entry.path();
|
||||
let file_name = match path.file_stem() {
|
||||
@ -142,9 +111,15 @@ fn get_pages(path: &str, pages: &mut Vec<SiteFile>) -> io::Result<()> {
|
||||
/// # Arguments
|
||||
/// * `path` - path to search in
|
||||
/// * `page_name` - file to look for
|
||||
fn get_page(path: &Path) -> PageResult<SiteFile> {
|
||||
let file_name = path.file_name().ok_or(PageNotFoundError)?;
|
||||
let file_name = file_name.to_str().ok_or(PageNotFoundError)?.to_string();
|
||||
fn get_page(path: &std::path::Path) -> PageResult<SiteFile> {
|
||||
let file_name = path
|
||||
.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() {
|
||||
return Ok(SiteFile {
|
||||
rank: 0,
|
||||
@ -171,21 +146,14 @@ fn get_page(path: &Path) -> PageResult<SiteFile> {
|
||||
}
|
||||
}
|
||||
|
||||
Err(JSiteError::from(PageNotFoundError))
|
||||
}
|
||||
|
||||
fn error_page(page: &str) -> Template {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("error_page", page);
|
||||
Template::render("404", map)
|
||||
Err(JSiteError::PageNotFound(path.to_path_buf()))
|
||||
}
|
||||
|
||||
/// Returns a rendered template of a raw rst page if it exists
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `page` - path to page
|
||||
#[get("/about/<page..>")]
|
||||
fn rst_page(page: PathBuf) -> Template {
|
||||
async fn rst_page(tera: State<Arc<Tera>>, Path(page): Path<PathBuf>) -> PageResult<Html<String>> {
|
||||
let mut path = PathBuf::from("static/raw_rst");
|
||||
path.push(page);
|
||||
|
||||
@ -193,17 +161,17 @@ fn rst_page(page: PathBuf) -> Template {
|
||||
let site_page = match get_page(path.as_path()) {
|
||||
Ok(site_page) => site_page,
|
||||
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
|
||||
let mut map = HashMap::new();
|
||||
let mut map = Context::new();
|
||||
let mut sub_files: Vec<SiteFile> = Vec::new();
|
||||
match get_pages(site_page.path.to_str().unwrap(), &mut sub_files) {
|
||||
Ok(_) => (),
|
||||
Err(_) => return error_page(&site_page.link_name),
|
||||
Err(_) => return error_page(&tera, &site_page.link_name).await,
|
||||
}
|
||||
|
||||
let page_data = PageData {
|
||||
@ -211,18 +179,14 @@ fn rst_page(page: PathBuf) -> Template {
|
||||
site_file: site_page,
|
||||
};
|
||||
|
||||
map.insert("page_data", page_data);
|
||||
Template::render("listing", &map)
|
||||
map.insert("page_data", &page_data);
|
||||
tera.render("listing.html.tera", &map)?
|
||||
} else {
|
||||
// Else, render the RST page
|
||||
let mut map = HashMap::new();
|
||||
let contents = match fs::read_to_string(site_page.path) {
|
||||
let mut map = Context::new();
|
||||
let contents = match std::fs::read_to_string(site_page.path.clone()) {
|
||||
Ok(contents) => contents,
|
||||
Err(_) => {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("error_page", site_page.link_name);
|
||||
return Template::render("404", map);
|
||||
}
|
||||
Err(_) => return error_page(&tera, site_page.path.to_str().unwrap()).await,
|
||||
};
|
||||
|
||||
// Render links
|
||||
@ -230,34 +194,72 @@ fn rst_page(page: PathBuf) -> Template {
|
||||
contents = parse_images(contents.as_str()).unwrap();
|
||||
|
||||
// Ensure render will look good
|
||||
contents = contents.replace("\n", "<br>");
|
||||
contents = contents.replace('\n', "<br>");
|
||||
contents = contents.replace(" ", " ");
|
||||
|
||||
map.insert("page", site_page.link_name);
|
||||
map.insert("content", contents);
|
||||
Template::render("rst_page", &map)
|
||||
}
|
||||
map.insert("page", &site_page.link_name);
|
||||
map.insert("content", &contents);
|
||||
tera.render("rst_page.html.tera", &map)?
|
||||
};
|
||||
|
||||
Ok(Html(page))
|
||||
}
|
||||
|
||||
/// Catches 404 errors and displays an error message
|
||||
///
|
||||
/// #Arguments
|
||||
/// * `req` - information on the original request
|
||||
#[catch(404)]
|
||||
fn not_found(req: &Request<'_>) -> Template {
|
||||
let mut map = HashMap::new();
|
||||
async fn error_page(tera: &Tera, page: &str) -> PageResult<Html<String>> {
|
||||
let mut map = Context::new();
|
||||
map.insert("error_page", page);
|
||||
Ok(Html(tera.render("404.html.tera", &map)?))
|
||||
}
|
||||
|
||||
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
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.mount("/", routes![index, rst_page])
|
||||
.mount("/static", rocket::fs::FileServer::from("static"))
|
||||
.attach(Template::fairing())
|
||||
.register("/",catchers![not_found])
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Use globbing
|
||||
let tera = match Tera::new("templates/*.tera") {
|
||||
Ok(t) => Arc::new(t),
|
||||
Err(e) => {
|
||||
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;
|
||||
|
||||
use regex::{Captures, Regex};
|
||||
use std::collections::HashMap;
|
||||
use rst_error::RSTError;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
/// RST Image
|
||||
@ -106,6 +106,8 @@ pub fn parse_images(string: &str) -> Result<String, RSTError> {
|
||||
)?;
|
||||
|
||||
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())
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#[cfg(test)]
|
||||
use super::rst_parser::{parse_links, parse_images};
|
||||
use super::rst_parser::{parse_images, parse_links};
|
||||
|
||||
#[test]
|
||||
fn test_link_parser() {
|
||||
@ -16,8 +16,7 @@ fn test_link_parser() {
|
||||
|
||||
#[test]
|
||||
fn test_image_parser() {
|
||||
let input =
|
||||
".. image:: cool/image/123.png
|
||||
let input = ".. image:: cool/image/123.png
|
||||
:width: 60%
|
||||
:height: auto
|
||||
:alt: this is the alt text
|
||||
@ -26,5 +25,4 @@ fn test_image_parser() {
|
||||
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%;\">")
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends "base" %}
|
||||
{% extends "base.html.tera" %}
|
||||
|
||||
{% block command %}
|
||||
./error.sh
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends "base" %}
|
||||
{% extends "base.html.tera" %}
|
||||
|
||||
{% block command %}
|
||||
./about.sh
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends "base" %}
|
||||
{% extends "base.html.tera" %}
|
||||
|
||||
{% block command %}
|
||||
ls {{ page_data.site_file.link_name }}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends "base" %}
|
||||
{% extends "base.html.tera" %}
|
||||
|
||||
{% block command %}
|
||||
cat {{ page }}.rst
|
||||
|
Loading…
x
Reference in New Issue
Block a user