mirror of
https://github.com/lxsang/luafcgi.git
synced 2024-12-26 13:18:22 +01:00
POC of working lua fastcgi
This commit is contained in:
commit
4e122bd245
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
324
Cargo.lock
generated
Normal file
324
Cargo.lock
generated
Normal file
@ -0,0 +1,324 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastcgi"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4159a0f48bea0281602e508eb070d7d7ba1f6ac2480f9db1a60a39274aea1cc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
|
||||
[[package]]
|
||||
name = "lua-src"
|
||||
version = "544.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "708ba3c844d5e9d38def4a09dd871c17c370f519b3c4b7261fbabe4a613a814c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "luad"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"fastcgi",
|
||||
"libc",
|
||||
"mlua",
|
||||
"nix",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "luajit-src"
|
||||
version = "210.4.5+resty2cf5186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27b7992a40e602786272d84c6f2beca44a588ededcfd57b48ec6f82008a7cb97"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mlua"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ee2ad7a9aa69056b148d9d590344bc155d3ce0d2200e3b2838f7034f6ba33c1"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"cc",
|
||||
"lua-src",
|
||||
"luajit-src",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"pkg-config",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"pin-utils",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
24
Cargo.toml
Normal file
24
Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "luad"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
fastcgi = "1.0.0"
|
||||
mlua = { version = "0.8", features = ["lua54", "vendored"] }
|
||||
clap = "2.33"
|
||||
nix = "0.26.1"
|
||||
serde = {version = "1.0", features = ["derive"]}
|
||||
serde_derive = "1.0"
|
||||
toml = "0.5"
|
||||
libc = "0.2"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 's'
|
||||
# 's' for size
|
||||
lto = true
|
||||
# this strategy doesnot work on 1.3.9
|
||||
# panic = 'abort'
|
||||
codegen-units = 1
|
10
config-example.toml
Normal file
10
config-example.toml
Normal file
@ -0,0 +1,10 @@
|
||||
# TCP socket or Unix socket file
|
||||
socket = "unix:/tmp/lua.sock"
|
||||
|
||||
# pid file
|
||||
pidfile = "/tmp/luad.pid"
|
||||
|
||||
# user name
|
||||
# user = "demo"
|
||||
# group name
|
||||
# group = "demo"
|
785
src/lib.rs
Normal file
785
src/lib.rs
Normal file
@ -0,0 +1,785 @@
|
||||
use mlua::prelude::*;
|
||||
use libc;
|
||||
use nix;
|
||||
use std::io::{Error, ErrorKind, Read,Write};
|
||||
use std::ffi::CString;
|
||||
use std::fmt::Arguments;
|
||||
use std::os::fd::RawFd;
|
||||
use std::os::unix::io::{AsRawFd};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// app author
|
||||
pub const APP_AUTHOR: &str = "Dany LE <mrsang@iohub.dev>";
|
||||
|
||||
/// app version
|
||||
pub const APP_VERSION: &str = "0.1.0";
|
||||
|
||||
/// Application name
|
||||
pub const DAEMON_NAME: &str = "luad";
|
||||
|
||||
/// Drop user privileges
|
||||
///
|
||||
/// This function drop the privileges of the current user
|
||||
/// to another inferior privileges user.
|
||||
/// e.g. drop from root->maint
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user` - system user name
|
||||
/// * `group` - system group name
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * `nix::Error` - The error from the nix package
|
||||
pub fn privdrop(useropt: Option<&str>, groupopt: Option<&str>) -> Result<(), nix::Error> {
|
||||
match groupopt{
|
||||
Some(group) => {
|
||||
INFO!("Dropping current process group to {}", group);
|
||||
match nix::unistd::Group::from_name(group)? {
|
||||
Some(group) => nix::unistd::setgid(group.gid),
|
||||
None => Err(nix::Error::last()),
|
||||
}?;
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
match useropt {
|
||||
Some(user) => {
|
||||
INFO!("Dropping current process user to {}", user);
|
||||
match nix::unistd::User::from_name(user)? {
|
||||
Some(user) => nix::unistd::setuid(user.uid),
|
||||
None => Err(nix::Error::last()),
|
||||
}?
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// Utility function to catch common signal that
|
||||
/// cause the program to exit
|
||||
///
|
||||
/// Signals catched: SIGABRT, SIGINT, SIGTERM, SIGQUIT
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `f` - callback function that will be called when a signal is trapped
|
||||
pub fn on_exit(f: fn(n: i32) -> ()) {
|
||||
unsafe {
|
||||
let _ = libc::signal(libc::SIGPIPE, libc::SIG_IGN);
|
||||
let _ = libc::signal(libc::SIGABRT, (f as *const std::ffi::c_void) as usize);
|
||||
let _ = libc::signal(libc::SIGINT, (f as *const std::ffi::c_void) as usize);
|
||||
let _ = libc::signal(libc::SIGTERM, (f as *const std::ffi::c_void) as usize);
|
||||
let _ = libc::signal(libc::SIGQUIT, (f as *const std::ffi::c_void) as usize);
|
||||
};
|
||||
}
|
||||
|
||||
/// Return an Error Result object from error string
|
||||
///
|
||||
#[macro_export]
|
||||
macro_rules! ERR {
|
||||
($x:expr) => {
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("({}:{}): {}", file!(), line!(), $x),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for error log helper
|
||||
///
|
||||
#[macro_export]
|
||||
macro_rules! INFO {
|
||||
($($args:tt)*) => ({
|
||||
let prefix = format!(":info@[{}:{}]: ",file!(), line!());
|
||||
let _ = LOG::log(&prefix[..], &LogLevel::INFO, format_args!($($args)*));
|
||||
})
|
||||
}
|
||||
|
||||
/// Macro for warning log helper
|
||||
///
|
||||
#[macro_export]
|
||||
macro_rules! WARN {
|
||||
($($args:tt)*) => ({
|
||||
let prefix = format!(":warning@[{}:{}]: ",file!(), line!());
|
||||
let _ = LOG::log(&prefix[..], &LogLevel::WARN, format_args!($($args)*));
|
||||
})
|
||||
}
|
||||
|
||||
/// Macro for info log helper
|
||||
///
|
||||
#[macro_export]
|
||||
macro_rules! ERROR {
|
||||
($($args:tt)*) => ({
|
||||
let prefix = format!(":error@[{}:{}]: ",file!(), line!());
|
||||
let _ = LOG::log(&prefix[..], &LogLevel::ERROR, format_args!($($args)*));
|
||||
})
|
||||
}
|
||||
|
||||
/// Different Logging levels for `LOG`
|
||||
pub enum LogLevel {
|
||||
/// Error conditions
|
||||
ERROR,
|
||||
/// Normal, but significant, condition
|
||||
INFO,
|
||||
/// Warning conditions
|
||||
WARN,
|
||||
}
|
||||
|
||||
/// Log struct wrapper
|
||||
///
|
||||
pub struct LOG {}
|
||||
|
||||
impl LOG {
|
||||
/// Init the system log
|
||||
///
|
||||
/// This should be called only once in the entire lifetime
|
||||
/// of the program, the returned LOG instance should
|
||||
/// be keep alive during the lifetime of the program (the main function).
|
||||
/// When it is dropped, the connection to the system log will be
|
||||
/// closed automatically
|
||||
#[must_use]
|
||||
pub fn init_log() -> Self {
|
||||
// connect to the system log
|
||||
unsafe {
|
||||
libc::openlog(
|
||||
std::ptr::null(),
|
||||
libc::LOG_CONS | libc::LOG_PID | libc::LOG_NDELAY,
|
||||
libc::LOG_DAEMON,
|
||||
);
|
||||
}
|
||||
Self {}
|
||||
}
|
||||
|
||||
/// Wrapper function that log error or info message to the
|
||||
/// connected syslog server
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `prefix` - Prefix of the log message
|
||||
/// * `level` - Log level
|
||||
/// * `args` - Arguments object representing a format string and its arguments
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * `std error` - All errors related to formated and C string manipulation
|
||||
pub fn log(prefix: &str, level: &LogLevel, args: Arguments<'_>) -> Result<(), Error> {
|
||||
use std::fmt::Write;
|
||||
let mut output = String::new();
|
||||
if output.write_fmt(args).is_err() {
|
||||
return Err(ERR!("Unable to create format string from arguments"));
|
||||
}
|
||||
let log_fmt = format!("{}(v{}){}%s\n", DAEMON_NAME, APP_VERSION, prefix);
|
||||
let fmt = CString::new(log_fmt.as_bytes())?;
|
||||
let c_msg = CString::new(output.as_bytes())?;
|
||||
let sysloglevel = match level {
|
||||
LogLevel::ERROR => libc::LOG_ERR,
|
||||
LogLevel::WARN => libc::LOG_WARNING,
|
||||
_ => libc::LOG_NOTICE,
|
||||
};
|
||||
unsafe {
|
||||
libc::syslog(sysloglevel, fmt.as_ptr(), c_msg.as_ptr());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LOG {
|
||||
/// The connection to the syslog will be closed
|
||||
/// automatically when the log object is drop
|
||||
fn drop(&mut self) {
|
||||
// Close the current connection to the system logger
|
||||
unsafe {
|
||||
libc::closelog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Protocol goes here
|
||||
#[derive(Debug)]
|
||||
enum FCGIHeaderType{
|
||||
BeginRequest,
|
||||
AbortRequest,
|
||||
EndRequest,
|
||||
Params,
|
||||
Stdin,
|
||||
Stdout,
|
||||
Stderr,
|
||||
Data,
|
||||
GetValues,
|
||||
GetValuesResult,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl FCGIHeaderType {
|
||||
|
||||
/// convert a u8 value to `FCGIHeaderType` value
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - u8 header value
|
||||
fn from_u8(value: u8) -> Self {
|
||||
match value {
|
||||
1 => FCGIHeaderType::BeginRequest,
|
||||
2 => FCGIHeaderType::AbortRequest,
|
||||
3 => FCGIHeaderType::EndRequest,
|
||||
4 => FCGIHeaderType::Params,
|
||||
5 => FCGIHeaderType::Stdin,
|
||||
6 => FCGIHeaderType::Stdout,
|
||||
7 => FCGIHeaderType::Stderr,
|
||||
8 => FCGIHeaderType::Data,
|
||||
9 => FCGIHeaderType::GetValues,
|
||||
10 => FCGIHeaderType::GetValuesResult,
|
||||
_ => FCGIHeaderType::Unknown,
|
||||
}
|
||||
}
|
||||
/// convert an `FCGIHeaderType` value to u8
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - `FCGIHeaderType` header value
|
||||
fn as_u8(&self) -> u8 {
|
||||
match self {
|
||||
FCGIHeaderType::BeginRequest => 1,
|
||||
FCGIHeaderType::AbortRequest => 2,
|
||||
FCGIHeaderType::EndRequest => 3,
|
||||
FCGIHeaderType::Params => 4,
|
||||
FCGIHeaderType::Stdin => 5,
|
||||
FCGIHeaderType::Stdout => 6,
|
||||
FCGIHeaderType::Stderr => 7,
|
||||
FCGIHeaderType::Data => 8,
|
||||
FCGIHeaderType::GetValues => 9,
|
||||
FCGIHeaderType::GetValuesResult => 10,
|
||||
FCGIHeaderType::Unknown => 11,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FCGIHeaderType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
FCGIHeaderType::BeginRequest => "FCGI_BEGIN_REQUEST",
|
||||
FCGIHeaderType::AbortRequest => "FCGI_ABORT_REQUEST",
|
||||
FCGIHeaderType::EndRequest => "FCGI_END_REQUEST",
|
||||
FCGIHeaderType::Params => "FCGI_PARAMS",
|
||||
FCGIHeaderType::Stdin => "FCGI_STDIN",
|
||||
FCGIHeaderType::Stdout => "FCGI_STDOUT",
|
||||
FCGIHeaderType::Stderr => "FCGI_STDERR",
|
||||
FCGIHeaderType::Data => "FCGI_DATA",
|
||||
FCGIHeaderType::GetValues => "FCGI_GET_VALUES",
|
||||
FCGIHeaderType::GetValuesResult => "FCGI_GET_VALUES_RESULT",
|
||||
FCGIHeaderType::Unknown => "FCGI_UNKNOWN_TYPE",
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
enum EndRequestStatus{
|
||||
Complete,
|
||||
// CantMaxMPXConn,
|
||||
// Overloaded,
|
||||
UnknownRole,
|
||||
}
|
||||
|
||||
impl EndRequestStatus {
|
||||
/// convert an `EndRequestStatus` value to u8
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - `EndRequestStatus` header value
|
||||
fn as_u8(&self) -> u8 {
|
||||
match self {
|
||||
EndRequestStatus::Complete => 0,
|
||||
//EndRequestStatus::CantMaxMPXConn => 1,
|
||||
//EndRequestStatus::Overloaded => 2,
|
||||
EndRequestStatus::UnknownRole => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const FCGI_HEADER_LEN:usize = 8;
|
||||
const FCGI_VERSION:u8 = 1;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq)]
|
||||
enum FCGIRole {
|
||||
Responder,
|
||||
Authorizer,
|
||||
Filter,
|
||||
Unknown
|
||||
}
|
||||
|
||||
impl FCGIRole {
|
||||
|
||||
/// convert a u8 value to `FCGIRole` value
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - u16 header value
|
||||
fn from_u16(value: u16) -> Self {
|
||||
match value {
|
||||
1 => FCGIRole::Responder,
|
||||
2 => FCGIRole::Authorizer,
|
||||
3 => FCGIRole::Filter,
|
||||
_ => FCGIRole::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FCGIRole {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
FCGIRole::Responder => "FCGI_RESPONDER",
|
||||
FCGIRole::Authorizer => "FCGI_AUTHORIZER",
|
||||
FCGIRole::Filter => "FCGI_FILTER",
|
||||
FCGIRole::Unknown => "FCGI_UNKNOWN_ROLE",
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct FCGIBeginRequestBody{
|
||||
role: FCGIRole,
|
||||
flags: u8,
|
||||
}
|
||||
|
||||
impl FCGIBeginRequestBody {
|
||||
pub fn from_bytes(data: &[u8]) -> Self
|
||||
{
|
||||
Self {
|
||||
role: FCGIRole::from_u16(((data[0] as u16) << 8) | (data[1] as u16)),
|
||||
flags: data[2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FCGIBeginRequestBody {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "role: {} \n", self.role)?;
|
||||
write!(f, "flags: {} \n", self.flags)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FcgiHeader {
|
||||
version: u8,
|
||||
kind: FCGIHeaderType,
|
||||
id: u16,
|
||||
padding: u8,
|
||||
length:u16,
|
||||
}
|
||||
|
||||
impl FcgiHeader {
|
||||
pub fn from_bytes(data: &[u8]) -> Self
|
||||
{
|
||||
Self {
|
||||
version: data[0],
|
||||
kind: FCGIHeaderType::from_u8(data[1]),
|
||||
id: ((data[2] as u16) << 8) | (data[3] as u16),
|
||||
length: ((data[4] as u16) << 8) | (data[5]as u16),
|
||||
padding: data[6]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> Vec<u8>
|
||||
{
|
||||
vec![
|
||||
self.version,
|
||||
self.kind.as_u8(),
|
||||
(self.id >> 8) as u8,
|
||||
(self.id & 0xFF) as u8,
|
||||
(self.length >> 8) as u8,
|
||||
(self.length & 0xFF) as u8,
|
||||
self.padding,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FcgiHeader {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "Version: {} \n", self.version)?;
|
||||
write!(f, "Kind: {} \n", self.kind)?;
|
||||
write!(f, "ID: {} \n", self.id)?;
|
||||
write!(f, "Data length: {} \n", self.length)?;
|
||||
write!(f, "Padding: {} \n", self.padding)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq)]
|
||||
enum FCGIRequestState {
|
||||
WaitForParams,
|
||||
WaitForStdin,
|
||||
WaitForStdout
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FCGIRequestState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
FCGIRequestState::WaitForParams => "WaitForParams",
|
||||
FCGIRequestState::WaitForStdin => "WaitForStdin",
|
||||
FCGIRequestState::WaitForStdout => "WaitForStdout",
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
struct FGCIRequest {
|
||||
params: HashMap<String, String>,
|
||||
id: u16,
|
||||
fd: RawFd,
|
||||
data: Vec<u8>,
|
||||
state: FCGIRequestState,
|
||||
}
|
||||
|
||||
struct FCGIStdoutStream {
|
||||
fd: RawFd,
|
||||
id: u16
|
||||
}
|
||||
|
||||
impl FCGIStdoutStream {
|
||||
fn _write(&self, buf: &[u8]) -> std::io::Result<usize>
|
||||
{
|
||||
let ret = unsafe { libc::write(self.fd, buf.as_ptr() as *const libc::c_void, buf.len()) };
|
||||
if ret != buf.len() as isize
|
||||
{
|
||||
let msg = format!("Unable to write data to {}: only {} out of {} bytes have been written", self.fd, ret, buf.len());
|
||||
return Err(ERR!(msg));
|
||||
}
|
||||
Ok(ret as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for FCGIStdoutStream
|
||||
{
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self._write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl mlua::UserData for FCGIStdoutStream {
|
||||
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("write", |_, this:& FCGIStdoutStream, strings:mlua::Variadic<String>| {
|
||||
let mut stream = FCGIStdoutStream
|
||||
{
|
||||
fd: this.fd,
|
||||
id: this.id
|
||||
};
|
||||
let mut output = String::new();
|
||||
for string in strings{
|
||||
output.push_str(&string);
|
||||
}
|
||||
fcgi_send_stdout(&mut stream, this.id, Some(output.as_bytes().to_vec()))
|
||||
.map_err(|e| mlua::Error::external(ERR!(e.to_string())))?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn lua_define_print(lua: &Lua, strings: mlua::Variadic<String>) -> LuaResult<()> {
|
||||
let global = lua.globals();
|
||||
let server = global.get::<_, mlua::Table>("_SERVER")?;
|
||||
let fd = server.get::<_,RawFd>("FD")?;
|
||||
let id: u16 = server.get::<_,u16>("ID")?;
|
||||
if fd <= 0 {
|
||||
return Err(mlua::Error::external(ERR!("Invalid file descriptor")));
|
||||
}
|
||||
let mut data = String::new();
|
||||
for string in strings{
|
||||
data.push_str(&string);
|
||||
}
|
||||
// make the request
|
||||
let mut stream = FCGIStdoutStream { fd, id};
|
||||
let body = data.as_bytes().to_vec();
|
||||
fcgi_send_stdout(&mut stream, id, Some(body))
|
||||
.map_err(|e| mlua::Error::external(ERR!(e.to_string())))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//fn lua_define_io_write(lua: &Lua, strings: String) -> LuaResult<()> {
|
||||
//}
|
||||
|
||||
fn fcgi_execute_request_handle(rq: & FGCIRequest) -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
let lua = mlua::Lua::new();
|
||||
let global = lua.globals();
|
||||
let request = lua.create_table()?;
|
||||
request.set("ID", rq.id)?;
|
||||
request.set("FD", rq.fd)?;
|
||||
for (k,v) in &rq.params{
|
||||
request.set(String::from(k),String::from(v))?;
|
||||
}
|
||||
// request params stored in _SERVER table
|
||||
global.set("_SERVER", request)?;
|
||||
// replace the print function
|
||||
let func = lua.create_function(lua_define_print)?;
|
||||
global.set("print", func)?;
|
||||
|
||||
// replace the io.stdout
|
||||
let stdout = FCGIStdoutStream {
|
||||
fd: rq.fd,
|
||||
id: rq.id
|
||||
};
|
||||
let io = global.get::<_, mlua::Table>("io")?;
|
||||
io.set("stdout", stdout)?;
|
||||
let func = lua.create_function(lua_define_print)?;
|
||||
io.set("write",func)?;
|
||||
|
||||
let path = rq.params.get("SCRIPT_FILENAME").ok_or(ERR!("No SCRIPT_FILENAME found"))?;
|
||||
let source = std::fs::read_to_string(path)?;
|
||||
INFO!("source: {}", &source);
|
||||
lua.load(&source).exec()?;
|
||||
Ok(())
|
||||
|
||||
//global.set("hello", func)?;
|
||||
//let source = std::fs::read_to_string(script).unwrap();
|
||||
//lua.load(&source).exec().unwrap();
|
||||
//lua.load("hello('world')").exec()?;
|
||||
}
|
||||
|
||||
fn fcgi_send_stderr<T: Read + Write + AsRawFd >(stream: &mut T,id: u16, eopt: Option<Box<dyn std::error::Error>>) -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
let mut header = FcgiHeader {
|
||||
version: FCGI_VERSION,
|
||||
kind: FCGIHeaderType::Stderr,
|
||||
id: id,
|
||||
length: 0,
|
||||
padding: 0 ,
|
||||
};
|
||||
if let Some(error) = eopt{
|
||||
let err_str = error.to_string();
|
||||
let str_len = err_str.len();
|
||||
let mut padding = (8 - str_len % 8) as u8;
|
||||
if padding == 8
|
||||
{
|
||||
padding = 0;
|
||||
}
|
||||
let mut body = err_str.as_bytes().to_vec();
|
||||
let pad = vec![0;padding as usize];
|
||||
header.length = str_len as u16;
|
||||
header.padding = padding;
|
||||
body.extend(pad);
|
||||
stream.write_all(&header.as_bytes())?;
|
||||
stream.write_all(&body)?;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.write_all(&header.as_bytes())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fcgi_send_stdout<T: Write >(stream: &mut T,id: u16, dopt: Option<Vec<u8>>) -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
let mut header = FcgiHeader {
|
||||
version: FCGI_VERSION,
|
||||
kind: FCGIHeaderType::Stdout,
|
||||
id: id,
|
||||
length: 0,
|
||||
padding: 0,
|
||||
};
|
||||
if let Some(data) = dopt{
|
||||
header.length = data.len() as u16;
|
||||
header.padding = (8 - header.length % 8) as u8;
|
||||
if header.padding == 8
|
||||
{
|
||||
header.padding = 0;
|
||||
}
|
||||
let mut body = data;
|
||||
let pad = vec![0;header.padding as usize];
|
||||
body.extend(pad);
|
||||
stream.write_all(&header.as_bytes())?;
|
||||
stream.write_all(&body)?;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.write_all(&header.as_bytes())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fcgi_send_end_request<T: Read + Write + AsRawFd >(stream: &mut T,id:u16, status: EndRequestStatus) -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
let header = FcgiHeader {
|
||||
version: FCGI_VERSION,
|
||||
kind: FCGIHeaderType::EndRequest,
|
||||
id: id,
|
||||
length: 8,
|
||||
padding: 0 ,
|
||||
};
|
||||
let body = vec![
|
||||
0,0,0,0,
|
||||
status.as_u8(),
|
||||
0,0,0
|
||||
];
|
||||
stream.write_all(&header.as_bytes())?;
|
||||
stream.write_all(&body)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_request<T: Read + Write + AsRawFd >(stream:&mut T)-> Result<(), Box<dyn std::error::Error>>{
|
||||
let mut requests: HashMap<u16, FGCIRequest> = HashMap::new();
|
||||
loop{
|
||||
let header = fcgi_read_header(stream)?;
|
||||
match header.kind {
|
||||
FCGIHeaderType::BeginRequest => {
|
||||
let body = FCGIBeginRequestBody::from_bytes(&fcgi_read_body(stream, &header)?);
|
||||
INFO!("Begin Request: {:?}, with body {:?}", header, body);
|
||||
if body.role != FCGIRole::Responder
|
||||
{
|
||||
fcgi_send_end_request(stream, header.id, EndRequestStatus::UnknownRole)?;
|
||||
return Err(Box::new(ERR!("Only Responder role is supported")));
|
||||
}
|
||||
// check if we have already request of this kind
|
||||
if let Some(_) = requests.get(&header.id)
|
||||
{
|
||||
WARN!("Request {} already exists, ignore this message", header.id);
|
||||
}
|
||||
else
|
||||
{
|
||||
let rq:FGCIRequest = FGCIRequest {
|
||||
id: header.id,
|
||||
params: HashMap::new(),
|
||||
data: vec![0;0],
|
||||
state: FCGIRequestState::WaitForParams,
|
||||
fd: stream.as_raw_fd(),
|
||||
};
|
||||
requests.insert(header.id, rq);
|
||||
}
|
||||
},
|
||||
FCGIHeaderType::Params => {
|
||||
if let Some(rq) = requests.get_mut(&header.id)
|
||||
{
|
||||
if rq.state != FCGIRequestState::WaitForParams
|
||||
{
|
||||
WARN!("Should not receive a param record as the request is in {} state", rq.state);
|
||||
}
|
||||
else
|
||||
{
|
||||
if header.length == 0
|
||||
{
|
||||
INFO!("All param records read, now wait for stdin data on request: {}", header.id);
|
||||
rq.state = FCGIRequestState::WaitForStdin;
|
||||
}
|
||||
else
|
||||
{
|
||||
fcgi_decode_params(rq,&fcgi_read_body(stream, &header)?)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN!("Uknow request {}, ignore param record", header.id);
|
||||
}
|
||||
},
|
||||
FCGIHeaderType::Stdin => {
|
||||
if let Some(rq) = requests.get_mut(&header.id)
|
||||
{
|
||||
if rq.state != FCGIRequestState::WaitForStdin
|
||||
{
|
||||
WARN!("Should not receive a stdin record as the request is in {} state", rq.state);
|
||||
}
|
||||
else
|
||||
{
|
||||
if header.length == 0
|
||||
{
|
||||
INFO!("All stdin records read, now wait for stdout data on request: {}", header.id);
|
||||
rq.state = FCGIRequestState::WaitForStdout;
|
||||
if let Err(error) = fcgi_execute_request_handle(rq)
|
||||
{
|
||||
// send stderror
|
||||
fcgi_send_stderr(stream, header.id, Some(error))?;
|
||||
}
|
||||
fcgi_send_stderr(stream, header.id, None)?;
|
||||
fcgi_send_stdout(stream, header.id, None)?;
|
||||
// send end connection
|
||||
fcgi_send_end_request(stream, header.id, EndRequestStatus::Complete)?;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
let body = fcgi_read_body(stream, &header)?;
|
||||
rq.data.extend(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN!("Uknow request {}, ignore stdin record", header.id);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
WARN!("Unsupported record type: {} on request {}", header.kind, header.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fcgi_read_header<T: Read + Write + AsRawFd >(stream: &mut T) -> Result<FcgiHeader, Error>
|
||||
{
|
||||
let mut buf = vec![0;FCGI_HEADER_LEN];
|
||||
stream.read_exact(&mut buf)?;
|
||||
let header: FcgiHeader = FcgiHeader::from_bytes(&buf);
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
fn fcgi_read_body<T: Read + Write + AsRawFd >(stream: &mut T, header: & FcgiHeader) -> Result<Vec<u8>, Error>
|
||||
{
|
||||
let mut buf = vec![0; header.length as usize];
|
||||
stream.read_exact(&mut buf)?;
|
||||
let mut pad: Vec<u8> = vec![0; header.padding as usize];
|
||||
stream.read_exact(&mut pad)?;
|
||||
|
||||
Ok(buf.to_vec())
|
||||
}
|
||||
|
||||
fn fcgi_decode_strlen(data: &[u8]) -> usize
|
||||
{
|
||||
let b0 = data[0];
|
||||
if b0 >> 7 == 0
|
||||
{
|
||||
b0 as usize
|
||||
}
|
||||
else
|
||||
{
|
||||
return (((data[0] as usize) & 0x7f) << 24) + ((data[1] as usize) << 16) + ((data[2] as usize) << 8) + (data[3] as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn fcgi_decode_params(rq: &mut FGCIRequest, data:& Vec<u8>) -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
let mut index: usize = 1;
|
||||
let key_len = fcgi_decode_strlen(data);
|
||||
if key_len > 127
|
||||
{
|
||||
index = 4;
|
||||
}
|
||||
let value_len = fcgi_decode_strlen(&data[index..]);
|
||||
//INFO!("Key len {}, value len {}", key_len, value_len);
|
||||
if value_len > 127
|
||||
{
|
||||
index += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
index += 1;
|
||||
}
|
||||
//INFO!("data: {:?}", data);
|
||||
//INFO!("key: {:?}", data[index..index + key_len].to_vec());
|
||||
//INFO!("Value: {:?}", data[index+key_len..index+key_len+value_len].to_vec());
|
||||
let key = String::from_utf8(data[index..index+key_len].to_vec())?;
|
||||
let value: String = String::from_utf8(data[index+key_len..index+key_len+value_len].to_vec())?;
|
||||
INFO!("PARAM: [{}] -> [{}]", key, value);
|
||||
let _ = rq.params.insert(key, value);
|
||||
Ok(())
|
||||
|
||||
|
||||
}
|
167
src/main.rs
Normal file
167
src/main.rs
Normal file
@ -0,0 +1,167 @@
|
||||
//! Lua FastCGI main application
|
||||
//!
|
||||
//! **Author**: "Dany LE <mrsang@iohub.dev>"
|
||||
//!
|
||||
//!
|
||||
#![warn(
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unused_extern_crates,
|
||||
unused_qualifications,
|
||||
unused_results,
|
||||
missing_docs,
|
||||
clippy::pedantic,
|
||||
clippy::missing_docs_in_private_items
|
||||
)]
|
||||
use serde;
|
||||
use toml;
|
||||
use clap;
|
||||
//use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::net::TcpListener;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::net::UnixListener;
|
||||
use std::panic;
|
||||
use std::path::Path;
|
||||
use std::os::fd::FromRawFd;
|
||||
use std::thread;
|
||||
use std::io::Read;
|
||||
use luad::*;
|
||||
|
||||
/// Callback: clean up function
|
||||
///
|
||||
/// This function remove the unix socket file if
|
||||
/// exist before quiting the program
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `n` - system exit code
|
||||
fn clean_up(n: i32) {
|
||||
if let Ok(socket_name) = std::env::var("socket") {
|
||||
let file = socket_name.replace("unix:", "");
|
||||
let path = Path::new(&file);
|
||||
if path.exists() {
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
}
|
||||
if n != 0 {
|
||||
panic!("{}", format!("The LUA fastCGI daemon is terminated by system signal: {}", n));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn handle_request<T: Read + Write + AsRawFd >(stream: &mut T) {
|
||||
if let Err(error) = process_request(stream)
|
||||
{
|
||||
ERROR!("Unable to process request: {}", error);
|
||||
}
|
||||
INFO!("Request on socket {} is processed", stream.as_raw_fd());
|
||||
}
|
||||
|
||||
/// Start the `fastCGI` server
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `socket_opt` - The socket string that the server listens on
|
||||
fn serve(socket_opt: Option<&str>) {
|
||||
|
||||
|
||||
// bind to a socket if any
|
||||
if let Some(socket_name) = socket_opt {
|
||||
// test if the socket name is an unix domain socket
|
||||
if socket_name.starts_with("unix:") {
|
||||
// e.g unix:/var/run/lighttpd/maint/efcgi.socket
|
||||
INFO!("Use unix domain socket: {}", socket_name);
|
||||
std::env::set_var("socket", socket_name);
|
||||
let listener = UnixListener::bind(socket_name.replace("unix:", "")).unwrap();
|
||||
on_exit(clean_up);
|
||||
for client in listener.incoming() {
|
||||
let mut stream = client.unwrap();
|
||||
let _= std::thread::spawn(move || {
|
||||
handle_request(&mut stream);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// TCP socket eg. 127.0.0.1:9000
|
||||
INFO!("Use TCP socket: {}", socket_name);
|
||||
let listener = TcpListener::bind(socket_name).unwrap();
|
||||
for client in listener.incoming() {
|
||||
let mut stream = client.unwrap();
|
||||
let _= thread::spawn(move || {
|
||||
handle_request(&mut stream);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if there is no socket configuration, assume that the stdio is already mapped
|
||||
// to a socket. This is usually done by by the parent process (e.g. webserver) that launches efcgi
|
||||
INFO!("No socket specified! use stdin as listenning socket");
|
||||
let stdin = std::io::stdin();
|
||||
let listener = unsafe{ UnixListener::from_raw_fd(stdin.as_raw_fd())};
|
||||
for client in listener.incoming() {
|
||||
let mut stream = client.unwrap();
|
||||
|
||||
let _= thread::spawn(move || {
|
||||
handle_request(&mut stream);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Config {
|
||||
socket: Option<String>,
|
||||
pidfile: Option<String>,
|
||||
user: Option<String>,
|
||||
group: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Main application entry
|
||||
///
|
||||
/// Run a `fastCGI` server
|
||||
fn main() {
|
||||
let _log = LOG::init_log();
|
||||
|
||||
let matches = clap::App::new(DAEMON_NAME)
|
||||
.author(APP_AUTHOR)
|
||||
.about("Lua general purpose socket handle daemon")
|
||||
.version(APP_VERSION)
|
||||
.arg(
|
||||
clap::Arg::with_name("file")
|
||||
.short("f")
|
||||
.long("file")
|
||||
.value_name("FILE")
|
||||
.help("Configuration file")
|
||||
.required(false)
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
match matches.value_of("file") {
|
||||
Some(path) => {
|
||||
INFO!("Configuration file: {}", path);
|
||||
let contents = std::fs::read_to_string(path).unwrap();
|
||||
let config: Config = toml::from_str(&contents).unwrap();
|
||||
|
||||
// write pid file
|
||||
match config.pidfile {
|
||||
Some(pidfile) => {
|
||||
let mut f = std::fs::File::create(&pidfile).unwrap();
|
||||
write!(f, "{}", std::process::id()).unwrap();
|
||||
INFO!("PID file created at {}", pidfile);
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
// drop user privilege if only user and group available in
|
||||
// the configuration file, otherwise ignore
|
||||
privdrop(config.user.as_deref(), config.group.as_deref()).unwrap();
|
||||
serve(config.socket.as_deref());
|
||||
},
|
||||
None => {
|
||||
serve(None);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user