Skip to content

Commit

Permalink
feat: initial implementation for wasm plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
luizfonseca committed Dec 19, 2024
1 parent 974bb0f commit 8cf2076
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 90 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ jobs:
components: rustfmt,clippy

- name: Publish to crates.io
run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }}
run: cargo publish -p proksi --token ${{ secrets.CRATES_IO_TOKEN }}

upload-artifacts:
continue-on-error: true
Expand Down
2 changes: 1 addition & 1 deletion crates/plugin_request_id/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ edition = "2021"
crate-type = ["cdylib"]

[dependencies]
plugins_api = { path = "../plugins_api", version = "0.1.0" }
plugins_api = { path = "../plugins_api" }
19 changes: 19 additions & 0 deletions crates/plugin_request_id/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
use plugins_api::{register_plugin, Plugin};

pub struct RequestIdPlugin {}

impl Plugin for RequestIdPlugin {
fn new() -> Self {
Self {}
}
fn new_ctx(&self, _: String) -> String {
String::from("Adds request Id to every request")
}

fn on_request_filter(&self, session: &plugins_api::Session, _ctx: String) -> Result<bool, ()> {
let v = session.get_header("test");
println!("test: {:?}", v);
Ok(true)
}
}

register_plugin!(RequestIdPlugin);
4 changes: 2 additions & 2 deletions crates/plugins_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ repository = "https://github.com/luizfonseca/proksi"
rust-version.workspace = true
workspace = "../.."

[lib]
crate-type = ["cdylib"]
# [lib]
# crate-type = ["lib"]

[dependencies]
anyhow = "1.0.91"
Expand Down
64 changes: 36 additions & 28 deletions crates/plugins_api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,59 @@
#[allow(clippy::wildcard_imports)]
use wit::*;

#[derive(Clone, Debug, PartialEq, Ord, Eq, PartialOrd, Hash)]
pub struct Context {}

#[derive(Clone, Debug, PartialEq, Ord, Eq, PartialOrd, Hash)]
pub struct Session {}
impl Session {
#[must_use]
pub fn get_header(&self, _key: &str) -> Option<&str> {
unimplemented!()
}

#[must_use]
pub fn req_header() -> Option<bool> {
unimplemented!()
}
}
pub use wit::Session;

pub trait Plugin: Send + Sync {
fn new_ctx(ctx: String) -> String;

#[must_use]
fn on_request_filter(
_session: Session,
_ctx: Context,
) -> impl std::future::Future<Output = Result<bool, ()>> {
async { Ok(true) }
fn new() -> Self
where
Self: Sized;
fn new_ctx(&self, ctx: String) -> String;

fn on_request_filter(&self, _session: &Session, _ctx: String) -> Result<bool, ()> {
Ok(true)
}
}

mod wit {
wit_bindgen::generate!({
world: "plugin"
world: "plugin",
skip: ["init-plugin"]
});
}

wit::export!(Component);

/// Registers the provided type as a Proksi plugin.
#[macro_export]
macro_rules! register_plugin {
($plugin_type:ty) => {
#[export_name = "init-plugin"]
pub extern "C" fn __init_plugin() {
std::env::set_current_dir(std::env::var("PWD").unwrap()).unwrap();
plugins_api::register_plugin(|| Box::new(<$plugin_type as Plugin>::new()));
}
};
}

#[doc(hidden)]
pub fn register_plugin(build_plugin: fn() -> Box<dyn Plugin>) {
unsafe { PLUGIN = Some((build_plugin)()) }
}

fn plugin() -> &'static mut dyn Plugin {
unsafe { PLUGIN.as_deref_mut().unwrap() }
}

static mut PLUGIN: Option<Box<dyn Plugin>> = None;

struct Component;

