reorganize template processor, implement new server options, optimize component init arguments

This commit is contained in:
yggverse 2025-06-25 00:42:22 +03:00
parent 60f702b3d1
commit b93f389304
9 changed files with 126 additions and 61 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "nexy" name = "nexy"
version = "0.1.0" version = "0.2.0"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"

View file

@ -36,12 +36,28 @@ nexy -p /path/to/public_dir
[default: ei] [default: ei]
-t, --template <TEMPLATE>
Absolute path to the template files directory
-p, --public <PUBLIC> -p, --public <PUBLIC>
Absolute path to the public files directory Absolute path to the public files directory
--template-access-denied <TEMPLATE_ACCESS_DENIED>
Absolute path to the `Access denied` template file
--template-internal-server-error <TEMPLATE_INTERNAL_SERVER_ERROR>
Absolute path to the `Internal server error` template file
--template-not-found <TEMPLATE_NOT_FOUND>
Absolute path to the `Not found` template file
--template-welcome <TEMPLATE_WELCOME>
Absolute path to the `Welcome` template file. Unlike `template_index`, this applies only to the `public` location
**Patterns** * `{list}` - entries list for the `public` directory
--template-index <TEMPLATE_INDEX>
Absolute path to the `Index` template file for each directory
**Patterns** * `{list}` - entries list for the current directory
-r, --read-chunk <READ_CHUNK> -r, --read-chunk <READ_CHUNK>
Optimize memory usage on reading large files or stream Optimize memory usage on reading large files or stream

View file

@ -23,14 +23,37 @@ pub struct Config {
#[arg(short, long, default_value_t = String::from("ei"))] #[arg(short, long, default_value_t = String::from("ei"))]
pub debug: String, pub debug: String,
/// Absolute path to the template files directory
#[arg(short, long)]
pub template: Option<String>,
/// Absolute path to the public files directory /// Absolute path to the public files directory
#[arg(short, long)] #[arg(short, long)]
pub public: String, pub public: String,
/// Absolute path to the `Access denied` template file
#[arg(long)]
pub template_access_denied: Option<String>,
/// Absolute path to the `Internal server error` template file
#[arg(long)]
pub template_internal_server_error: Option<String>,
/// Absolute path to the `Not found` template file
#[arg(long)]
pub template_not_found: Option<String>,
/// Absolute path to the `Welcome` template file.
/// Unlike `template_index`, this applies only to the `public` location
///
/// **Patterns**
/// * `{list}` - entries list for the `public` directory
#[arg(long)]
pub template_welcome: Option<String>,
/// Absolute path to the `Index` template file for each directory
///
/// **Patterns**
/// * `{list}` - entries list for the current directory
#[arg(long)]
pub template_index: Option<String>,
/// Optimize memory usage on reading large files or stream /// Optimize memory usage on reading large files or stream
#[arg(short, long, default_value_t = 1024)] #[arg(short, long, default_value_t = 1024)]
pub read_chunk: usize, pub read_chunk: usize,

View file

@ -7,5 +7,7 @@ pub enum Response<'a> {
/// Includes reference to the original request /// Includes reference to the original request
NotFound(&'a str), NotFound(&'a str),
/// Includes bytes array /// Includes bytes array
Success(&'a [u8]), File(&'a [u8]),
/// Includes bytes array + public root directory status
Directory(String, bool),
} }

View file

