mirror of
https://github.com/YGGverse/nexy.git
synced 2026-03-31 17:25:27 +00:00
implement CLF option
This commit is contained in:
parent
50a0a567a8
commit
ad9bb1ae49
7 changed files with 72 additions and 16 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "nexy"
|
name = "nexy"
|
||||||
version = "0.2.1"
|
version = "0.3.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
@ -11,5 +11,6 @@ repository = "https://github.com/YGGverse/nexy"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
chrono = "^0.4.20"
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
urlencoding = "2.1"
|
urlencoding = "2.1"
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -4,7 +4,12 @@
|
||||||
[](https://deps.rs/repo/github/yggverse/nexy)
|
[](https://deps.rs/repo/github/yggverse/nexy)
|
||||||
[](https://crates.io/crates/nexy)
|
[](https://crates.io/crates/nexy)
|
||||||
|
|
||||||
Run server accessible to Internet IPv4/IPv6, [Yggdrasil](https://yggdrasil-network.github.io/), [Mycelium](https://github.com/threefoldtech/mycelium), and other networks simultaneously, as many as desired. Optimized for streaming large files (in chunks) without memory overload on reading.
|
## Features
|
||||||
|
|
||||||
|
* Run server accessible to Internet IPv4/IPv6, [Yggdrasil](https://yggdrasil-network.github.io/), [Mycelium](https://github.com/threefoldtech/mycelium), and other networks simultaneously, as many as desired
|
||||||
|
* Optimized for streaming large files (in chunks) without memory overload on buffering the data
|
||||||
|
* Supports the [CLF](https://en.wikipedia.org/wiki/Common_Log_Format) access log, which is compatible with analytics tools such as [GoAccess](https://goaccess.io/), [GoatCounter](https://www.goatcounter.com/) or just [htcount](https://github.com/yggverse/htcount)
|
||||||
|
* See the [Options](#options) section for a complete list of other features.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
|
|
@ -22,6 +27,9 @@ nexy -p /path/to/public_dir
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
|
-a, --access-log <ACCESS_LOG>
|
||||||
|
Absolute path to the access log file
|
||||||
|
|
||||||
-b, --bind <BIND>
|
-b, --bind <BIND>
|
||||||
Bind server(s) `host:port` to listen incoming connections
|
Bind server(s) `host:port` to listen incoming connections
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ const PORT: u16 = 1900;
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
/// Absolute path to the access log file
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub access_log: Option<String>,
|
||||||
|
|
||||||
/// Bind server(s) `host:port` to listen incoming connections
|
/// Bind server(s) `host:port` to listen incoming connections
|
||||||
///
|
///
|
||||||
/// * use `[host]:port` notation for IPv6
|
/// * use `[host]:port` notation for IPv6
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ impl Connection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle(mut self) {
|
pub fn handle(mut self) {
|
||||||
|
let mut t = 0; // total bytes
|
||||||
match self.request() {
|
match self.request() {
|
||||||
Ok(q) => {
|
Ok(q) => {
|
||||||
self.session.debug.info(&format!(
|
self.session.debug.info(&format!(
|
||||||
|
|
@ -41,12 +42,16 @@ impl Connection {
|
||||||
self.session
|
self.session
|
||||||
.clone()
|
.clone()
|
||||||
.storage
|
.storage
|
||||||
.request(&q, |r| self.response(r))
|
.request(&q, |r| t += self.response(r)); // chunk loop
|
||||||
|
self.session.log.clf(&self.address.client, Some(&q), 0, t);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
t += self.response(Response::InternalServerError(format!(
|
||||||
|
"[{}] < [{}] failed to handle incoming request: `{e}`",
|
||||||
|
self.address.server, self.address.client
|
||||||
|
)));
|
||||||
|
self.session.log.clf(&self.address.client, None, 1, t);
|
||||||
}
|
}
|
||||||
Err(e) => self.response(Response::InternalServerError(format!(
|
|
||||||
"[{}] < [{}] failed to handle incoming request: `{e}`",
|
|
||||||
self.address.server, self.address.client
|
|
||||||
))),
|
|
||||||
}
|
}
|
||||||
self.shutdown()
|
self.shutdown()
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +62,7 @@ impl Connection {
|
||||||
Ok(urlencoding::decode(std::str::from_utf8(&b[..n])?.trim())?.to_string())
|
Ok(urlencoding::decode(std::str::from_utf8(&b[..n])?.trim())?.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response(&mut self, response: Response) {
|
fn response(&mut self, response: Response) -> usize {
|
||||||
let bytes = match response {
|
let bytes = match response {
|
||||||
Response::File(b) => b,
|
Response::File(b) => b,
|
||||||
Response::Directory(ref s, is_root) => {
|
Response::Directory(ref s, is_root) => {
|
||||||
|
|
@ -97,7 +102,8 @@ impl Connection {
|
||||||
"[{}] ! [{}] failed to response: `{e}`",
|
"[{}] ! [{}] failed to response: `{e}`",
|
||||||
self.address.server, self.address.client,
|
self.address.server, self.address.client,
|
||||||
)),
|
)),
|
||||||
}
|
};
|
||||||
|
bytes.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shutdown(self) {
|
fn shutdown(self) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
mod debug;
|
mod debug;
|
||||||
|
mod log;
|
||||||
mod storage;
|
mod storage;
|
||||||
mod template;
|
mod template;
|
||||||
|
|
||||||
use {debug::Debug, storage::Storage, template::Template};
|
use {debug::Debug, log::Log, storage::Storage, template::Template};
|
||||||
|
|
||||||
/// Single container for the current session
|
/// Shared, multi-thread features for the current server session
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
pub debug: Debug,
|
pub debug: Debug,
|
||||||
|
pub log: Log,
|
||||||
pub storage: Storage,
|
pub storage: Storage,
|
||||||
pub template: Template,
|
pub template: Template,
|
||||||
}
|
}
|
||||||
|
|
@ -15,6 +17,7 @@ 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::init(config)?,
|
||||||
|
log: Log::init(config)?,
|
||||||
storage: Storage::init(config)?,
|
storage: Storage::init(config)?,
|
||||||
template: Template::init(config)?,
|
template: Template::init(config)?,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,6 @@ impl Debug {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn now() -> u128 {
|
fn now() -> String {
|
||||||
std::time::SystemTime::now()
|
chrono::Local::now().to_rfc3339()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_millis()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
37
src/session/log.rs
Normal file
37
src/session/log.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
//! Standard access logs feature
|
||||||
|
//! that is compatible with analytics tools such as [GoAccess](https://goaccess.io/),
|
||||||
|
//! [GoatCounter](https://www.goatcounter.com/) or [htcount](https://github.com/yggverse/htcount)
|
||||||
|
|
||||||
|
use std::{fs::File, io::Write, net::SocketAddr, sync::RwLock};
|
||||||
|
|
||||||
|
/// Writes log as
|
||||||
|
pub struct Log(Option<RwLock<File>>);
|
||||||
|
|
||||||
|
impl Log {
|
||||||
|
pub fn init(config: &crate::config::Config) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self(match config.access_log {
|
||||||
|
Some(ref p) => Some(RwLock::new(File::create(p)?)),
|
||||||
|
None => None,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/// [CLF](https://en.wikipedia.org/wiki/Common_Log_Format)
|
||||||
|
///
|
||||||
|
/// * the code value (`u8`) is relative, use 1|0 for failure / success
|
||||||
|
pub fn clf(&self, client: &SocketAddr, query: Option<&str>, code: u8, size: usize) {
|
||||||
|
if let Some(ref f) = self.0 {
|
||||||
|
f.write()
|
||||||
|
.unwrap()
|
||||||
|
.write_all(
|
||||||
|
format!(
|
||||||
|
"{} {} - [{}] \"GET {}\" {code} {size}\n",
|
||||||
|
client.ip(),
|
||||||
|
client.port(),
|
||||||
|
chrono::Local::now().format("%d/%b/%Y:%H:%M:%S %z"),
|
||||||
|
query.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue