diff --git a/demo/faas/code/rust/.gitignore b/demo/faas/code/rust/.gitignore new file mode 100644 index 0000000000..8e2418576a --- /dev/null +++ b/demo/faas/code/rust/.gitignore @@ -0,0 +1,15 @@ +# Generated by Cargo +# will have compiled files and executables +**/target/ +# These are backup files generated by rustfmt +**/*.rs.bk + +.DS_Store + +# The cache for docker container dependency +.cargo + +# The cache for chain data in container +.local + +.idea/ \ No newline at end of file diff --git a/demo/faas/code/rust/.rustfmt.toml b/demo/faas/code/rust/.rustfmt.toml new file mode 100644 index 0000000000..729e340ee4 --- /dev/null +++ b/demo/faas/code/rust/.rustfmt.toml @@ -0,0 +1,7 @@ +format_strings = true +group_imports = "StdExternalCrate" +hex_literal_case = "Lower" +imports_granularity = "Crate" +reorder_impl_items = true +use_field_init_shorthand = true +wrap_comments = true diff --git a/demo/faas/code/rust/Cargo.lock b/demo/faas/code/rust/Cargo.lock new file mode 100644 index 0000000000..06f7495634 --- /dev/null +++ b/demo/faas/code/rust/Cargo.lock @@ -0,0 +1,21 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "client" +version = "0.1.0" +dependencies = [ + "proxy-wasm", +] + +[[package]] +name = "proxy-wasm" +version = "0.1.0" + +[[package]] +name = "server" +version = "0.1.0" +dependencies = [ + "proxy-wasm", +] diff --git a/demo/faas/code/rust/Cargo.toml b/demo/faas/code/rust/Cargo.toml new file mode 100644 index 0000000000..4607ea2465 --- /dev/null +++ b/demo/faas/code/rust/Cargo.toml @@ -0,0 +1,6 @@ +[profile.release] +panic = "abort" +lto = true + +[workspace] +members = ["./client", "./proxy-wasm", "./server"] diff --git a/demo/faas/code/rust/README.md b/demo/faas/code/rust/README.md new file mode 100644 index 0000000000..9cb216848f --- /dev/null +++ b/demo/faas/code/rust/README.md @@ -0,0 +1,5 @@ +# Build + +* `cargo build --target=wasm32-unknown-unknown --release` +* Copy `target/wasm32-unknown-unknown/release/function_1.wasm` and `target/wasm32-unknown-unknown/release/function_2.wasm` to your actual directory. +* Test the demo follow [https://mosn.io/layotto/#/zh/start/faas/start](https://mosn.io/layotto/#/zh/start/faas/start). \ No newline at end of file diff --git a/demo/faas/code/rust/client/Cargo.toml b/demo/faas/code/rust/client/Cargo.toml new file mode 100644 index 0000000000..3771942df6 --- /dev/null +++ b/demo/faas/code/rust/client/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "client" +version = "0.1.0" +edition = "2021" + +[lib] +name = "function_1" +crate-type = ["cdylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +proxy-wasm = { path = "../proxy-wasm" } diff --git a/demo/faas/code/rust/client/src/lib.rs b/demo/faas/code/rust/client/src/lib.rs new file mode 100644 index 0000000000..64ea878921 --- /dev/null +++ b/demo/faas/code/rust/client/src/lib.rs @@ -0,0 +1,81 @@ +use std::str; + +use proxy_wasm::{ + dispatcher, get_buffer, invoke_service, log, set_buffer, + traits::{Context, DefaultRootContext, HttpContext, RootContext}, + types::*, +}; + +fn get_query_param(body: &str, param_name: &str) -> Option { + for item in body.split("&") { + let key = format!("{:}=", param_name); + if item.starts_with(&key) { + return Some((&item[key.len()..]).to_owned()); + } + } + None +} + +fn get_book_name(body: &Option) -> Option { + match body { + Some(body) => match str::from_utf8(body) { + Ok(body) => get_query_param(body, "name"), + _ => None, + }, + None => None, + } +} + +#[no_mangle] +pub fn _start() { + dispatcher::set_root_context(|_| -> Box { + Box::new(DefaultRootContext::::default()) + }); +} + +#[no_mangle] +#[allow(unused_must_use)] +pub extern "C" fn proxy_get_id() { + set_buffer(BufferType::BufferTypeCallData, 0, b"id_1"); +} + +#[derive(Default)] +struct ClientHttpContext {} + +impl Context for ClientHttpContext {} + +#[allow(unused_must_use)] +impl HttpContext for ClientHttpContext { + fn on_http_request_body(&mut self, body_size: usize, _end_of_stream: bool) -> Action { + let book_name = get_buffer(BufferType::HttpRequestBody, 0, body_size) + .map_or(None, |buffer| get_book_name(&buffer)); + match book_name { + Some(book_name) => match invoke_service("id_2", "", &book_name) { + Ok(response) => { + let response = response.map_or("".to_string(), |v| { + String::from_utf8(v).unwrap_or("".to_string()) + }); + set_buffer( + BufferType::HttpResponseBody, + 0, + format!("There are {:} inventories for {:}.\n", response, book_name) + .as_bytes(), + ) + .unwrap(); + Action::Continue + } + Err(status) => { + log( + LogLevel::Error, + &format!("Invoke service failed: {}", status as u32), + ); + Action::Pause + } + }, + None => { + log(LogLevel::Error, "Param 'name' not found"); + Action::Pause + } + } + } +} diff --git a/demo/faas/code/rust/lib.rs b/demo/faas/code/rust/lib.rs deleted file mode 100644 index bbff9bb675..0000000000 --- a/demo/faas/code/rust/lib.rs +++ /dev/null @@ -1,225 +0,0 @@ -mod mosn; -mod types; -mod traits; -mod dispatcher; - -use crate::types::*; -use std::ptr::{null, null_mut}; -use protobuf::*; -use std::str; -use crate::traits::{RootContext, Context, HttpContext}; - -#[no_mangle] -pub fn _start() { - dispatcher::set_root_context(|_| -> Box { Box::new(HttpRoot) }); -} - -struct HttpRoot; - -impl Context for HttpRoot {} - -impl RootContext for HttpRoot { - fn create_http_context(&self, context_id: u32) -> Option> { - Some(Box::new(MyHttpContext { context_id })) - } - - fn get_type(&self) -> Option { - Some(ContextType::HttpContext) - } -} - -struct MyHttpContext { - context_id: u32, -} - -impl Context for MyHttpContext {} - -impl HttpContext for MyHttpContext { - fn on_http_request_headers(&mut self, _: usize) -> Action { - log(LogLevel::Info, "rust wasm receive a http request"); - - let name = match self.get_http_request_header("name") { - Some(name) => name, - None => "".to_string(), - }; - - let mut req = mosn::SayHelloRequest::new(); - req.service_name = String::from("helloworld"); - req.name = name; - let data = match req.write_to_bytes() { - Ok(data) => data, - Err(e) => panic!(e), - }; - - let resp = match call_foreign_function("SayHello", Option::Some(data.as_slice())){ - Ok(b) => b.unwrap_or_default(), - Err(e) => panic!(e) - }; - - let response = mosn::SayHelloResponse::parse_from_bytes(resp.as_slice()).expect(""); - set_buffer(BufferType::HttpResponseBody, 0, response.get_hello().as_ref()); - - Action::Continue - } - - -} - -#[no_mangle] -pub extern "C" fn proxy_on_request_trailers(context_id: u32, num_trailers: usize) -> Action { - Action::Continue -} - -extern "C" { - fn proxy_log(level: LogLevel, message_data: *const u8, message_size: usize) -> Status; -} - -pub fn log(level: LogLevel, message: &str) -> Result<(), Status> { - unsafe { - match proxy_log(level, message.as_ptr(), message.len()) { - Status::Ok => Ok(()), - status => panic!("unexpected status: {}", status as u32), - } - } -} - -extern "C" { - fn proxy_call_foreign_function( - function_name: *const u8, - function_name_size: usize, - arguments: *const u8, - arguments_size: usize, - results: *mut *mut u8, - results_size: *mut usize, - ) -> Status; -} - -pub fn call_foreign_function( - function_name: &str, - arguments: Option<&[u8]>, -) -> Result, Status> { - let mut return_data: *mut u8 = null_mut(); - let mut return_size: usize = 0; - unsafe { - match proxy_call_foreign_function( - function_name.as_ptr(), - function_name.len(), - arguments.map_or(null(), |arguments| arguments.as_ptr()), - arguments.map_or(0, |arguments| arguments.len()), - &mut return_data, - &mut return_size, - ) { - Status::Ok => { - if !return_data.is_null() { - Ok(Some(Vec::from_raw_parts( - return_data, - return_size, - return_size, - ))) - } else { - Ok(None) - } - } - Status::NotFound => Ok(None), - status => panic!("unexpected status: {}", status as u32), - } - } -} - -#[cfg_attr( -all(target_arch = "wasm32", target_os = "unknown"), -export_name = "malloc" -)] -#[no_mangle] -pub extern "C" fn proxy_on_memory_allocate(size: usize) -> *mut u8 { - let mut vec: Vec = Vec::with_capacity(size); - unsafe { - vec.set_len(size); - } - let slice = vec.into_boxed_slice(); - Box::into_raw(slice) as *mut u8 -} - -extern "C" { - fn proxy_get_header_map_value( - map_type: MapType, - key_data: *const u8, - key_size: usize, - return_value_data: *mut *mut u8, - return_value_size: *mut usize, - ) -> Status; -} - -pub fn get_map_value(map_type: MapType, key: &str) -> Result, Status> { - let mut return_data: *mut u8 = null_mut(); - let mut return_size: usize = 0; - unsafe { - match proxy_get_header_map_value( - map_type, - key.as_ptr(), - key.len(), - &mut return_data, - &mut return_size, - ) { - Status::Ok => { - if !return_data.is_null() { - Ok(Some( - String::from_utf8(Vec::from_raw_parts( - return_data, - return_size, - return_size, - )) - .unwrap(), - )) - } else { - Ok(None) - } - } - status => panic!("unexpected status: {}", status as u32), - } - } -} - -extern "C" { - fn proxy_set_buffer_bytes( - buffer_type: BufferType, - start: usize, - size: usize, - buffer_data: *const u8, - buffer_size: usize, - ) -> Status; -} - -pub fn set_buffer( - buffer_type: BufferType, - start: usize, - value: &[u8], -) -> Result<(), Status> { - unsafe { - match proxy_set_buffer_bytes(buffer_type, start, value.len(), value.as_ptr(), value.len()) { - Status::Ok => Ok(()), - status => panic!("unexpected status: {}", status as u32), - } - } -} - -#[no_mangle] -pub extern "C" fn proxy_abi_version_0_2_0() {} - -#[no_mangle] -pub extern "C" fn proxy_on_vm_start(context_id: u32, vm_configuration_size: usize) -> bool { - true -} - -#[no_mangle] -pub extern "C" fn proxy_on_configure(context_id: u32, plugin_configuration_size: usize) -> bool { - true -} - -#[no_mangle] -pub extern "C" fn proxy_on_done(context_id: u32) -> bool { - true -} - -#[no_mangle] -pub extern "C" fn proxy_on_delete(context_id: u32) {} \ No newline at end of file diff --git a/demo/faas/code/rust/proxy-wasm/Cargo.toml b/demo/faas/code/rust/proxy-wasm/Cargo.toml new file mode 100644 index 0000000000..1f83d64c18 --- /dev/null +++ b/demo/faas/code/rust/proxy-wasm/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "proxy-wasm" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] \ No newline at end of file diff --git a/demo/faas/code/rust/dispatcher.rs b/demo/faas/code/rust/proxy-wasm/src/dispatcher.rs similarity index 81% rename from demo/faas/code/rust/dispatcher.rs rename to demo/faas/code/rust/proxy-wasm/src/dispatcher.rs index 14bbc78149..1e7e8020a5 100644 --- a/demo/faas/code/rust/dispatcher.rs +++ b/demo/faas/code/rust/proxy-wasm/src/dispatcher.rs @@ -1,18 +1,22 @@ -use std::cell::{Cell, RefCell}; -use std::collections::HashMap; -use crate::types::{NewRootContext, NewHttpContext, Action, ContextType}; -use crate::traits::{RootContext, HttpContext}; -use crate::dispatcher; +use std::{ + cell::{Cell, RefCell}, + collections::HashMap, +}; + +use crate::{ + traits::{HttpContext, RootContext}, + types::{Action, ContextType, NewHttpContext, NewRootContext}, +}; thread_local! { -static DISPATCHER: Dispatcher = Dispatcher::new(); + static DISPATCHER: Dispatcher = Dispatcher::new(); } -pub(crate) fn set_root_context(callback: NewRootContext) { +pub fn set_root_context(callback: NewRootContext) { DISPATCHER.with(|dispatcher| dispatcher.set_root_context(callback)); } -pub(crate) fn set_http_context(callback: NewHttpContext) { +pub fn set_http_context(callback: NewHttpContext) { DISPATCHER.with(|dispatcher| dispatcher.set_http_context(callback)); } @@ -24,7 +28,6 @@ struct Dispatcher { } impl Dispatcher { - fn new() -> Dispatcher { Dispatcher { new_root: Cell::new(None), @@ -116,16 +119,26 @@ impl Dispatcher { } #[no_mangle] -pub extern "C" fn proxy_on_request_headers(context_id: u32, num_headers: usize, end_of_stream: usize) -> Action { - return DISPATCHER.with(|dispatcher| dispatcher.on_http_request_headers(context_id, num_headers)); +pub extern "C" fn proxy_on_request_headers( + context_id: u32, + num_headers: usize, + _end_of_stream: usize, +) -> Action { + return DISPATCHER + .with(|dispatcher| dispatcher.on_http_request_headers(context_id, num_headers)); } #[no_mangle] -pub extern "C" fn proxy_on_request_body(context_id: u32, body_size: usize, end_of_stream: bool) -> Action { - DISPATCHER.with(|dispatcher| dispatcher.on_http_request_body(context_id, body_size, end_of_stream)) +pub extern "C" fn proxy_on_request_body( + context_id: u32, + body_size: usize, + end_of_stream: bool, +) -> Action { + DISPATCHER + .with(|dispatcher| dispatcher.on_http_request_body(context_id, body_size, end_of_stream)) } #[no_mangle] pub extern "C" fn proxy_on_context_create(context_id: u32, root_context_id: u32) { DISPATCHER.with(|dispatcher| dispatcher.on_create_context(context_id, root_context_id)) -} \ No newline at end of file +} diff --git a/demo/faas/code/rust/proxy-wasm/src/lib.rs b/demo/faas/code/rust/proxy-wasm/src/lib.rs new file mode 100644 index 0000000000..017e2786a8 --- /dev/null +++ b/demo/faas/code/rust/proxy-wasm/src/lib.rs @@ -0,0 +1,183 @@ +pub mod dispatcher; +pub mod traits; +pub mod types; + +use std::{ptr::null_mut, str}; + +use crate::types::*; + +unsafe fn parse_proxy_status( + data: *mut u8, + data_size: usize, + status: Status, +) -> Result, Status> { + match status { + Status::Ok => { + if data.is_null() { + Ok(None) + } else { + Ok(Some(Vec::from_raw_parts(data, data_size, data_size))) + } + } + v => Err(v), + } +} + +extern "C" { + fn proxy_log(level: LogLevel, message_data: *const u8, message_size: usize) -> Status; +} + +pub fn log(level: LogLevel, message: &str) -> Result<(), Status> { + unsafe { + match proxy_log(level, message.as_ptr(), message.len()) { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_invoke_service( + id_ptr: *const u8, + id_size: usize, + method_ptr: *const u8, + method_size: usize, + param_ptr: *const u8, + param_size: usize, + result_ptr: *mut *mut u8, + result_size: *mut usize, + ) -> Status; +} + +pub fn invoke_service(id: &str, method: &str, param: &str) -> Result, Status> { + let mut return_data: *mut u8 = null_mut(); + let mut return_size: usize = 0; + unsafe { + let status = proxy_invoke_service( + id.as_ptr(), + id.len(), + method.as_ptr(), + method.len(), + param.as_ptr(), + param.len(), + &mut return_data, + &mut return_size, + ); + parse_proxy_status(return_data, return_size, status) + } +} + +extern "C" { + fn proxy_set_buffer_bytes( + buffer_type: BufferType, + start: usize, + size: usize, + buffer_data: *const u8, + buffer_size: usize, + ) -> Status; +} + +pub fn set_buffer(buffer_type: BufferType, start: usize, value: &[u8]) -> Result<(), Status> { + unsafe { + match proxy_set_buffer_bytes(buffer_type, start, value.len(), value.as_ptr(), value.len()) { + Status::Ok => Ok(()), + v => panic!("Unexpected status: {}", v as u32), + } + } +} + +extern "C" { + fn proxy_get_buffer_bytes( + buffer_type: BufferType, + start: usize, + max_size: usize, + return_buffer_data: *mut *mut u8, + return_buffer_size: *mut usize, + ) -> Status; +} + +pub fn get_buffer( + buffer_type: BufferType, + start: usize, + max_size: usize, +) -> Result, Status> { + let mut return_data: *mut u8 = null_mut(); + let mut return_size: usize = 0; + unsafe { + let status = proxy_get_buffer_bytes( + buffer_type, + start, + max_size, + &mut return_data, + &mut return_size, + ); + parse_proxy_status(return_data, return_size, status) + } +} + +extern "C" { + fn proxy_get_state( + store_name_ptr: *const u8, + store_name_size: usize, + key_ptr: *const u8, + key_size: usize, + result_ptr: *mut *mut u8, + result_size: *mut usize, + ) -> Status; +} + +pub fn get_state(store_name: &str, key: &str) -> Result, Status> { + let mut return_data: *mut u8 = null_mut(); + let mut return_size: usize = 0; + unsafe { + let status = proxy_get_state( + store_name.as_ptr(), + store_name.len(), + key.as_ptr(), + key.len(), + &mut return_data, + &mut return_size, + ); + parse_proxy_status(return_data, return_size, status) + } +} + +#[cfg_attr( + all(target_arch = "wasm32", target_os = "unknown"), + export_name = "malloc" +)] +#[no_mangle] +pub extern "C" fn proxy_on_memory_allocate(size: usize) -> *mut u8 { + let mut vec: Vec = Vec::with_capacity(size); + unsafe { + vec.set_len(size); + } + let slice = vec.into_boxed_slice(); + Box::into_raw(slice) as *mut u8 +} + +#[no_mangle] +pub extern "C" fn proxy_on_request_trailers(_context_id: u32, _num_trailers: usize) -> Action { + Action::Continue +} + +#[no_mangle] +pub extern "C" fn proxy_abi_version_0_2_0() {} + +#[no_mangle] +pub extern "C" fn proxy_on_vm_start(_context_id: u32, _vm_configuration_size: usize) -> bool { + true +} + +#[no_mangle] +pub extern "C" fn proxy_on_configure(_context_id: u32, _plugin_configuration_size: usize) -> bool { + true +} + +#[no_mangle] +pub extern "C" fn proxy_on_done(_context_id: u32) -> bool { + true +} + +#[no_mangle] +pub extern "C" fn proxy_on_delete(_context_id: u32) {} diff --git a/demo/faas/code/rust/traits.rs b/demo/faas/code/rust/proxy-wasm/src/traits.rs similarity index 54% rename from demo/faas/code/rust/traits.rs rename to demo/faas/code/rust/proxy-wasm/src/traits.rs index 61a479b845..b4f206aace 100644 --- a/demo/faas/code/rust/traits.rs +++ b/demo/faas/code/rust/proxy-wasm/src/traits.rs @@ -1,9 +1,8 @@ -use crate::types::*; -use crate::get_map_value; +use std::marker::PhantomData; -pub trait Context { +use crate::types::*; -} +pub trait Context {} pub trait RootContext: Context { fn on_vm_start(&mut self, _vm_configuration_size: usize) -> bool { @@ -27,8 +26,22 @@ pub trait HttpContext: Context { fn on_http_request_body(&mut self, _body_size: usize, _end_of_stream: bool) -> Action { Action::Continue } +} + +#[derive(Default)] +pub struct DefaultRootContext(PhantomData); + +impl Context for DefaultRootContext where T: HttpContext {} - fn get_http_request_header(&self, name: &str) -> Option { - get_map_value(MapType::HttpRequestHeaders, &name).unwrap() +impl RootContext for DefaultRootContext +where + T: HttpContext + Default + 'static, +{ + fn create_http_context(&self, _context_id: u32) -> Option> { + Some(Box::new(T::default())) } -} \ No newline at end of file + + fn get_type(&self) -> Option { + Some(ContextType::HttpContext) + } +} diff --git a/demo/faas/code/rust/types.rs b/demo/faas/code/rust/proxy-wasm/src/types.rs similarity index 80% rename from demo/faas/code/rust/types.rs rename to demo/faas/code/rust/proxy-wasm/src/types.rs index 1690612075..ff2a1ed57b 100644 --- a/demo/faas/code/rust/types.rs +++ b/demo/faas/code/rust/proxy-wasm/src/types.rs @@ -8,7 +8,12 @@ pub type Bytes = Vec; #[repr(u32)] #[derive(Debug)] pub enum LogLevel { + Trace = 0, + Debug = 1, Info = 2, + Warn = 3, + Error = 4, + Critical = 5, } #[repr(u32)] @@ -18,16 +23,11 @@ pub enum Status { NotFound = 1, } -#[repr(u32)] -#[derive(Debug)] -pub enum MapType { - HttpRequestHeaders = 0, -} - #[repr(u32)] #[derive(Debug)] pub enum Action { Continue = 0, + Pause = 1, } #[repr(u32)] @@ -39,5 +39,7 @@ pub enum ContextType { #[repr(u32)] #[derive(Debug)] pub enum BufferType { + HttpRequestBody = 0, HttpResponseBody = 1, + BufferTypeCallData = 8, } diff --git a/demo/faas/code/rust/server/Cargo.toml b/demo/faas/code/rust/server/Cargo.toml new file mode 100644 index 0000000000..b27f62413a --- /dev/null +++ b/demo/faas/code/rust/server/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "server" +version = "0.1.0" +edition = "2021" + +[lib] +name = "function_2" +crate-type = ["cdylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +proxy-wasm = { path = "../proxy-wasm" } diff --git a/demo/faas/code/rust/server/src/lib.rs b/demo/faas/code/rust/server/src/lib.rs new file mode 100644 index 0000000000..3f0de58170 --- /dev/null +++ b/demo/faas/code/rust/server/src/lib.rs @@ -0,0 +1,57 @@ +use proxy_wasm::{ + dispatcher, get_buffer, get_state, log, set_buffer, + traits::{Context, DefaultRootContext, HttpContext, RootContext}, + types::*, +}; + +#[no_mangle] +pub fn _start() { + dispatcher::set_root_context(|_| -> Box { + Box::new(DefaultRootContext::::default()) + }); +} + +#[no_mangle] +#[allow(unused_must_use)] +pub extern "C" fn proxy_get_id() { + set_buffer(BufferType::BufferTypeCallData, 0, b"id_2"); +} + +#[derive(Default)] +struct ServerHttpContext {} + +impl Context for ServerHttpContext {} + +#[allow(unused_must_use)] +impl HttpContext for ServerHttpContext { + fn on_http_request_body(&mut self, body_size: usize, _end_of_stream: bool) -> Action { + let book_name: Option = get_buffer(BufferType::HttpRequestBody, 0, body_size) + .map_or(None, |buffer| match buffer { + Some(buffer) => match String::from_utf8(buffer) { + Ok(v) => Some(v), + _ => None, + }, + None => None, + }); + match book_name { + Some(book_name) => match get_state("redis", &book_name) { + Ok(response) => { + let response = response.unwrap_or(vec![]); + set_buffer(BufferType::HttpResponseBody, 0, &response).unwrap(); + Action::Continue + } + Err(status) => { + log( + LogLevel::Error, + &format!("Get State failed: {}", status as u32), + ); + Action::Pause + } + }, + None => { + log(LogLevel::Error, "Param 'name' not found"); + Action::Pause + } + } + } +}