Skip to content

Commit 5f56127

Browse files
Add an option to dynamically initialize the server handler
1 parent 3f5fc2e commit 5f56127

File tree

10 files changed

+150
-39
lines changed

10 files changed

+150
-39
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ members = [
55
"crates/mime",
66
"crates/http",
77
"crates/client",
8-
"crates/server"
8+
"crates/server",
99
]
1010
default-members = ["crates/server"]
1111

crates/http/src/request/mod.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{
44
collections::HashMap,
55
env,
66
ffi::OsStr,
7-
io::{BufRead, BufReader, Read, Write},
7+
io::{BufRead, BufReader, BufWriter, Read, Write},
88
path::Path,
99
};
1010

@@ -316,6 +316,20 @@ impl HttpRequest {
316316
pub fn respond_str(&mut self, text: &str) -> Result<()> {
317317
self.respond_buf(text.as_bytes())
318318
}
319+
/// Responds using the given function.
320+
///
321+
/// It sends the header for this request, and then calls
322+
/// the provided function with a [writer](Write).
323+
pub fn respond_with<F>(&mut self, f: F) -> Result<()>
324+
where
325+
F: FnOnce(&mut dyn Write) -> Result<()>,
326+
{
327+
self.respond()?;
328+
let mut out = BufWriter::new(self.stream.get_mut());
329+
f(&mut out)?;
330+
out.flush()?;
331+
Ok(())
332+
}
319333
/// Respond to the request with the data read from reader as a body
320334
///
321335
/// # Errors

crates/server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pool = { package = "job-pool", version = ">=0.6.0", git = "https://github.com/sa
1616
jsonrs = { package = "jsonrs", version = ">=0.1.4", git = "https://github.com/saulvaldelvira/json.rs" }
1717
base64 = { package = "rb64", version = ">=0.1.0", git = "https://github.com/saulvaldelvira/rb64" }
1818
url = { package = "url-utils", version = ">=0.1.0", path = "../url" }
19+
libloading = "0.8.8"
1920

2021
[dependencies.rustls]
2122
version = ">=0.23.28"

crates/server/src/config.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub struct ServerConfig {
2626
pub keep_alive_timeout: Duration,
2727
pub keep_alive_requests: u16,
2828
pub log_file: Option<String>,
29+
pub setup_lib: Option<String>,
2930

3031
#[cfg(feature = "tls")]
3132
pub tls_config: Option<Arc<rustls::ServerConfig>>,
@@ -38,6 +39,7 @@ impl fmt::Debug for ServerConfig {
3839
.field("pool_conf", &self.pool_conf)
3940
.field("keep_alive_timeout", &self.keep_alive_timeout)
4041
.field("keep_alive_requests", &self.keep_alive_requests)
42+
.field("setup_lib", &self.setup_lib)
4143
.field("log_file", &self.log_file);
4244

4345
#[cfg(feature = "tls")]
@@ -187,6 +189,9 @@ impl ServerConfig {
187189
let n: u8 = parse_next!();
188190
log::set_level(n.try_into()?);
189191
}
192+
193+
"--setup-lib" => conf.setup_lib = Some(parse_next!()),
194+
190195
#[cfg(feature = "tls")]
191196
"--tls" => tls = true,
192197

@@ -378,7 +383,7 @@ impl ServerConfig {
378383

379384
fn help() -> ! {
380385
/* FIXME: Don't output tls options if the tls feature is disabled */
381-
println!(concat!(
386+
println!(
382387
"\
383388
http-srv: Copyright (C) 2025 Saúl Valdelvira
384389
@@ -397,6 +402,7 @@ PARAMETERS:
397402
-l, --log <file> Set log file
398403
-h, --help Display this help message
399404
--log-level <n> Set log level
405+
--setup-lib <file> Load the given file to setup the server
400406
--conf <file> Use the given config file instead of the default one
401407
--license Output the license of this program
402408
@@ -407,7 +413,7 @@ EXAMPLES:
407413
http-srv -p 8080 -d /var/html
408414
http-srv -d ~/desktop -n 1024 --keep-alive 120
409415
http-srv --log /var/log/http-srv.log"
410-
));
416+
);
411417
process::exit(0);
412418
}
413419

@@ -444,7 +450,7 @@ impl Default for ServerConfig {
444450
keep_alive_timeout: Duration::from_secs(0),
445451
keep_alive_requests: 10000,
446452
log_file: None,
447-
453+
setup_lib: None,
448454
#[cfg(feature = "tls")]
449455
tls_config: None,
450456
}

crates/server/src/handler/indexing.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,14 @@ pub fn index_of(filename: &str, show_hidden: bool) -> Result<String> {
7373
files.sort_by_key(DirEntry::path);
7474

7575
html.push_str("<table><tr><th>Name</th><th>Size</th></tr>");
76-
if let Some(parent) = Path::new(filename).parent() {
77-
if parent.starts_with(cwd) {
78-
let url = parent.strip_prefix(cwd)?;
79-
let url = encode_path(url, show_hidden)?;
80-
html.write_fmt(format_args!(
81-
"<tr><td>&larr;</td><td><a href=\"{url}\">..</a></td></tr>"
82-
))?;
83-
}
76+
if let Some(parent) = Path::new(filename).parent()
77+
&& parent.starts_with(cwd)
78+
{
79+
let url = parent.strip_prefix(cwd)?;
80+
let url = encode_path(url, show_hidden)?;
81+
html.write_fmt(format_args!(
82+
"<tr><td>&larr;</td><td><a href=\"{url}\">..</a></td></tr>"
83+
))?;
8484
}
8585
for file in files {
8686
let path = file.path();

crates/server/src/main.rs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,32 @@ use std::{env, process, thread, time::Duration};
22

33
use encoding::StreamReader;
44
use http_srv::prelude::*;
5+
use libloading::{Library, Symbol};
56

6-
pub fn main() {
7-
let args: Vec<_> = env::args().skip(1).collect();
8-
let config = ServerConfig::parse(&args).unwrap_or_else(|err| {
9-
eprintln!("{err}");
10-
process::exit(1);
11-
});
7+
type Result<T> = ::core::result::Result<T, libloading::Error>;
8+
9+
fn load_lib(handler: &mut Handler, name: &str) -> Result<Library> {
10+
unsafe {
11+
let lib = libloading::Library::new(name)?;
12+
13+
let init_handler: Symbol<fn(*mut Handler)> = lib.get(b"init_handler")?;
1214

15+
init_handler(handler);
16+
17+
Ok(lib)
18+
}
19+
}
20+
21+
fn get_handler(config: &ServerConfig) -> Result<(Option<Library>, Handler)> {
1322
let mut handler = Handler::default();
23+
let mut _lib = None;
24+
25+
if let Some(path) = &config.setup_lib {
26+
let mut handler = Handler::new();
27+
_lib = Some(load_lib(&mut handler, path)?);
28+
return Ok((_lib, handler));
29+
}
30+
1431
handler.get("/sleep", |req: &mut HttpRequest| {
1532
thread::sleep(Duration::from_secs(5));
1633
req.ok()
@@ -83,10 +100,26 @@ pub fn main() {
83100
auth.apply(|req: &mut HttpRequest| req.respond_str("Secret message")),
84101
);
85102

103+
Ok((_lib, handler))
104+
}
105+
106+
pub fn main() {
107+
let args: Vec<_> = env::args().skip(1).collect();
108+
let config = ServerConfig::parse(&args).unwrap_or_else(|err| {
109+
eprintln!("{err}");
110+
process::exit(1);
111+
});
112+
113+
let (_lib, handler) = get_handler(&config).unwrap_or_else(|err| {
114+
eprintln!("ERROR: {err}");
115+
process::exit(1);
116+
});
117+
86118
let mut server = HttpServer::new(config).unwrap_or_else(|err| {
87119
eprintln!("ERROR: {err}");
88120
std::process::exit(1)
89121
});
122+
90123
server.set_handler(handler);
91124
server.run();
92125
}

crates/url/src/ffi.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,26 @@ pub struct Buffer {
1616
#[inline(always)]
1717
unsafe fn __bind_fn(ptr: *const c_char, f: fn(&str) -> crate::Result<Cow<str>>) -> Buffer {
1818
let cstr = unsafe { CStr::from_ptr(ptr) };
19-
if let Ok(s) = cstr.to_str() {
20-
if let Ok(d) = f(s) {
21-
match d {
22-
Cow::Owned(mut own) => {
23-
own.push('\0');
24-
let own = own.into_boxed_str();
25-
let len = own.len();
26-
return Buffer {
27-
ptr: Box::into_raw(own) as *const u8,
28-
len,
29-
__is_owned: true,
30-
};
31-
}
32-
Cow::Borrowed(bor) => {
33-
return Buffer {
34-
ptr: bor.as_ptr(),
35-
len: bor.len(),
36-
__is_owned: false,
37-
};
38-
}
19+
if let Ok(s) = cstr.to_str()
20+
&& let Ok(d) = f(s)
21+
{
22+
match d {
23+
Cow::Owned(mut own) => {
24+
own.push('\0');
25+
let own = own.into_boxed_str();
26+
let len = own.len();
27+
return Buffer {
28+
ptr: Box::into_raw(own) as *const u8,
29+
len,
30+
__is_owned: true,
31+
};
32+
}
33+
Cow::Borrowed(bor) => {
34+
return Buffer {
35+
ptr: bor.as_ptr(),
36+
len: bor.len(),
37+
__is_owned: false,
38+
};
3939
}
4040
}
4141
}

example-plugin/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Cargo.lock
2+
target

example-plugin/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[workspace]
2+
3+
[package]
4+
name = "example-plugin"
5+
version = "0.1.0"
6+
edition = "2024"
7+
8+
[lib]
9+
name = "init_handler"
10+
crate-type = [ "cdylib" ]
11+
12+
[dependencies]
13+
server = { package = "http-srv", path = "../crates/server" }

example-plugin/src/lib.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
extern crate server;
2+
3+
use http::HttpRequest;
4+
use server::{handler::Handler, http};
5+
6+
#[unsafe(no_mangle)]
7+
pub extern "Rust" fn init_handler(handler: Option<&mut Handler>) {
8+
let handler = handler.unwrap();
9+
10+
handler.get("/count", |req: &mut HttpRequest| {
11+
macro_rules! get_numeric_arg {
12+
($n:literal) => {
13+
match req.param($n) {
14+
Some(num) => match num.parse::<u32>() {
15+
Ok(num) => num,
16+
Err(err) => {
17+
return req
18+
.set_status(400)
19+
.respond_str(&format!("Error parsing '{}': {err}", $n));
20+
}
21+
},
22+
None => {
23+
return req
24+
.set_status(400)
25+
.respond_str(concat!("Missing numeric argument ", $n));
26+
}
27+
}
28+
};
29+
}
30+
let start = get_numeric_arg!("start");
31+
let end = get_numeric_arg!("end");
32+
33+
req.respond_with(|out| {
34+
for i in start..=end {
35+
write!(out, "{i}, ")?;
36+
}
37+
Ok(())
38+
})
39+
});
40+
41+
println!("example-plugin: Done");
42+
}

0 commit comments

Comments
 (0)