implement connections count with template macro, add --show-hidden option, fix configuration variable names

This commit is contained in:
yggverse 2025-06-28 15:03:40 +03:00
parent 8cdd7ea8ae
commit c484a59579
8 changed files with 119 additions and 19 deletions

View file

@ -51,6 +51,11 @@ nexy -p /path/to/public_dir
-p, --public <PUBLIC> -p, --public <PUBLIC>
Absolute path to the public files directory Absolute path to the public files directory
--show-hidden
Show hidden entries (in the directory listing)
* Important: this option does not prevent access to hidden files!
--template-access-denied <TEMPLATE_ACCESS_DENIED> --template-access-denied <TEMPLATE_ACCESS_DENIED>
Absolute path to the `Access denied` template file Absolute path to the `Access denied` template file
@ -71,7 +76,7 @@ nexy -p /path/to/public_dir
* this template file expects pattern and cannot be in binary format * this template file expects pattern and cannot be in binary format
**Patterns** * `{list}` - entries list for the `public` directory **Patterns** * `{list}` - entries list for the `public` directory * `{hosts}` - unique visitors count * `{hits}` - requests count
--template-index <TEMPLATE_INDEX> --template-index <TEMPLATE_INDEX>
Absolute path to the `Index` template file for each directory Absolute path to the `Index` template file for each directory

View file

