static serve refactor

This commit is contained in:
Tyler Murphy 2023-02-01 20:34:22 -05:00
parent 028026bfdc
commit 2026a8f457
16 changed files with 192 additions and 173 deletions

View file

@ -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;

View file

@ -33,11 +33,11 @@ function remove(id) {
}
function pfp(id) {
return `<img src="/cdn/avatar?user_id=${id}">`
return `<img src="/image/avatar?user_id=${id}">`
}
function banner(id) {
return `<img src="/cdn/banner?user_id=${id}" onerror="this.remove()" >`
return `<img src="/image/banner?user_id=${id}" onerror="this.remove()" >`
}
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',

View file

@ -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,

View file

@ -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<Query<AvatarRequest>>) -> 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<Query<BannerRequest>>) -> 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))
}

View file

@ -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(

View file

@ -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() {

View file

@ -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 = "

View file

@ -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<B>(req: Request<B>, next: Next<B>) -> 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<B>(mut req: Request<B>, next: Next<B>) -> 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));

69
src/public/file.rs Normal file
View file

@ -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<String>) -> Response {
let path = format!("/js/{}", path);
super::serve(&path).await
}
pub async fn css(Path(path): Path<String>) -> Response {
let path = format!("/css/{}", path);
super::serve(&path).await
}
pub async fn fonts(Path(path): Path<String>) -> Response {
let path = format!("/fonts/{}", path);
super::serve(&path).await
}
pub async fn image(Path(path): Path<String>) -> Response {
let path = format!("/image/{}", path);
super::serve(&path).await
}
#[derive(Deserialize)]
pub struct AvatarRequest {
user_id: u64,
}
pub async fn avatar(params: Option<Query<AvatarRequest>>) -> 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<Query<BannerRequest>>) -> 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
}

47
src/public/mod.rs Normal file
View file

@ -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()
}

View file

@ -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<AuthorizedUser>, _: 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<AuthorizedUser>, _: 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<AuthorizedUser>, _: 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<AuthorizedUser>, _: 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 {

View file

@ -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<Self> {
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::<IpAddr>().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::<IpAddr>().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::<IpAddr>().ok());
let realip = headers
.get("x-real-ip")
.and_then(|hv| hv.to_str().ok())
.and_then(|s| s.parse::<IpAddr>().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::<IpAddr>().ok());
let realip = headers
.get("x-real-ip")
.and_then(|hv| hv.to_str().ok())
.and_then(|s| s.parse::<IpAddr>().ok());
if let Some(realip) = realip {
return Ok(RequestIp(realip))
return Ok(RequestIp(realip));
}
let info = parts.extensions.get::<ConnectInfo<SocketAddr>>();
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<B>, state: &S) -> Result<Self> {
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<BoxError>,
S: Send + Sync,
{
let Ok(RequestIp(ip)) = req.extract_parts::<RequestIp>().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())
}

View file

@ -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<T> = std::result::Result<T, Response>;

View file

@ -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};