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;
}