@ -59,24 +59,31 @@ impl Connection {
fn response(&mut self, response: Response) { fn response(&mut self, response: Response) {
let bytes = match response { let bytes = match response {
Response::Success(b) => b, Response::File(b) => b,
Response::Directory(b, is_root) => {
&if is_root {
self.session.template.welcome(Some(&b))
} else {
self.session.template.index(Some(&b))
}
}
Response::InternalServerError(e) => { Response::InternalServerError(e) => {
self.session.debug.error(&e); self.session.debug.error(&e);
self.session.template.internal_server_error.as_bytes() self.session.template.internal_server_error()
} }
Response::AccessDenied(q) => { Response::AccessDenied(q) => {
self.session.debug.error(&format!( self.session.debug.error(&format!(
"[{}] < [{}] access to `{q}` denied.", "[{}] < [{}] access to `{q}` denied.",
self.address.server, self.address.client self.address.server, self.address.client
)); ));
self.session.template.access_denied.as_bytes() self.session.template.access_denied()
} }
Response::NotFound(q) => { Response::NotFound(q) => {
self.session.debug.error(&format!( self.session.debug.error(&format!(
"[{}] < [{}] requested resource `{q}` not found.", "[{}] < [{}] requested resource `{q}` not found.",
self.address.server, self.address.client self.address.server, self.address.client
)); ));
self.session.template.not_found.as_bytes() self.session.template.not_found()
} }
}; };
match self.stream.write_all(bytes) { match self.stream.write_all(bytes) {

View file

@ -14,9 +14,9 @@ pub struct Session {
impl Session { impl Session {
pub fn init(config: &crate::config::Config) -> anyhow::Result<Self> { pub fn init(config: &crate::config::Config) -> anyhow::Result<Self> {
Ok(Self { Ok(Self {
debug: Debug::init(&config.debug)?, debug: Debug::init(config)?,
storage: Storage::init(&config.public, config.read_chunk)?, storage: Storage::init(config)?,
template: Template::init(&config.template)?, template: Template::init(config)?,
}) })
} }
} }

View file

@ -4,9 +4,9 @@ use level::Level;
pub struct Debug(Vec<Level>); pub struct Debug(Vec<Level>);
impl Debug { impl Debug {
pub fn init(levels: &str) -> anyhow::Result<Self> { pub fn init(config: &crate::config::Config) -> anyhow::Result<Self> {
let mut l = Vec::with_capacity(levels.len()); let mut l = Vec::with_capacity(config.debug.len());
for s in levels.to_lowercase().chars() { for s in config.debug.to_lowercase().chars() {
l.push(Level::parse(s)?); l.push(Level::parse(s)?);
} }
Ok(Self(l)) Ok(Self(l))

View file

@ -8,8 +8,8 @@ pub struct Storage {
} }
impl Storage { impl Storage {
pub fn init(path: &str, read_chunk: usize) -> Result<Self> { pub fn init(config: &crate::config::Config) -> Result<Self> {
let public_dir = PathBuf::from_str(path)?.canonicalize()?; let public_dir = PathBuf::from_str(&config.public)?.canonicalize()?;
let t = fs::metadata(&public_dir)?; let t = fs::metadata(&public_dir)?;
if !t.is_dir() { if !t.is_dir() {
bail!("Storage destination is not directory!"); bail!("Storage destination is not directory!");
@ -19,7 +19,7 @@ impl Storage {
} }
Ok(Self { Ok(Self {
public_dir, public_dir,
read_chunk, read_chunk: config.read_chunk,
}) })
} }
@ -41,7 +41,7 @@ impl Storage {
match fs::metadata(&p) { match fs::metadata(&p) {
Ok(t) => match (t.is_dir(), t.is_file()) { Ok(t) => match (t.is_dir(), t.is_file()) {
(true, _) => callback(match self.list(&p) { (true, _) => callback(match self.list(&p) {
Ok(ref l) => Response::Success(l.as_bytes()), Ok(l) => Response::Directory(l, p == self.public_dir),
Err(e) => Response::InternalServerError(e.to_string()), Err(e) => Response::InternalServerError(e.to_string()),
}), }),
(_, true) => match fs::File::open(p) { (_, true) => match fs::File::open(p) {
@ -49,7 +49,7 @@ impl Storage {
let mut b = vec![0; self.read_chunk]; let mut b = vec![0; self.read_chunk];
match f.read(&mut b) { match f.read(&mut b) {
Ok(0) => break, Ok(0) => break,
Ok(n) => callback(Response::Success(&b[..n])), Ok(n) => callback(Response::File(&b[..n])),
Err(e) => { Err(e) => {
return callback(Response::InternalServerError(format!( return callback(Response::InternalServerError(format!(
"failed to read response chunk for `{query}`: `{e}`" "failed to read response chunk for `{query}`: `{e}`"

View file

@ -1,46 +1,63 @@
use anyhow::{Result, bail};
pub struct Template { pub struct Template {
pub access_denied: String, access_denied: Vec<u8>,
pub internal_server_error: String, index: Vec<u8>,
pub not_found: String, internal_server_error: Vec<u8>,
not_found: Vec<u8>,
welcome: Vec<u8>,
} }
impl Template { impl Template {
pub fn init(directory: &Option<String>) -> Result<Self> { pub fn init(config: &crate::config::Config) -> anyhow::Result<Self> {
use std::{fs::read_to_string, path::PathBuf}; use std::fs::read;
Ok(Self {
const ACCESS_DENIED: (&str, &str) = ("access_denied.txt", "Access denied"); access_denied: match config.template_access_denied {
const INTERNAL_SERVER_ERROR: (&str, &str) = Some(ref p) => read(p)?,
("internal_server_error.txt", "Internal server error"); None => "Access denied".into(),
const NOT_FOUND: (&str, &str) = ("not_found.txt", "Not found");
fn path(directory: &str, file: &str) -> Result<PathBuf> {
let mut p = PathBuf::from(directory).canonicalize()?;
p.push(file);
if !p.exists() {
bail!("Template path `{}` does not exist", p.to_string_lossy())
}
if !p.is_file() {
bail!("Template path `{}` is not file", p.to_string_lossy())
}
if p.is_symlink() {
bail!("Symlinks yet not supported!");
}
Ok(p)
}
Ok(match directory {
Some(d) => Self {
access_denied: read_to_string(path(d, ACCESS_DENIED.0)?)?,
internal_server_error: read_to_string(path(d, INTERNAL_SERVER_ERROR.0)?)?,
not_found: read_to_string(path(d, NOT_FOUND.0)?)?,
}, },
None => Self { index: match config.template_access_denied {
access_denied: ACCESS_DENIED.1.to_string(), Some(ref p) => read(p)?,
internal_server_error: INTERNAL_SERVER_ERROR.1.to_string(), None => "{list}".into(),
not_found: NOT_FOUND.1.to_string(), },
internal_server_error: match config.template_access_denied {
Some(ref p) => read(p)?,
None => "Internal server error".into(),
},
not_found: match config.template_access_denied {
Some(ref p) => read(p)?,
None => "Not found".into(),
},
welcome: match config.template_access_denied {
Some(ref p) => read(p)?,
None => "Welcome to Nexy!\n{list}".into(),
}, },
}) })
} }
pub fn access_denied(&self) -> &[u8] {
&self.access_denied
}
pub fn index(&self, list: Option<&str>) -> Vec<u8> {
let l = list.unwrap_or_default();
match std::str::from_utf8(&self.index) {
Ok(s) => s.replace("{list}", l).into(),
Err(_) => l.into(),
}
}
pub fn internal_server_error(&self) -> &[u8] {
&self.internal_server_error
}
pub fn not_found(&self) -> &[u8] {
&self.not_found
}
pub fn welcome(&self, list: Option<&str>) -> Vec<u8> {
let l = list.unwrap_or_default();
match std::str::from_utf8(&self.welcome) {
Ok(s) => s.replace("{list}", l).into(),
Err(_) => l.into(),
}
}
} }