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]
name = "nexy"
version = "0.1.0"
version = "0.2.0"
edition = "2024"
license = "MIT"
readme = "README.md"

View file

@ -36,12 +36,28 @@ nexy -p /path/to/public_dir
[default: ei]
-t, --template <TEMPLATE>
Absolute path to the template files directory
-p, --public <PUBLIC>
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>
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"))]
pub debug: String,
/// Absolute path to the template files directory
#[arg(short, long)]
pub template: Option<String>,
/// Absolute path to the public files directory
#[arg(short, long)]
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
#[arg(short, long, default_value_t = 1024)]
pub read_chunk: usize,

View file

@ -7,5 +7,7 @@ pub enum Response<'a> {
/// Includes reference to the original request
NotFound(&'a str),
/// 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) {
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) => {
self.session.debug.error(&e);
self.session.template.internal_server_error.as_bytes()
self.session.template.internal_server_error()
}
Response::AccessDenied(q) => {
self.session.debug.error(&format!(
"[{}] < [{}] access to `{q}` denied.",
self.address.server, self.address.client
));
self.session.template.access_denied.as_bytes()
self.session.template.access_denied()
}
Response::NotFound(q) => {
self.session.debug.error(&format!(
"[{}] < [{}] requested resource `{q}` not found.",
self.address.server, self.address.client
));
self.session.template.not_found.as_bytes()
self.session.template.not_found()
}
};
match self.stream.write_all(bytes) {

View file

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

View file

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

View file

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

View file

@ -1,46 +1,63 @@
use anyhow::{Result, bail};
pub struct Template {
pub access_denied: String,
pub internal_server_error: String,
pub not_found: String,
access_denied: Vec<u8>,
index: Vec<u8>,
internal_server_error: Vec<u8>,
not_found: Vec<u8>,
welcome: Vec<u8>,
}
impl Template {
pub fn init(directory: &Option<String>) -> Result<Self> {
use std::{fs::read_to_string, path::PathBuf};
const ACCESS_DENIED: (&str, &str) = ("access_denied.txt", "Access denied");
const INTERNAL_SERVER_ERROR: (&str, &str) =
("internal_server_error.txt", "Internal server error");
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)?)?,
pub fn init(config: &crate::config::Config) -> anyhow::Result<Self> {
use std::fs::read;
Ok(Self {
access_denied: match config.template_access_denied {
Some(ref p) => read(p)?,
None => "Access denied".into(),
},
None => Self {
access_denied: ACCESS_DENIED.1.to_string(),
internal_server_error: INTERNAL_SERVER_ERROR.1.to_string(),
not_found: NOT_FOUND.1.to_string(),
index: match config.template_access_denied {
Some(ref p) => read(p)?,
None => "{list}".into(),
},
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(),
}
}
}