@ -31,6 +31,12 @@ pub struct Config {
#[arg(short, long)] #[arg(short, long)]
pub public: String, pub public: String,
/// Show hidden entries (in the directory listing)
///
/// * Important: this option does not prevent access to hidden files!
#[arg(long, default_value_t = false)]
pub show_hidden: bool,
/// Absolute path to the `Access denied` template file /// Absolute path to the `Access denied` template file
/// ///
/// * this template file can be in binary format (e.g. image) /// * this template file can be in binary format (e.g. image)
@ -56,6 +62,8 @@ pub struct Config {
/// ///
/// **Patterns** /// **Patterns**
/// * `{list}` - entries list for the `public` directory /// * `{list}` - entries list for the `public` directory
/// * `{hosts}` - unique visitors count
/// * `{hits}` - requests count
#[arg(long)] #[arg(long)]
pub template_welcome: Option<String>, pub template_welcome: Option<String>,

View file

@ -32,6 +32,7 @@ impl Connection {
} }
pub fn handle(mut self) { pub fn handle(mut self) {
self.session.event.connection.update(&self.address.client);
let mut t = 0; // total bytes let mut t = 0; // total bytes
match self.request() { match self.request() {
Ok(q) => { Ok(q) => {
@ -71,7 +72,11 @@ impl Connection {
Response::File(b) => b, Response::File(b) => b,
Response::Directory(ref s, is_root) => { Response::Directory(ref s, is_root) => {
&if is_root { &if is_root {
self.session.template.welcome(Some(s)) self.session.template.welcome(
Some(s),
Some(self.session.event.connection.hosts()),
Some(self.session.event.connection.hits()),
)
} else { } else {
self.session.template.index(Some(s)) self.session.template.index(Some(s))
} }

View file

@ -1,25 +1,32 @@
mod access_log; mod access_log;
mod debug; mod debug;
mod event;
mod storage; mod storage;
mod template; mod template;
use {access_log::AccessLog, debug::Debug, storage::Storage, template::Template}; use {access_log::AccessLog, debug::Debug, event::Event, storage::Storage, template::Template};
/// Shared, multi-thread features for the current server session /// Shared, multi-thread features for the current server session
pub struct Session { pub struct Session {
pub debug: Debug,
pub access_log: AccessLog, pub access_log: AccessLog,
pub debug: Debug,
pub event: Event,
pub storage: Storage, pub storage: Storage,
pub template: Template, pub template: Template,
} }
impl Session { impl Session {
pub fn init(config: &crate::config::Config) -> anyhow::Result<Self> { pub fn init(config: &crate::config::Config) -> anyhow::Result<Self> {
let template = Template::init(config)?;
Ok(Self { Ok(Self {
debug: Debug::init(config)?,
access_log: AccessLog::init(config)?, access_log: AccessLog::init(config)?,
debug: Debug::init(config)?,
event: Event::init(
// do not init `Connection` event if its features not in use
template.welcome.contains("{hosts}") || template.welcome.contains("{hits}"),
)?,
storage: Storage::init(config)?, storage: Storage::init(config)?,
template: Template::init(config)?, template,
}) })
} }
} }

15
src/session/event.rs Normal file
View file

@ -0,0 +1,15 @@
mod connection;
use connection::Connection;
pub struct Event {
pub connection: Connection,
// another features...
}
impl Event {
pub fn init(is_connection_enabled: bool) -> anyhow::Result<Self> {
Ok(Self {
connection: Connection::init(is_connection_enabled),
})
}
}

View file

@ -0,0 +1,44 @@
use std::{
collections::HashMap,
net::{IpAddr, SocketAddr},
sync::RwLock,
};
/// Count peer connections (for the current server session)
pub struct Connection(Option<RwLock<HashMap<IpAddr, usize>>>);
impl Connection {
pub fn init(is_enabled: bool) -> Self {
if is_enabled {
Self(Some(RwLock::new(HashMap::with_capacity(100))))
} else {
Self(None)
}
}
pub fn update(&self, peer: &SocketAddr) {
if let Some(ref this) = self.0 {
this.write()
.unwrap()
.entry(peer.ip())
.and_modify(|c| *c += 1)
.or_insert(1);
}
}
pub fn hosts(&self) -> usize {
if let Some(ref this) = self.0 {
this.read().unwrap().len()
} else {
0
}
}
pub fn hits(&self) -> usize {
if let Some(ref this) = self.0 {
this.read().unwrap().values().sum()
} else {
0
}
}
}

View file

@ -18,6 +18,10 @@ pub struct Storage {
public_dir: PathBuf, public_dir: PathBuf,
/// Streaming buffer options /// Streaming buffer options
read_chunk: usize, read_chunk: usize,
/// Show hidden entries (in the directory listing)
///
/// * important: this option does not prevent access to hidden files!
show_hidden: bool,
} }
impl Storage { impl Storage {
@ -34,6 +38,7 @@ impl Storage {
list_config: ListConfig::init(config)?, list_config: ListConfig::init(config)?,
public_dir, public_dir,
read_chunk: config.read_chunk, read_chunk: config.read_chunk,
show_hidden: config.show_hidden,
}) })
} }
@ -124,18 +129,19 @@ impl Storage {
let mut files = Vec::with_capacity(C); let mut files = Vec::with_capacity(C);
for entry in fs::read_dir(path)? { for entry in fs::read_dir(path)? {
let e = entry?; let e = entry?;
let name = e.file_name().to_string_lossy().to_string();
if !self.show_hidden && name.starts_with('.') {
continue;
}
let meta = fs::metadata(e.path())?; let meta = fs::metadata(e.path())?;
match (meta.is_dir(), meta.is_file()) { match (meta.is_dir(), meta.is_file()) {
(true, _) => dirs.push(Dir { (true, _) => dirs.push(Dir {
meta, meta,
name: e.file_name().to_string_lossy().to_string(), name,
count: fs::read_dir(e.path()).map_or(0, |i| i.count()), count: fs::read_dir(e.path()).map_or(0, |i| i.count()),
}), }),
(_, true) => files.push(File { (_, true) => files.push(File { meta, name }),
meta, _ => continue, // @TODO symlinks support?
name: e.file_name().to_string_lossy().to_string(),
}),
_ => {} // @TODO symlinks support?
} }
} }
// build resulting list // build resulting list

View file

@ -3,7 +3,7 @@ pub struct Template {
index: String, index: String,
internal_server_error: Vec<u8>, internal_server_error: Vec<u8>,
not_found: Vec<u8>, not_found: Vec<u8>,
welcome: String, pub welcome: String,
} }
impl Template { impl Template {
@ -14,21 +14,21 @@ impl Template {
Some(ref p) => read(p)?, Some(ref p) => read(p)?,
None => "Access denied".into(), None => "Access denied".into(),
}, },
index: match config.template_access_denied { index: match config.template_index {
Some(ref p) => read_to_string(p)?, Some(ref p) => read_to_string(p)?,
None => "{list}".into(), None => "{list}".into(),
}, },
internal_server_error: match config.template_access_denied { internal_server_error: match config.template_internal_server_error {
Some(ref p) => read(p)?, Some(ref p) => read(p)?,
None => "Internal server error".into(), None => "Internal server error".into(),
}, },
not_found: match config.template_access_denied { not_found: match config.template_not_found {
Some(ref p) => read(p)?, Some(ref p) => read(p)?,
None => "Not found".into(), None => "Not found".into(),
}, },
welcome: match config.template_access_denied { welcome: match config.template_welcome {
Some(ref p) => read_to_string(p)?, Some(ref p) => read_to_string(p)?,
None => "Welcome to Nexy!\n\n{list}".into(), None => "Welcome to Nexy!\n\n{list}\n\n👁 {hosts} / {hits}".into(),
}, },
}) })
} }
@ -51,9 +51,19 @@ impl Template {
&self.not_found &self.not_found
} }
pub fn welcome(&self, list: Option<&str>) -> Vec<u8> { pub fn welcome(
&self,
list: Option<&str>,
hosts: Option<usize>,
hits: Option<usize>,
) -> Vec<u8> {
fn format_count(v: Option<usize>) -> String {
v.map_or(String::new(), |c| c.to_string())
}
self.welcome self.welcome
.replace("{list}", list.unwrap_or_default()) .replace("{list}", list.unwrap_or_default())
.replace("{hosts}", &format_count(hosts))
.replace("{hits}", &format_count(hits))
.into() .into()
} }
} }