mirror of
https://github.com/YGGverse/nexy.git
synced 2026-03-31 09:15:28 +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]
|
||||
name = "nexy"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
|
|
@ -11,5 +11,6 @@ repository = "https://github.com/YGGverse/nexy"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
chrono = "^0.4.20"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
urlencoding = "2.1"
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -4,7 +4,12 @@
|
|||
[](https://deps.rs/repo/github/yggverse/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
|
||||
|
||||
|
|
@ -22,6 +27,9 @@ nexy -p /path/to/public_dir
|
|||
### Options
|
||||
|
||||
``` bash
|
||||
-a, --access-log <ACCESS_LOG>
|
||||
Absolute path to the access log file
|
||||
|
||||
-b, --bind <BIND>
|
||||
Bind server(s) `host:port` to listen incoming connections
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ const PORT: u16 = 1900;
|
|||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
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
|
||||
///
|
||||
/// * use `[host]:port` notation for IPv6
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ impl Connection {
|
|||
}
|
||||
|
||||
pub fn handle(mut self) {
|
||||
let mut t = 0; // total bytes
|
||||
match self.request() {
|
||||
Ok(q) => {
|
||||
self.session.debug.info(&format!(
|
||||
|
|
@ -41,12 +42,16 @@ impl Connection {
|
|||
self.session
|
||||
.clone()
|
||||
.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()
|
||||
}
|
||||
|
|
@ -57,7 +62,7 @@ impl Connection {
|
|||
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 {
|
||||
Response::File(b) => b,
|
||||
Response::Directory(ref s, is_root) => {
|
||||
|
|
@ -97,7 +102,8 @@ impl Connection {
|
|||
"[{}] ! [{}] failed to response: `{e}`",
|
||||
self.address.server, self.address.client,
|
||||
)),
|
||||
}
|
||||
};
|
||||
bytes.len()
|
||||
}
|
||||
|
||||
fn shutdown(self) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
mod debug;
|
||||
mod log;
|
||||
mod storage;
|
||||
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 debug: Debug,
|
||||
pub log: Log,
|
||||
pub storage: Storage,
|
||||
pub template: Template,
|
||||
}
|
||||
|
|
@ -15,6 +17,7 @@ impl Session {
|
|||
pub fn init(config: &crate::config::Config) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
debug: Debug::init(config)?,
|
||||
log: Log::init(config)?,
|
||||
storage: Storage::init(config)?,
|
||||
template: Template::init(config)?,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -25,9 +25,6 @@ impl Debug {
|
|||
}
|
||||
}
|
||||
|
||||
fn now() -> u128 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis()
|
||||
fn now() -> String {
|
||||
chrono::Local::now().to_rfc3339()
|
||||
}
|
||||
|
|
|
|||
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