implement custom static loading
This commit is contained in:
parent
9d91e829c8
commit
2eb14be977
7 changed files with 101 additions and 7 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -323,6 +323,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
@ -2343,6 +2344,7 @@ version = "0.1.40"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
|
|
|
@ -56,6 +56,7 @@ tokio = { version = "1.37.0", features = [
|
|||
] }
|
||||
tokio-util = { version = "0.7.10", default-features = false }
|
||||
toml = "0.8.12"
|
||||
tower = "0.4.13"
|
||||
tower-http = { version = "0.5.2", features = [
|
||||
"compression-gzip",
|
||||
"fs",
|
||||
|
|
|
@ -61,8 +61,9 @@ link = "https://..." # public url of the blog, required if rss is enabled
|
|||
[dirs]
|
||||
posts = "posts" # where posts are stored
|
||||
media = "media" # directory served under /media/
|
||||
static = "static" # directory server under /static/ (css and js)
|
||||
|
||||
custom_templates = "custom/templates" # custom templates dir
|
||||
custom_static = "custom/static" # custom static dir
|
||||
# see CUSTOM.md for documentation
|
||||
[http]
|
||||
host = "0.0.0.0" # ip to listen on
|
||||
port = 3000 # port to listen on
|
||||
|
|
|
@ -9,10 +9,12 @@ use axum::response::{IntoResponse, Redirect, Response};
|
|||
use axum::routing::get;
|
||||
use axum::{Json, Router};
|
||||
use handlebars::Handlebars;
|
||||
use include_dir::{include_dir, Dir};
|
||||
use rss::{Category, ChannelBuilder, ItemBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Map;
|
||||
use tokio::sync::RwLock;
|
||||
use tower::service_fn;
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::{info, info_span, Span};
|
||||
|
@ -20,6 +22,9 @@ use tracing::{info, info_span, Span};
|
|||
use crate::config::{Config, DateFormat, Sort};
|
||||
use crate::error::{AppError, AppResult};
|
||||
use crate::post::{MarkdownPosts, PostManager, PostMetadata, RenderStats, ReturnedPost};
|
||||
use crate::serve_dir_included::handle;
|
||||
|
||||
const STATIC: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/static");
|
||||
|
||||
#[derive(Clone)]
|
||||
#[non_exhaustive]
|
||||
|
@ -252,7 +257,9 @@ pub fn new(config: &Config) -> Router<AppState> {
|
|||
.route("/feed.xml", get(rss))
|
||||
.nest_service(
|
||||
"/static",
|
||||
ServeDir::new(&config.dirs._static).precompressed_gzip(),
|
||||
ServeDir::new(&config.dirs.custom_static)
|
||||
.precompressed_gzip()
|
||||
.fallback(service_fn(|req| handle(req, &STATIC))),
|
||||
)
|
||||
.nest_service("/media", ServeDir::new(&config.dirs.media))
|
||||
.layer(
|
||||
|
|
|
@ -49,8 +49,8 @@ pub struct HttpConfig {
|
|||
pub struct DirsConfig {
|
||||
pub posts: PathBuf,
|
||||
pub media: PathBuf,
|
||||
#[serde(rename = "static")]
|
||||
pub _static: PathBuf,
|
||||
pub custom_static: PathBuf,
|
||||
pub custom_templates: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
|
@ -126,7 +126,8 @@ impl Default for DirsConfig {
|
|||
Self {
|
||||
posts: "posts".into(),
|
||||
media: "media".into(),
|
||||
_static: "static".into(),
|
||||
custom_static: "custom/static".into(),
|
||||
custom_templates: "custom/templates".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![feature(let_chains)]
|
||||
#![feature(let_chains, pattern)]
|
||||
|
||||
mod app;
|
||||
mod config;
|
||||
|
@ -9,6 +9,7 @@ mod markdown_render;
|
|||
mod platform;
|
||||
mod post;
|
||||
mod ranged_i128_visitor;
|
||||
mod serve_dir_included;
|
||||
mod systemtime_as_secs;
|
||||
mod templates;
|
||||
|
||||
|
|
81
src/serve_dir_included.rs
Normal file
81
src/serve_dir_included.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use std::convert::Infallible;
|
||||
use std::str::pattern::Pattern;
|
||||
|
||||
use axum::extract::Request;
|
||||
use axum::http::{header, StatusCode};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use include_dir::{Dir, DirEntry};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
fn if_empty<'a>(a: &'a str, b: &'a str) -> &'a str {
|
||||
if a.is_empty() {
|
||||
b
|
||||
} else {
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_prefixes(mut src: &str, pat: (impl Pattern + Copy)) -> &str {
|
||||
while let Some(removed) = src.strip_prefix(pat) {
|
||||
src = removed;
|
||||
}
|
||||
src
|
||||
}
|
||||
|
||||
fn from_included_file(file: &'static include_dir::File<'static>) -> Response {
|
||||
let mime_type = mime_guess::from_path(file.path()).first_or_octet_stream();
|
||||
|
||||
(
|
||||
[(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::try_from(mime_type.essence_str()).expect("invalid mime type"),
|
||||
)],
|
||||
file.contents(),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
pub async fn handle(
|
||||
req: Request,
|
||||
included_dir: &'static Dir<'static>,
|
||||
) -> Result<Response, Infallible> {
|
||||
#[cfg(windows)]
|
||||
compile_error!("this is not safe");
|
||||
|
||||
let path = req.uri().path();
|
||||
|
||||
let has_dotdot = path.split('/').any(|seg| seg == "..");
|
||||
if has_dotdot {
|
||||
return Ok(StatusCode::NOT_FOUND.into_response());
|
||||
}
|
||||
|
||||
let relative_path = if_empty(remove_prefixes(path, '/'), ".");
|
||||
|
||||
match included_dir.get_entry(relative_path) {
|
||||
Some(DirEntry::Dir(dir)) => {
|
||||
trace!("{relative_path:?} is a directory, trying \"index.html\"");
|
||||
if let Some(file) = dir.get_file("index.html") {
|
||||
debug!("{path:?} (index.html) serving from included dir");
|
||||
return Ok(from_included_file(file));
|
||||
} else {
|
||||
trace!("\"index.html\" not found in {relative_path:?} in included files");
|
||||
}
|
||||
}
|
||||
None if relative_path == "." => {
|
||||
trace!("requested root, trying \"index.html\"");
|
||||
if let Some(file) = included_dir.get_file("index.html") {
|
||||
debug!("{path:?} (index.html) serving from included dir");
|
||||
return Ok(from_included_file(file));
|
||||
} else {
|
||||
trace!("\"index.html\" not found in included files");
|
||||
}
|
||||
}
|
||||
Some(DirEntry::File(file)) => {
|
||||
debug!("{path:?} serving from included dir");
|
||||
return Ok(from_included_file(file));
|
||||
}
|
||||
None => trace!("{relative_path:?} not found in included files"),
|
||||
};
|
||||
|
||||
Ok(StatusCode::NOT_FOUND.into_response())
|
||||
}
|
Loading…
Reference in a new issue