Skip to content

Commit b3ba6b0

Browse files
committed
feat: load service as library
1 parent 0e9cd15 commit b3ba6b0

File tree

8 files changed

+99
-67
lines changed

8 files changed

+99
-67
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/file-explorer/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ publish = false
99
crate-type = ["cdylib"]
1010

1111
[dependencies]
12+
http-body-util = { workspace = true }
13+
hyper = { workspace = true }
14+
1215
http-server-plugin = { workspace = true }

crates/file-explorer/src/lib.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1+
use std::sync::Arc;
2+
3+
use http_body_util::Full;
4+
use hyper::body::{Bytes, Incoming};
5+
use hyper::{Request, Response};
6+
17
use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar};
28

39
export_plugin!(register);
410

11+
#[allow(improper_ctypes_definitions)]
512
extern "C" fn register(registrar: &mut dyn PluginRegistrar) {
613
registrar.register_function(
714
"file-explorer",
8-
Box::new(FileExplorer {
15+
Arc::new(FileExplorer {
916
path: String::from("/"),
1017
}),
1118
);
@@ -16,7 +23,7 @@ pub struct FileExplorer {
1623
}
1724

1825
impl Function for FileExplorer {
19-
fn call(&self, _args: &[f64]) -> Result<f64, InvocationError> {
20-
Ok(0.0)
26+
fn call(&self, _: Request<Incoming>) -> Result<Response<Full<Bytes>>, InvocationError> {
27+
Ok(Response::new(Full::new(Bytes::from("Hello, World!"))))
2128
}
2229
}

crates/http-server-plugin/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,9 @@ keywords = ["sdk", "http", "server", "plugin", "dll"]
1010
license = "MIT OR Apache-2.0"
1111
readme = "README.md"
1212

13+
[dependencies]
14+
http-body-util = { workspace = true }
15+
hyper = { workspace = true }
16+
1317
[build-dependencies]
1418
rustc_version = { workspace = true }

crates/http-server-plugin/src/lib.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1+
use std::sync::Arc;
2+
3+
use http_body_util::Full;
4+
use hyper::body::{Bytes, Incoming};
5+
use hyper::{Request, Response};
6+
17
pub static CORE_VERSION: &str = env!("CARGO_PKG_VERSION");
28
pub static RUSTC_VERSION: &str = env!("RUSTC_VERSION");
39

4-
pub trait Function {
5-
fn call(&self, args: &[f64]) -> Result<f64, InvocationError>;
6-
7-
/// Help text that may be used to display information about this function.
8-
fn help(&self) -> Option<&str> {
9-
None
10-
}
10+
pub trait Function: Send + Sync {
11+
fn call(&self, req: Request<Incoming>) -> Result<Response<Full<Bytes>>, InvocationError>;
1112
}
1213

1314
#[derive(Debug)]
@@ -24,7 +25,7 @@ pub struct PluginDeclaration {
2425
}
2526

2627
pub trait PluginRegistrar {
27-
fn register_function(&mut self, name: &str, function: Box<dyn Function>);
28+
fn register_function(&mut self, name: &str, function: Arc<dyn Function>);
2829
}
2930

3031
#[macro_export]

crates/http-server/src/main.rs

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,14 @@ pub mod config;
33
pub mod plugin;
44
pub mod server;
55

6-
use std::{path::PathBuf, process::exit, str::FromStr};
6+
use std::process::exit;
77

88
use anyhow::Result;
99

10-
use crate::plugin::ExternalFunctions;
11-
1210
use self::server::Server;
1311

1412
#[tokio::main]
1513
async fn main() -> Result<()> {
16-
let mut functions = ExternalFunctions::new();
17-
let plugin_library = PathBuf::from_str("./target/debug/libfile_explorer.dylib").unwrap();
18-
19-
unsafe {
20-
functions
21-
.load(plugin_library)
22-
.expect("Function loading failed");
23-
}
24-
25-
let result = functions
26-
.call("file-explorer", &[])
27-
.expect("Invocation failed");
28-
29-
println!("file-explorer() = {}", result);
30-
3114
match Server::run().await {
3215
Ok(_) => {
3316
println!("Server exited successfuly");

crates/http-server/src/plugin.rs

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use std::collections::HashMap;
22
use std::ffi::OsStr;
33
use std::io;
4-
use std::rc::Rc;
4+
use std::sync::{Arc, Mutex};
55

6+
use http_body_util::Full;
7+
use hyper::body::{Bytes, Incoming};
8+
use hyper::{Request, Response};
69
use libloading::Library;
710

811
use http_server_plugin::{
@@ -12,74 +15,83 @@ use http_server_plugin::{
1215
/// A proxy object which wraps a [`Function`] and makes sure it can't outlive
1316
/// the library it came from.
1417
pub struct FunctionProxy {
15-
function: Box<dyn Function>,
16-
_lib: Rc<Library>,
18+
function: Arc<dyn Function>,
19+
_lib: Arc<Library>,
1720
}
1821

1922
impl Function for FunctionProxy {
20-
fn call(&self, args: &[f64]) -> Result<f64, InvocationError> {
21-
self.function.call(args)
22-
}
23-
24-
fn help(&self) -> Option<&str> {
25-
self.function.help()
23+
fn call(&self, req: Request<Incoming>) -> Result<Response<Full<Bytes>>, InvocationError> {
24+
self.function.call(req)
2625
}
2726
}
2827

2928
#[derive(Default)]
3029
pub struct ExternalFunctions {
31-
functions: HashMap<String, FunctionProxy>,
32-
libraries: Vec<Rc<Library>>,
30+
functions: Mutex<HashMap<String, FunctionProxy>>,
31+
libraries: Mutex<Vec<Arc<Library>>>,
3332
}
3433

3534
impl ExternalFunctions {
3635
pub fn new() -> ExternalFunctions {
3736
ExternalFunctions::default()
3837
}
3938

40-
pub unsafe fn load<P: AsRef<OsStr>>(&mut self, library_path: P) -> io::Result<()> {
41-
// load the library into memory
42-
let library = Rc::new(Library::new(library_path).unwrap());
43-
44-
// get a pointer to the plugin_declaration symbol.
39+
/// Loads a plugin from the given path.
40+
///
41+
/// # Safety
42+
///
43+
/// This function is unsafe because it loads a shared library and calls
44+
/// functions from it.
45+
pub unsafe fn load<P: AsRef<OsStr>>(&self, library_path: P) -> io::Result<()> {
46+
let library = Arc::new(Library::new(library_path).unwrap());
4547
let decl = library
4648
.get::<*mut PluginDeclaration>(b"plugin_declaration\0")
4749
.unwrap()
4850
.read();
4951

50-
// version checks to prevent accidental ABI incompatibilities
5152
if decl.rustc_version != RUSTC_VERSION || decl.core_version != CORE_VERSION {
5253
return Err(io::Error::new(io::ErrorKind::Other, "Version mismatch"));
5354
}
5455

55-
let mut registrar = PluginRegistrar::new(Rc::clone(&library));
56+
let mut registrar = PluginRegistrar::new(Arc::clone(&library));
5657

5758
(decl.register)(&mut registrar);
5859

59-
// add all loaded plugins to the functions map
60-
self.functions.extend(registrar.functions);
61-
// and make sure ExternalFunctions keeps a reference to the library
62-
self.libraries.push(library);
60+
self.functions
61+
.lock()
62+
.expect("Cannot lock Mutex")
63+
.extend(registrar.functions);
64+
65+
self.libraries
66+
.lock()
67+
.expect("Cannot lock Mutex")
68+
.push(library);
6369

6470
Ok(())
6571
}
6672

67-
pub fn call(&self, function: &str, arguments: &[f64]) -> Result<f64, InvocationError> {
73+
pub fn call(
74+
&self,
75+
function: &str,
76+
req: Request<Incoming>,
77+
) -> Result<Response<Full<Bytes>>, InvocationError> {
6878
self.functions
79+
.lock()
80+
.expect("Cannot lock Mutex")
6981
.get(function)
7082
.ok_or_else(|| format!("\"{}\" not found", function))
7183
.unwrap()
72-
.call(arguments)
84+
.call(req)
7385
}
7486
}
7587

7688
struct PluginRegistrar {
7789
functions: HashMap<String, FunctionProxy>,
78-
lib: Rc<Library>,
90+
lib: Arc<Library>,
7991
}
8092

8193
impl PluginRegistrar {
82-
fn new(lib: Rc<Library>) -> PluginRegistrar {
94+
fn new(lib: Arc<Library>) -> PluginRegistrar {
8395
PluginRegistrar {
8496
lib,
8597
functions: HashMap::default(),
@@ -88,10 +100,10 @@ impl PluginRegistrar {
88100
}
89101

90102
impl http_server_plugin::PluginRegistrar for PluginRegistrar {
91-
fn register_function(&mut self, name: &str, function: Box<dyn Function>) {
103+
fn register_function(&mut self, name: &str, function: Arc<dyn Function>) {
92104
let proxy = FunctionProxy {
93105
function,
94-
_lib: Rc::clone(&self.lib),
106+
_lib: Arc::clone(&self.lib),
95107
};
96108

97109
self.functions.insert(name.to_string(), proxy);

crates/http-server/src/server/mod.rs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
1-
use std::{convert::Infallible, net::SocketAddr};
1+
use std::{convert::Infallible, net::SocketAddr, path::PathBuf, str::FromStr, sync::Arc};
22

33
use anyhow::Result;
44
use http_body_util::Full;
5-
use hyper::{
6-
body::{Bytes, Incoming},
7-
server::conn::http1,
8-
Method, Request, Response,
9-
};
5+
use hyper::{body::Bytes, server::conn::http1, Method, Response};
106
use hyper_util::{rt::TokioIo, service::TowerToHyperService};
117
use tokio::net::TcpListener;
128
use tower::ServiceBuilder;
139
use tower_http::cors::{Any, CorsLayer};
1410

15-
async fn hello(_: Request<Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
16-
Ok(Response::new(Full::new(Bytes::from("Hello, World!"))))
17-
}
11+
use crate::plugin::ExternalFunctions;
1812

1913
pub struct Server {}
2014

2115
impl Server {
2216
pub async fn run() -> Result<()> {
2317
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
2418
let listener = TcpListener::bind(addr).await?;
19+
let functions = Arc::new(ExternalFunctions::new());
20+
let plugin_library = PathBuf::from_str("./target/debug/libfile_explorer.dylib").unwrap();
21+
22+
unsafe {
23+
functions
24+
.load(plugin_library)
25+
.expect("Function loading failed");
26+
}
2527

2628
loop {
2729
let (stream, _) = listener.accept().await?;
2830
let io = TokioIo::new(stream);
31+
let functions = Arc::clone(&functions);
2932

3033
let cors = CorsLayer::new()
3134
// allow `GET` and `POST` when accessing the resource
@@ -34,8 +37,23 @@ impl Server {
3437
.allow_origin(Any);
3538

3639
tokio::spawn(async move {
40+
let functions = Arc::clone(&functions);
41+
3742
// N.B. should use tower service_fn here, since it's reuqired to be implemented tower Service trait before convert to hyper Service!
38-
let svc = tower::service_fn(hello);
43+
let svc = tower::service_fn(|req| async {
44+
match functions.call("file-explorer", req) {
45+
Ok(res) => Ok::<
46+
Response<http_body_util::Full<hyper::body::Bytes>>,
47+
Infallible,
48+
>(res),
49+
Err(err) => {
50+
eprintln!("Error: {:?}", err);
51+
Ok(Response::new(Full::new(Bytes::from(
52+
"Internal Server Error",
53+
))))
54+
}
55+
}
56+
});
3957
let svc = ServiceBuilder::new().layer(cors).service(svc);
4058
// Convert it to hyper service
4159
let svc = TowerToHyperService::new(svc);

0 commit comments

Comments
 (0)