impl wit::Guest for Component {
fn new_ctx(_ctx: String) -> String {
String::from("hello")
String::from("hello from wit")
}

fn on_request_filter(_session: &wit::Session, _ctx: String) -> Result<bool, ()> {
Ok(true)
fn on_request_filter(session: &wit::Session, ctx: String) -> Result<bool, ()> {
plugin().on_request_filter(session, ctx)
}
}
2 changes: 2 additions & 0 deletions crates/plugins_api/wit/plugin.wit
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package proksi:plugin;

world plugin {

/// Initializes the extension.
export init-plugin: func();

resource session {
get-header: func(key: string) -> option<string>;
Expand Down
176 changes: 118 additions & 58 deletions crates/proksi/src/wasm/js_plugin.rs
Original file line number Diff line number Diff line change
@@ -1,74 +1,134 @@
// use std::sync::Arc;

// // use wasmer::{imports, Instance, Module, Store, ValueType};
// use wasmtime::{Engine, Linker, Store};
// use wasmtime_wasi::{
// preview1::{self, WasiP1Ctx},
// WasiCtxBuilder,
// };
use wasmtime::{
component::{Linker, Resource},
Engine, Store,
};
use wasmtime_wasi::{add_to_linker_async, preview1::WasiP1Ctx, WasiCtxBuilder, WasiView};

// wasmtime::component::bindgen!({
// path: "../plugins_api/wit/plugin.wit",
// with: {
// "session": SessionTest,
// }
// });

#[allow(dead_code)]
struct SessionTest {}
#[derive(Clone)]
pub struct SessionTest {}

impl SessionTest {
#[allow(dead_code)]
pub fn new() -> Self {
Self {}
}
#[allow(dead_code)]
pub fn get_header(_key: &str) -> String {
pub fn get_header(&self, _key: String) -> String {
String::from("test")
}
}

// #[allow(dead_code)]
// pub async fn load_plugin() -> anyhow::Result<()> {
// let mut config = wasmtime::Config::new();
// config.wasm_reference_types(true);
// config.debug_info(false);
// config.async_support(true);

// let engine = Engine::new(&config)?;
// let wasi_ctx = WasiCtxBuilder::new()
// .inherit_stdio()
// .inherit_stdout()
// .build_p1();

// let mut linker: Linker<WasiP1Ctx> = Linker::new(&engine);
// preview1::add_to_linker_async(&mut linker, |t| t)?;
// let mut store = Store::new(&engine, wasi_ctx);

// let module = wasmtime::Module::from_binary(
// &engine,
// include_bytes!("../../../../mid-test/target/wasm32-wasip1/release/mid_test.wasm"),
// )?;

// // instance

// let instance = linker.instantiate_async(&mut store, &module).await?;

// let req_filter_fn = instance.get_func(&mut store, "on_request_filter").unwrap();

// let scope = wasmtime::RootScope::new(&mut store);
// let session = wasmtime::ExternRef::new(scope, Arc::new(SessionTest::new()))?;

// // call function with ref
// let mut ret: Vec<wasmtime::Val> = vec![wasmtime::Val::I32(0)];
// req_filter_fn
// .call_async(&mut store, &[session.into()], &mut ret)
// .await?;

// Ok(())
// }
#[allow(dead_code)]
pub async fn load_plugin(path: &[u8]) -> anyhow::Result<()> {
let mut config = wasmtime::Config::new();
config.wasm_reference_types(true);
config.debug_info(false);
config.async_support(true);

let engine = Engine::new(&config)?;
let wasi_ctx = WasiCtxBuilder::new()
.inherit_stdio()
.inherit_stdout()
.build_p1();

let mut linker: Linker<WasiP1Ctx> = wasmtime::component::Linker::new(&engine);

let session_now = SessionTest::new();
// let mut abc = wasmtime_wasi::ResourceTable::new();
// let res = wasi_ctx.table().push(session_now.clone())?;

add_to_linker_async(&mut linker)?;
let mut store = Store::new(&engine, wasi_ctx);
let resource = store.data_mut().table().push(session_now.clone())?;
let resource_id = resource.rep();

Check failure on line 51 in crates/proksi/src/wasm/js_plugin.rs

View workflow job for this annotation

GitHub Actions / Test

unused variable: `resource_id`
let module = wasmtime::component::Component::from_binary(&engine, path)?;

// instance

let ty = wasmtime::component::ResourceType::host::<SessionTest>();
linker.root().resource("session", ty, |mut storex, rep| {
storex
.data_mut()
.table()
.delete::<SessionTest>(Resource::new_own(rep));
Ok(())
})?;

// let res_before_move: Resource<SessionTest> =
// wasmtime::component::Resource::new_own(resource.rep());

// let into_any = res.try_into_resource_any(&mut store)?;

// linker
// .root()
// .func_wrap("[method]session.get-header", |mut st, (input,)| {
// let result = 1;

// Ok(())
// });
// linker.root().func_new(
// "[method]session.get-header",
// move |mut storex, params, results| {
// // in resources, the first param is the resource (self)
// // let input = match params[1].clone() {
// // wasmtime::component::Val::String(v) => v,
// // _ => panic!("invalid input"),
// // };
// // let ss = storex.data_mut().table().get(&resource)?;
// // let v = ss.get_header(input);
// results[0] = wasmtime::component::Val::Option(Some(Box::new(
// wasmtime::component::Val::String("123".into()),
// )));

// // storex.data_mut().table().delete(resource)?;

// Ok(())
// },
// )?;

let instance = linker.instantiate_async(&mut store, &module).await?;
let req_filter_fn = instance
.get_typed_func::<(Resource<SessionTest>, String), (Result<bool, ()>,)>(
&mut store,
"on-request-filter",
)
.unwrap();

let resource = store.data_mut().table().push(session_now.clone())?;

let ret = req_filter_fn
.call_async(
&mut store,
(Resource::new_own(resource.rep()), String::from("hello")),
)
.await?;

req_filter_fn.post_return(store)?;

println!("ret: {:?}", ret);
Ok(())
}

// #[cfg(test)]
// mod tests {
// use super::*;
#[cfg(test)]
mod tests {
use super::*;

// #[tokio::test]
// async fn test_load_plugin() {
// load_plugin().await.unwrap();
#[tokio::test]
async fn test_load_plugin() {
load_plugin(include_bytes!(

Check failure on line 126 in crates/proksi/src/wasm/js_plugin.rs

View workflow job for this annotation

GitHub Actions / Test

couldn't read `crates/proksi/src/wasm/../../../../target/wasm32-wasip2/debug/plugin_request_id.wasm`: No such file or directory (os error 2)
"../../../../target/wasm32-wasip2/debug/plugin_request_id.wasm"
))
.await
.unwrap();

// assert_eq!(1, 1)
// }
// }
assert_eq!(1, 1)
}
}

0 comments on commit 8cf2076

Please sign in to comment.