From 2026a8f4579b1db0f6e5e7b11ac33c13969adb6c Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Wed, 1 Feb 2023 20:34:22 -0500 Subject: [PATCH] static serve refactor --- public/css/home.css | 2 +- public/js/main.js | 4 +-- src/api/admin.rs | 3 +- src/api/image.rs | 54 ---------------------------- src/api/mod.rs | 19 +++++----- src/api/users.rs | 8 +++-- src/database/users.rs | 2 +- src/main.rs | 39 +++++++------------- src/{ => public}/admin.rs | 0 src/{ => public}/console.rs | 0 src/public/file.rs | 69 ++++++++++++++++++++++++++++++++++++ src/public/mod.rs | 47 ++++++++++++++++++++++++ src/{api => public}/pages.rs | 30 +++++----------- src/types/extract.rs | 63 ++++++++++++++++---------------- src/types/http.rs | 23 +----------- src/types/user.rs | 2 +- 16 files changed, 192 insertions(+), 173 deletions(-) delete mode 100644 src/api/image.rs rename src/{ => public}/admin.rs (100%) rename src/{ => public}/console.rs (100%) create mode 100644 src/public/file.rs create mode 100644 src/public/mod.rs rename src/{api => public}/pages.rs (59%) diff --git a/public/css/home.css b/public/css/home.css index 60dc3a5..f467aba 100644 --- a/public/css/home.css +++ b/public/css/home.css @@ -80,7 +80,7 @@ body { } .icons { - background-image: url('/images/icons.png'); + background-image: url('/image/icons.png'); display: inline-block; width: 18px; height: 18px; diff --git a/public/js/main.js b/public/js/main.js index 5737173..ffbc1f3 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -33,11 +33,11 @@ function remove(id) { } function pfp(id) { - return `` + return `` } function banner(id) { - return `` + return `` } const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', diff --git a/src/api/admin.rs b/src/api/admin.rs index 7de5fc0..a23d20f 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -5,7 +5,8 @@ use serde::Deserialize; use tower_cookies::{Cookie, Cookies}; use crate::{ - admin, database, + database, + public::admin, types::{ extract::{AdminUser, Check, CheckResult, Json}, http::ResponseCode, diff --git a/src/api/image.rs b/src/api/image.rs deleted file mode 100644 index 84eccc7..0000000 --- a/src/api/image.rs +++ /dev/null @@ -1,54 +0,0 @@ -use axum::{extract::Query, response::Response, routing::get, Router, http::StatusCode}; -use serde::Deserialize; - -use crate::types::http::ResponseCode; - - - -#[derive(Deserialize)] -struct AvatarRequest { - user_id: u64, -} - -async fn avatar(params: Option>) -> Response { - - let Some(params) = params else { - return ResponseCode::BadRequest.text("Missing query paramaters"); - }; - - let custom = format!("/image/custom/avatar/{}.png", params.user_id); - let default = format!("/image/default/{}.png", params.user_id % 25); - - let file = ResponseCode::Success.file(&custom).await; - if file.status() != StatusCode::OK { - return ResponseCode::Success.file(&default).await - } - file -} - -#[derive(Deserialize)] -struct BannerRequest { - user_id: u64, -} - -async fn banner(params: Option>) -> Response { - - let Some(params) = params else { - return ResponseCode::BadRequest.text("Missing query paramaters"); - }; - - let custom = format!("/image/custom/banner/{}.png", params.user_id); - - let file = ResponseCode::Success.file(&custom).await; - if file.status() != StatusCode::OK { - return ResponseCode::NotFound.text("User does not have a custom banner") - } - file -} - - -pub fn router() -> Router { - Router::new() - .route("/avatar", get(avatar)) - .route("/banner", get(banner)) -} diff --git a/src/api/mod.rs b/src/api/mod.rs index adc19d7..9efcefc 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,14 +1,17 @@ use crate::types::extract::RouterURI; -use axum::{Extension, Router, BoxError, error_handling::HandleErrorLayer}; +use axum::{error_handling::HandleErrorLayer, BoxError, Extension, Router}; use tower::ServiceBuilder; -use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer, errors::display_error, key_extractor::SmartIpKeyExtractor}; +use tower_governor::{ + errors::display_error, governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor, + GovernorLayer, +}; -pub mod admin; -pub mod auth; -pub mod pages; -pub mod posts; -pub mod users; -pub mod image; +mod admin; +mod auth; +mod posts; +mod users; + +pub use auth::RegistrationRequet; pub fn router() -> Router { let governor_conf = Box::new( diff --git a/src/api/users.rs b/src/api/users.rs index 83a0d4e..e3c992b 100644 --- a/src/api/users.rs +++ b/src/api/users.rs @@ -3,7 +3,11 @@ use crate::types::{ http::ResponseCode, user::User, }; -use axum::{response::Response, routing::{post, put}, Router}; +use axum::{ + response::Response, + routing::{post, put}, + Router, +}; use serde::Deserialize; #[derive(Deserialize)] @@ -64,7 +68,6 @@ async fn load_self(AuthorizedUser(user): AuthorizedUser) -> Response { } async fn avatar(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response { - let path = format!("./public/image/custom/avatar/{}.png", user.user_id); if img.save(path).is_err() { @@ -75,7 +78,6 @@ async fn avatar(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response } async fn banner(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response { - let path = format!("./public/image/custom/banner/{}.png", user.user_id); if img.save(path).is_err() { diff --git a/src/database/users.rs b/src/database/users.rs index 27c3b7f..8045bc4 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -2,7 +2,7 @@ use rusqlite::{OptionalExtension, Row}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tracing::instrument; -use crate::{api::auth::RegistrationRequet, database, types::user::User}; +use crate::{api::RegistrationRequet, database, types::user::User}; pub fn init() -> Result<(), rusqlite::Error> { let sql = " diff --git a/src/main.rs b/src/main.rs index e8f17b0..8a8e9d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,38 +1,25 @@ use axum::{ body::HttpBody, - http::{Request, StatusCode}, + extract::DefaultBodyLimit, + http::Request, middleware::{self, Next}, response::Response, - RequestExt, Router, extract::DefaultBodyLimit, + RequestExt, Router, }; -use std::{net::SocketAddr, process::exit, fs}; +use public::console; +use std::{fs, net::SocketAddr, process::exit}; use tower_cookies::CookieManagerLayer; use tracing::{error, info, metadata::LevelFilter}; use tracing_subscriber::{ filter::filter_fn, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer, }; -use types::{http::ResponseCode, extract::RequestIp}; +use types::extract::RequestIp; -use crate::api::{pages, image}; - -mod admin; mod api; -mod console; mod database; +mod public; mod types; -async fn serve(req: Request, next: Next) -> Response -where - B: Send + Sync + 'static + HttpBody, -{ - let uri = req.uri(); - let file = ResponseCode::Success.file(&uri.to_string()).await; - if file.status() != StatusCode::OK { - return next.run(req).await; - } - file -} - async fn log(mut req: Request, next: Next) -> Response where B: Send + Sync + 'static + HttpBody, @@ -47,7 +34,7 @@ where } async fn not_found() -> Response { - ResponseCode::NotFound.file("/404.html").await + public::serve("/404.html").await } #[tokio::main] @@ -69,16 +56,16 @@ async fn main() { }; fs::create_dir_all("./public/image/custom").expect("Coudn't make custom data directory"); - fs::create_dir_all("./public/image/custom/avatar").expect("Coudn't make custom avatar directory"); - fs::create_dir_all("./public/image/custom/banner").expect("Coudn't make custom banner directory"); + fs::create_dir_all("./public/image/custom/avatar") + .expect("Coudn't make custom avatar directory"); + fs::create_dir_all("./public/image/custom/banner") + .expect("Coudn't make custom banner directory"); let app = Router::new() .fallback(not_found) .layer(middleware::from_fn(log)) - .layer(middleware::from_fn(serve)) - .nest("/", pages::router()) + .nest("/", public::router()) .nest("/api", api::router()) - .nest("/cdn", image::router()) .layer(CookieManagerLayer::new()) .layer(DefaultBodyLimit::max(512_000)); diff --git a/src/admin.rs b/src/public/admin.rs similarity index 100% rename from src/admin.rs rename to src/public/admin.rs diff --git a/src/console.rs b/src/public/console.rs similarity index 100% rename from src/console.rs rename to src/public/console.rs diff --git a/src/public/file.rs b/src/public/file.rs new file mode 100644 index 0000000..b54ef25 --- /dev/null +++ b/src/public/file.rs @@ -0,0 +1,69 @@ +use axum::{ + extract::{Path, Query}, + http::StatusCode, + response::Response, +}; +use serde::Deserialize; + +use crate::types::http::ResponseCode; + +use super::console; + +pub async fn js(Path(path): Path) -> Response { + let path = format!("/js/{}", path); + super::serve(&path).await +} + +pub async fn css(Path(path): Path) -> Response { + let path = format!("/css/{}", path); + super::serve(&path).await +} + +pub async fn fonts(Path(path): Path) -> Response { + let path = format!("/fonts/{}", path); + super::serve(&path).await +} + +pub async fn image(Path(path): Path) -> Response { + let path = format!("/image/{}", path); + super::serve(&path).await +} + +#[derive(Deserialize)] +pub struct AvatarRequest { + user_id: u64, +} + +pub async fn avatar(params: Option>) -> Response { + let Some(params) = params else { + return ResponseCode::BadRequest.text("Missing query paramaters"); + }; + + let custom = format!("/image/custom/avatar/{}.png", params.user_id); + let default = format!("/image/default/{}.png", params.user_id % 25); + + let file = super::serve(&custom).await; + if file.status() != StatusCode::OK { + return super::serve(&default).await; + } + file +} + +#[derive(Deserialize)] +pub struct BannerRequest { + user_id: u64, +} + +pub async fn banner(params: Option>) -> Response { + let Some(params) = params else { + return ResponseCode::BadRequest.text("Missing query paramaters"); + }; + + let custom = format!("/image/custom/banner/{}.png", params.user_id); + + let file = super::serve(&custom).await; + if file.status() != StatusCode::OK { + return ResponseCode::NotFound.text("User does not have a custom banner"); + } + file +} diff --git a/src/public/mod.rs b/src/public/mod.rs new file mode 100644 index 0000000..cf8156d --- /dev/null +++ b/src/public/mod.rs @@ -0,0 +1,47 @@ +use axum::{ + body::Body, + http::{Request, StatusCode}, + response::{IntoResponse, Response}, + routing::get, + Router, +}; +use tower::ServiceExt; +use tower_http::services::ServeFile; + +use crate::types::http::ResponseCode; + +pub mod admin; +pub mod console; +pub mod file; +pub mod pages; + +pub fn router() -> Router { + Router::new() + .nest("/", pages::router()) + .route("/js/*path", get(file::js)) + .route("/css/*path", get(file::css)) + .route("/fonts/*path", get(file::fonts)) + .route("/image/*path", get(file::image)) + .route("/image/avatar", get(file::avatar)) + .route("/image/banner", get(file::banner)) +} + +pub async fn serve(path: &str) -> Response { + if !path.chars().any(|c| c == '.') { + return ResponseCode::BadRequest.text("Invalid file path"); + } + + let path = format!("public{path}"); + let file = ServeFile::new(path); + + let Ok(res) = file.oneshot(Request::new(Body::empty())).await else { + tracing::error!("Error while fetching file"); + return ResponseCode::InternalServerError.text("Error while fetching file"); + }; + + if res.status() != StatusCode::OK { + return ResponseCode::NotFound.text("File not found"); + } + + res.into_response() +} diff --git a/src/api/pages.rs b/src/public/pages.rs similarity index 59% rename from src/api/pages.rs rename to src/public/pages.rs index 4ed2e49..1614d81 100644 --- a/src/api/pages.rs +++ b/src/public/pages.rs @@ -5,7 +5,7 @@ use axum::{ }; use crate::{ - console, + public::console, types::{ extract::{AuthorizedUser, Log}, http::ResponseCode, @@ -24,32 +24,20 @@ async fn login(user: Option, _: Log) -> Response { if user.is_some() { Redirect::to("/home").into_response() } else { - ResponseCode::Success.file("/login.html").await + super::serve("/login.html").await } } -async fn home(user: Option, _: Log) -> Response { - if user.is_none() { - Redirect::to("/login").into_response() - } else { - ResponseCode::Success.file("/home.html").await - } +async fn home(_: Log) -> Response { + super::serve("/home.html").await } -async fn people(user: Option, _: Log) -> Response { - if user.is_none() { - Redirect::to("/login").into_response() - } else { - ResponseCode::Success.file("/people.html").await - } +async fn people(_: Log) -> Response { + super::serve("/people.html").await } -async fn profile(user: Option, _: Log) -> Response { - if user.is_none() { - Redirect::to("/login").into_response() - } else { - ResponseCode::Success.file("/profile.html").await - } +async fn profile(_: Log) -> Response { + super::serve("/profile.html").await } async fn console() -> Response { @@ -57,7 +45,7 @@ async fn console() -> Response { } async fn admin() -> Response { - ResponseCode::Success.file("/admin.html").await + super::serve("/admin.html").await } async fn wordpress(_: Log) -> Response { diff --git a/src/types/extract.rs b/src/types/extract.rs index 4d7ac51..1258ef9 100644 --- a/src/types/extract.rs +++ b/src/types/extract.rs @@ -1,20 +1,24 @@ -use std::{io::{Read, Cursor}, net::{IpAddr, SocketAddr}}; +use std::{ + io::{Cursor, Read}, + net::{IpAddr, SocketAddr}, +}; use axum::{ async_trait, body::HttpBody, - extract::{FromRequest, FromRequestParts, ConnectInfo}, + extract::{ConnectInfo, FromRequest, FromRequestParts}, http::{request::Parts, Request}, response::Response, BoxError, RequestExt, }; use bytes::Bytes; -use image::{io::Reader, ImageFormat, DynamicImage}; +use image::{io::Reader, DynamicImage, ImageFormat}; use serde::de::DeserializeOwned; use tower_cookies::Cookies; use crate::{ - admin, console, + public::admin, + public::console, types::{ http::{ResponseCode, Result}, session::Session, @@ -32,41 +36,43 @@ where type Rejection = Response; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { - let headers = &parts.headers; - let forwardedfor = headers.get("x-forwarded-for") - .and_then(|h| h.to_str().ok()) - .and_then(|h| - h.split(',') - .rev() - .find_map(|s| s.trim().parse::().ok()) - ); + let forwardedfor = headers + .get("x-forwarded-for") + .and_then(|h| h.to_str().ok()) + .and_then(|h| { + h.split(',') + .rev() + .find_map(|s| s.trim().parse::().ok()) + }); if let Some(forwardedfor) = forwardedfor { - return Ok(RequestIp(forwardedfor)) + return Ok(RequestIp(forwardedfor)); } - let realip = headers.get("x-real-ip") - .and_then(|hv| hv.to_str().ok()) - .and_then(|s| s.parse::().ok()); + let realip = headers + .get("x-real-ip") + .and_then(|hv| hv.to_str().ok()) + .and_then(|s| s.parse::().ok()); if let Some(realip) = realip { - return Ok(RequestIp(realip)) + return Ok(RequestIp(realip)); } - let realip = headers.get("x-real-ip") - .and_then(|hv| hv.to_str().ok()) - .and_then(|s| s.parse::().ok()); + let realip = headers + .get("x-real-ip") + .and_then(|hv| hv.to_str().ok()) + .and_then(|s| s.parse::().ok()); if let Some(realip) = realip { - return Ok(RequestIp(realip)) + return Ok(RequestIp(realip)); } let info = parts.extensions.get::>(); if let Some(info) = info { - return Ok(RequestIp(info.0.ip())) + return Ok(RequestIp(info.0.ip())); } Err(ResponseCode::Forbidden.text("You have no ip")) @@ -163,7 +169,6 @@ where type Rejection = Response; async fn from_request(req: Request, state: &S) -> Result { - let bytes = match read_body(req, state).await { Ok(body) => body, Err(err) => return Err(err), @@ -171,7 +176,7 @@ where let mut reader = Reader::new(Cursor::new(bytes)); reader.set_format(ImageFormat::Png); - + let Ok(img) = reader.decode() else { return Err(ResponseCode::BadRequest.text("Failed to decode png image")) }; @@ -238,7 +243,6 @@ where B::Error: Into, S: Send + Sync, { - let Ok(RequestIp(ip)) = req.extract_parts::().await else { tracing::error!("Failed to read client ip"); return Err(ResponseCode::InternalServerError.text("Failed to read client ip")); @@ -255,14 +259,7 @@ where return Err(ResponseCode::BadRequest.text("Request can be at most 512kb")); }; - console::log( - ip, - method, - uri, - Some(path.to_string()), - None, - ) - .await; + console::log(ip, method, uri, Some(path.to_string()), None).await; Ok(bytes.bytes().flatten().collect()) } diff --git a/src/types/http.rs b/src/types/http.rs index 8524b15..06674ca 100644 --- a/src/types/http.rs +++ b/src/types/http.rs @@ -1,11 +1,8 @@ use axum::{ - body::Body, headers::HeaderName, - http::{HeaderValue, Request, StatusCode}, + http::{HeaderValue, StatusCode}, response::{IntoResponse, Response}, }; -use tower::ServiceExt; -use tower_http::services::ServeFile; use tracing::instrument; #[derive(Debug)] @@ -58,24 +55,6 @@ impl ResponseCode { ); res } - - #[instrument()] - pub async fn file(self, path: &str) -> Response { - if !path.chars().any(|c| c == '.') { - return Self::BadRequest.text("Folders cannot be served"); - } - let path = format!("public{path}"); - let svc = ServeFile::new(path); - let Ok(mut res) = svc.oneshot(Request::new(Body::empty())).await else { - tracing::error!("Error while fetching file"); - return Self::InternalServerError.text("Error while fetching file"); - }; - if res.status() != StatusCode::OK { - return Self::NotFound.text("File not found"); - } - *res.status_mut() = self.code(); - res.into_response() - } } pub type Result = std::result::Result; diff --git a/src/types/user.rs b/src/types/user.rs index 2bffa52..835b675 100644 --- a/src/types/user.rs +++ b/src/types/user.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use tracing::instrument; -use crate::api::auth::RegistrationRequet; +use crate::api::RegistrationRequet; use crate::database; use crate::types::http::{ResponseCode, Result};