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}