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()) | ||||
| } | ||||
|  | ||||
| @ -25,4 +25,4 @@ impl std::fmt::Display for RSTError { | ||||
|             RSTError::RegexError(e) => write!(f, "regex compile error: `{:?}`", e), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -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