1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
use futures::future::FutureResult; use hyper::server::{Http, Request, Response, Service}; use hyper::{header::Headers, Body, Get, Method, Post, StatusCode}; use hyper::header::ContentLength; use std::{env, fs::File, io::Read}; use http::HeaderMap; use wasmtime::{Engine, Store, Val, Module, Instance, Trap}; use cloudevents::v02::CloudEvent; pub trait RequestExtractor { fn extract_args(&self, context: &Context) -> Vec<Val>; } pub struct WasmResponse { pub body: Vec<u8>, pub headers: Option<HeaderMap>, } pub trait ResponseHandler { fn create_response( &self, context: &Context, result: Result<Box<[Val]>, Trap> ) -> WasmResponse; } pub struct WasmExecutor { function_name: String, module_path: String, request_handler: Box<dyn RequestExtractor>, response_handler: Box<dyn ResponseHandler>, module: Module, } impl WasmExecutor { fn new( function_name: String, module_path: String, request_handler: Box<dyn RequestExtractor>, response_handler: Box<dyn ResponseHandler>, module: Module, ) -> WasmExecutor { WasmExecutor { function_name, module_path, request_handler, response_handler, module, } } } #[derive(Debug)] pub struct Context<'a> { pub module_path: String, pub function_name: String, pub user: String, pub method: Method, pub headers: Headers, pub path: String, pub query: Option<&'a str>, pub body: Option<&'a Body>, pub cloudevent: Option<CloudEvent>, } impl Service for WasmExecutor { type Request = Request; type Response = Response; type Error = hyper::Error; type Future = FutureResult<Response, hyper::Error>; fn call(&self, req: Request) -> Self::Future { futures::future::ok(match req.method() { &Get | &Post => { let instance = Instance::new(&self.module, &[]).unwrap(); let func = instance.get_export(&self.function_name).unwrap().func().unwrap(); let context = create_context(&req, &self); let args = self.request_handler.extract_args(&context); let result = func.call(&args); let wasm_response = self.response_handler.create_response( &context, result); Response::new() .with_header(ContentLength(wasm_response.body.len() as u64)) .with_body(wasm_response.body) } _ => Response::new().with_status(StatusCode::NotFound), }) } } fn create_context<'a>(req: &'a Request, executor: &WasmExecutor) -> Context<'a> { Context { module_path: executor.module_path.clone(), function_name: executor.function_name.clone(), user: String::from(""), method: req.method().clone(), headers: req.headers().clone(), path: req.path().to_string(), query: req.query(), body: req.body_ref(), cloudevent: None, } } pub fn start( req_handler: fn() -> Box<dyn RequestExtractor>, res_handler: fn() -> Box<dyn ResponseHandler>, ) { let port = env::var("PORT").expect("PORT environment variable not set"); let addr_port = format!("0.0.0.0:{}", port); let addr = addr_port.parse().unwrap(); let function_name = env::var("FUNCTION_NAME").expect("FUNCTION_NAME environment variable not set"); let module_path = env::var("MODULE_PATH").expect("MODULE_PATH environment variable not set"); println!( "WASI Runtime started. Port: {}, Module path: {}", port, module_path ); let binary: Vec<u8> = read_module(&module_path); let store = Store::new(&Engine::default()); let module = Module::new_with_name(&store, binary, &module_path).unwrap(); let server = Http::new() .bind(&addr, move || { Ok(WasmExecutor::new( function_name.clone(), module_path.clone(), req_handler(), res_handler(), module.clone(), )) }) .unwrap(); server.run().unwrap(); } pub fn read_module(module_path: &str) -> Vec<u8> { let mut module_file = File::open(module_path).expect("wasm not found"); let mut binary: Vec<u8> = Vec::new(); module_file.read_to_end(&mut binary).unwrap(); return binary; }