Skip to main content

wasm_executor/
lib.rs

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