From 8cf2076c4fa520a351bb2ce10dc2108c15c1b02a Mon Sep 17 00:00:00 2001 From: Luiz Fonseca Date: Thu, 19 Dec 2024 13:08:40 +0100 Subject: [PATCH] feat: initial implementation for wasm plugins --- .github/workflows/release.yml | 2 +- crates/plugin_request_id/Cargo.toml | 2 +- crates/plugin_request_id/src/lib.rs | 19 +++ crates/plugins_api/Cargo.toml | 4 +- crates/plugins_api/src/lib.rs | 64 +++++----- crates/plugins_api/wit/plugin.wit | 2 + crates/proksi/src/wasm/js_plugin.rs | 176 +++++++++++++++++++--------- 7 files changed, 179 insertions(+), 90 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d58fa77..c7a6de4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/crates/plugin_request_id/Cargo.toml b/crates/plugin_request_id/Cargo.toml index 26792a7..3521042 100644 --- a/crates/plugin_request_id/Cargo.toml +++ b/crates/plugin_request_id/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -plugins_api = { path = "../plugins_api", version = "0.1.0" } +plugins_api = { path = "../plugins_api" } diff --git a/crates/plugin_request_id/src/lib.rs b/crates/plugin_request_id/src/lib.rs index 8b13789..859465d 100644 --- a/crates/plugin_request_id/src/lib.rs +++ b/crates/plugin_request_id/src/lib.rs @@ -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 { + let v = session.get_header("test"); + println!("test: {:?}", v); + Ok(true) + } +} + +register_plugin!(RequestIdPlugin); diff --git a/crates/plugins_api/Cargo.toml b/crates/plugins_api/Cargo.toml index 2755236..1728226 100644 --- a/crates/plugins_api/Cargo.toml +++ b/crates/plugins_api/Cargo.toml @@ -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" diff --git a/crates/plugins_api/src/lib.rs b/crates/plugins_api/src/lib.rs index 64a7e20..d35e5d1 100644 --- a/crates/plugins_api/src/lib.rs +++ b/crates/plugins_api/src/lib.rs @@ -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 { - 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> { - 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 { + 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) { + unsafe { PLUGIN = Some((build_plugin)()) } +} + +fn plugin() -> &'static mut dyn Plugin { + unsafe { PLUGIN.as_deref_mut().unwrap() } +} + +static mut PLUGIN: Option> = 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 { - Ok(true) + fn on_request_filter(session: &wit::Session, ctx: String) -> Result { + plugin().on_request_filter(session, ctx) } } diff --git a/crates/plugins_api/wit/plugin.wit b/crates/plugins_api/wit/plugin.wit index 876f6c8..61dcc0e 100644 --- a/crates/plugins_api/wit/plugin.wit +++ b/crates/plugins_api/wit/plugin.wit @@ -2,6 +2,8 @@ package proksi:plugin; world plugin { + /// Initializes the extension. + export init-plugin: func(); resource session { get-header: func(key: string) -> option; diff --git a/crates/proksi/src/wasm/js_plugin.rs b/crates/proksi/src/wasm/js_plugin.rs index 88df4e3..235d8d3 100644 --- a/crates/proksi/src/wasm/js_plugin.rs +++ b/crates/proksi/src/wasm/js_plugin.rs @@ -1,14 +1,19 @@ -// 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)] @@ -16,59 +21,114 @@ impl SessionTest { 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 = 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 = 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 = 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(); + let module = wasmtime::component::Component::from_binary(&engine, path)?; + + // instance + + let ty = wasmtime::component::ResourceType::host::(); + linker.root().resource("session", ty, |mut storex, rep| { + storex + .data_mut() + .table() + .delete::(Resource::new_own(rep)); + Ok(()) + })?; + + // let res_before_move: Resource = + // 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, String), (Result,)>( + &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!( + "../../../../target/wasm32-wasip2/debug/plugin_request_id.wasm" + )) + .await + .unwrap(); -// assert_eq!(1, 1) -// } -// } + assert_eq!(1, 1) + } +}