diff options
author | gennyble <gen@nyble.dev> | 2025-03-22 16:11:20 -0500 |
---|---|---|
committer | gennyble <gen@nyble.dev> | 2025-03-22 16:11:20 -0500 |
commit | 1df2ee6bc406b87d96548da3a21e671490e82513 (patch) | |
tree | cf1431a38b5e61019d63308ec444fcc5df6829cd | |
parent | 62d3ce6cca2d6be9f8fb77cc7e56a25217070d87 (diff) | |
download | corgi-1df2ee6bc406b87d96548da3a21e671490e82513.tar.gz corgi-1df2ee6bc406b87d96548da3a21e671490e82513.zip |
stats!
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Cargo.lock | 290 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | README.md | 1 | ||||
-rw-r--r-- | corgi/Cargo.toml | 3 | ||||
-rw-r--r-- | corgi/src/main.rs | 28 | ||||
-rw-r--r-- | corgi/src/stats.rs | 100 | ||||
-rw-r--r-- | stats_module/Cargo.toml | 12 | ||||
-rw-r--r-- | stats_module/src/lib.rs | 118 |
9 files changed, 552 insertions, 4 deletions
diff --git a/.gitignore b/.gitignore index ea8c4bf..cee48f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target +*.sqlite +*.db \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1c55731..6e28733 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,12 +33,42 @@ dependencies = [ ] [[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] +name = "cc" +version = "1.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +dependencies = [ + "shlex", +] + +[[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -54,22 +84,81 @@ checksum = "ea618ded77af626818bde0f0802da7c20d47e38e23e37be40f6f807a76079e82" name = "corgi" version = "1.0.0" dependencies = [ + "base64", "confindent", "http-body-util", "hyper", "hyper-util", "libloading", "regex-lite", + "rusqlite", + "sha2", "tokio", ] [[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -103,12 +192,40 @@ dependencies = [ ] [[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown", +] + +[[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -211,6 +328,17 @@ dependencies = [ ] [[package]] +name = "libsqlite3-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -237,6 +365,12 @@ dependencies = [ ] [[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -266,18 +400,100 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] name = "regex-lite" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" [[package]] +name = "rusqlite" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "time", +] + +[[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -303,6 +519,56 @@ dependencies = [ ] [[package]] +name = "stats_module" +version = "0.1.0" +dependencies = [ + "rusqlite", + "time", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "time" +version = "0.3.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] name = "tokio" version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -319,6 +585,30 @@ dependencies = [ ] [[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index 8feb4b4..c5c5f05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["corgi", "parrot", "parrot_module"] +members = ["corgi", "parrot", "parrot_module", "stats_module"] resolver = "3" # use this profile like this: diff --git a/README.md b/README.md index 3b94588..c312ee6 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ corgi listens on port 26744 by default. ``` Server Port 26744 + StatsDb /var/corgi/stats.db Script <path-to-cgi-script> Match diff --git a/corgi/Cargo.toml b/corgi/Cargo.toml index 0abd9df..ea5fe74 100644 --- a/corgi/Cargo.toml +++ b/corgi/Cargo.toml @@ -10,10 +10,13 @@ version = "1.0.0" edition = "2024" [dependencies] +base64 = "0.22.1" http-body-util = "0.1.3" hyper-util = { version = "0.1.10", features = ["tokio"] } libloading = "0.8.6" regex-lite = "0.1.6" +rusqlite = { version = "0.34.0", features = ["bundled"] } +sha2 = "0.10.8" [dependencies.confindent] version = "2.2.1" diff --git a/corgi/src/main.rs b/corgi/src/main.rs index cd3b67c..6a3c528 100644 --- a/corgi/src/main.rs +++ b/corgi/src/main.rs @@ -1,7 +1,9 @@ use std::{ net::{IpAddr, SocketAddr}, + path::PathBuf, pin::Pin, process::Stdio, + sync::Arc, time::Instant, }; @@ -17,9 +19,11 @@ use hyper::{ }; use hyper_util::rt::TokioIo; use regex_lite::Regex; +use stats::Stats; use tokio::{io::AsyncWriteExt, net::TcpListener, process::Command, runtime::Runtime}; mod caller; +mod stats; #[derive(Clone, Debug)] pub struct Settings { @@ -64,8 +68,13 @@ fn main() { } } + let stats = Stats::new(PathBuf::from( + conf.get("Server/StatsDb").unwrap().to_owned(), + )); + stats.create_tables(); + let rt = Runtime::new().unwrap(); - rt.block_on(async { run(settings).await }); + rt.block_on(async { run(settings, stats).await }); } fn parse_script_conf(conf: &Value) -> Script { @@ -106,12 +115,13 @@ fn parse_script_conf(conf: &Value) -> Script { } // We have tokio::main at home :) -async fn run(settings: Settings) { +async fn run(settings: Settings, stats: Stats) { let addr = SocketAddr::from(([0, 0, 0, 0], settings.port)); let listen = TcpListener::bind(addr).await.unwrap(); let svc = Svc { settings, + stats: Arc::new(stats), client_addr: addr, }; @@ -129,6 +139,7 @@ async fn run(settings: Settings) { #[derive(Clone, Debug)] struct Svc { settings: Settings, + stats: Arc<Stats>, client_addr: SocketAddr, } @@ -140,14 +151,16 @@ impl Service<Request<Incoming>> for Svc { fn call(&self, req: Request<Incoming>) -> Self::Future { let settings = self.settings.clone(); let caddr = self.client_addr; + let stats = self.stats.clone(); - Box::pin(async move { Ok(Self::handle(settings, caddr, req).await) }) + Box::pin(async move { Ok(Self::handle(settings, stats, caddr, req).await) }) } } impl Svc { async fn handle( settings: Settings, + stats: Arc<Stats>, caddr: SocketAddr, req: Request<Incoming>, ) -> Response<Full<Bytes>> { @@ -242,6 +255,13 @@ impl Svc { response = response.header(key, value); } + let db_req = stats::Request { + agent: &uagent, + ip_address: &client_addr, + script: &script.name, + path: &path, + }; + println!( "served to [{client_addr}]\n\tscript: {}\n\tpath: {path}\n\tcgi took {}ms. total time {}ms\n\tUA: {uagent}", &script.name, @@ -249,6 +269,8 @@ impl Svc { start.elapsed().as_millis() ); + stats.log_request(db_req); + let response_body = cgi_response.body.map(|v| Bytes::from(v)).unwrap_or(Bytes::new()); response.body(Full::new(response_body)).unwrap() } diff --git a/corgi/src/stats.rs b/corgi/src/stats.rs new file mode 100644 index 0000000..0e3b99a --- /dev/null +++ b/corgi/src/stats.rs @@ -0,0 +1,100 @@ +use std::{ + net::{IpAddr, SocketAddr}, + path::PathBuf, + sync::Mutex, +}; + +use base64::{Engine, prelude::BASE64_STANDARD_NO_PAD}; +use rusqlite::{Connection, OptionalExtension, params}; +use sha2::{Digest, Sha256}; + +#[derive(Debug)] +pub struct Stats { + conn: Mutex<Connection>, +} + +impl Stats { + pub fn new(db_path: PathBuf) -> Self { + Self { + conn: Mutex::new(Connection::open(db_path).unwrap()), + } + } + + pub fn create_tables(&self) { + let conn = self.conn.lock().unwrap(); + conn.execute(CREATE_TABLE_AGENT, ()).unwrap(); + conn.execute(CREATE_TABLE_REQUESTS, ()).unwrap(); + } + + pub fn log_request(&self, request: Request) { + let Request { + agent, + ip_address, + script, + path, + } = request; + + let mut hasher = Sha256::new(); + hasher.update(agent.as_bytes()); + let hash = hasher.finalize(); + let agent_hash = BASE64_STANDARD_NO_PAD.encode(&hash[..]); + + let conn = self.conn.lock().unwrap(); + + // Try to get the agent ID from the hash + let maybe_agent_id: Option<i64> = conn + .query_row( + "SELECT id, hash FROM agents WHERE hash=?1", + params![&agent_hash], + |row| row.get(0), + ) + .optional() + .unwrap(); + + // Can't find the agent_id? Push a new entry + let agent_id = match maybe_agent_id { + Some(aid) => aid, + None => { + conn.execute( + "INSERT INTO agents(hash, agent) VALUES(?1, ?2)", + params![&agent_hash, agent], + ) + .unwrap(); + + conn.last_insert_rowid() + } + }; + + conn.execute( + "INSERT INTO requests(agent_id, ip_address, script, path) VALUES(?1, ?2, ?3, ?4)", + params![agent_id, ip_address.to_string(), script, path], + ) + .unwrap(); + } +} + +pub struct Request<'r> { + pub agent: &'r str, + pub ip_address: &'r IpAddr, + pub script: &'r str, + pub path: &'r str, +} + +const CREATE_TABLE_AGENT: &'static str = "\ + CREATE TABLE IF NOT EXISTS agents( + id INTEGER PRIMARY KEY AUTOINCREMENT, + hash TEXT NOT NULL, + agent TEXT NOT NULL + );"; + +const CREATE_TABLE_REQUESTS: &'static str = "\ + CREATE TABLE IF NOT EXISTS requests( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + agent_id INTEGER NOT NULL, + ip_address TEXT NOT NULL, + script TEXT NOT NULL, + path TEXT NOT NULL, + FOREIGN KEY (agent_id) + REFERENCES agents(id) + );"; diff --git a/stats_module/Cargo.toml b/stats_module/Cargo.toml new file mode 100644 index 0000000..ea0f965 --- /dev/null +++ b/stats_module/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "stats_module" +version = "0.1.0" +edition = "2024" + +[lib] +name = "stats" +crate-type = ["cdylib"] + +[dependencies] +rusqlite = { version = "0.34.0", features = ["bundled", "time"] } +time = "0.3.40" diff --git a/stats_module/src/lib.rs b/stats_module/src/lib.rs new file mode 100644 index 0000000..18c0e63 --- /dev/null +++ b/stats_module/src/lib.rs @@ -0,0 +1,118 @@ +use std::{ + ffi::{self, CStr, CString}, + sync::Mutex, +}; + +use rusqlite::{Connection, params}; +use time::{Duration, OffsetDateTime}; + +#[repr(C)] +struct ModuleRequest<'req> { + headers_len: ffi::c_ulong, + headers: &'req [[*const ffi::c_char; 2]], + body_len: ffi::c_ulong, + body: *const u8, +} + +#[repr(C)] +struct ModuleResponse { + status: ffi::c_ushort, + headers_len: ffi::c_ulong, + headers: &'static [[*const ffi::c_char; 2]], + body_len: ffi::c_ulong, + body: *const u8, +} + +const HEADERS: &'static [[*const ffi::c_char; 2]] = + &[[c"Content-Type".as_ptr(), c"text/html".as_ptr()]]; + +#[unsafe(no_mangle)] +extern "C" fn cgi_handle(req: *const ModuleRequest) -> *const ModuleResponse { + let mut ret = String::new(); + + // unwrap is bad here + let reqref = unsafe { req.as_ref() }.unwrap(); + + let mut stats_path = None; + for idx in 0..reqref.headers_len { + let kvarr = reqref.headers[idx as usize]; + let k = unsafe { CStr::from_ptr(kvarr[0]) }.to_string_lossy(); + let v = unsafe { CStr::from_ptr(kvarr[1]) }.to_string_lossy(); + + match k.as_ref() { + "CORGI_STATS_DB" => stats_path = Some(v.into_owned()), + _ => (), + } + } + + let db = if let Some(path) = stats_path { + Connection::open(path).unwrap() + } else { + return make_error(500, c"could not open stats database"); + }; + let now = OffsetDateTime::now_utc(); + let fifteen_ago = now - Duration::minutes(15); + + let query = "SELECT count(requests.id) AS request_count, agents.agent FROM requests \ + INNER JOIN agents ON requests.agent_id = agents.id \ + WHERE requests.timestamp > ?1 \ + GROUP BY requests.agent_id;"; + + let mut prepared = db.prepare(query).unwrap(); + let mut agents: Vec<(usize, String)> = prepared + .query_map(params![fifteen_ago], |row| Ok((row.get(0)?, row.get(1)?))) + .unwrap() + .map(|r| r.unwrap()) + .collect(); + + agents.sort_by(|a, b| a.0.cmp(&b.0).reverse()); + + ret.push_str("<p>In the last fifteen minutes:<br/><code><pre>"); + ret.push_str("total | req/m | agent\n"); + for (count, agent) in &agents { + ret.push_str(&format!( + "{count:<5} | {:<5.1} | {agent}\n", + *count as f32 / 15.0 + )); + } + ret.push_str("</pre></code></p>"); + + let body = CString::new(ret).unwrap(); + + let resp = ModuleResponse { + status: 200, + headers_len: 1, + headers: HEADERS, + body_len: body.as_bytes().len() as u64, + body: body.into_raw() as *const u8, + }; + + let boxed = Box::new(resp); + Box::<ModuleResponse>::into_raw(boxed) +} + +fn make_error<S: Into<CString>>(code: u16, msg: S) -> *const ModuleResponse { + let body = msg.into(); + + let resp = ModuleResponse { + status: code, + headers_len: 1, + headers: HEADERS, + body_len: body.as_bytes().len() as u64, + body: body.into_raw() as *const u8, + }; + + let boxed = Box::new(resp); + Box::<ModuleResponse>::into_raw(boxed) +} + +#[unsafe(no_mangle)] +extern "C" fn cgi_cleanup(req: *const ModuleResponse) { + // from_raw what we need to here so that these get dropped + let boxed = unsafe { Box::from_raw(req as *mut ModuleResponse) }; + let body = unsafe { CString::from_raw(boxed.body as *mut i8) }; + + // Explicitly calling drop here to feel good about myself + drop(body); + drop(boxed); +} |