From 5e16f6d4420d58acdce88a7e9ff33d1f2b5b9f11 Mon Sep 17 00:00:00 2001 From: Knarkzel Date: Sun, 22 Jan 2023 13:01:23 +0100 Subject: [PATCH] Add http example --- Cargo.lock | 17 +++++ Cargo.toml | 25 +++---- examples/http_dynamic_size.rs | 122 ++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 16 deletions(-) create mode 100644 examples/http_dynamic_size.rs diff --git a/Cargo.lock b/Cargo.lock index fc933c28285..e5e14764bc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3966,6 +3966,22 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "ureq" +version = "2.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls 0.20.7", + "url", + "webpki 0.22.0", + "webpki-roots 0.22.6", +] + [[package]] name = "url" version = "2.3.1" @@ -4879,6 +4895,7 @@ dependencies = [ "test-log", "tracing", "tracing-subscriber", + "ureq", "wasi-test-generator", "wasmer", "wasmer-cache", diff --git a/Cargo.toml b/Cargo.toml index 25dbdf20a83..59582163b36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ lazy_static = "1.4" serial_test = "0.5" compiler-test-derive = { path = "tests/lib/compiler-test-derive" } tempfile = "3.1" +ureq = "2.6" # For logging tests using the `RUST_LOG=debug` when testing test-log = { version = "0.2", default-features = false, features = ["trace"] } tracing = { version = "0.1", default-features = false, features = ["log"] } @@ -87,15 +88,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [ [features] # Don't add the compiler features in default, please add them on the Makefile # since we might want to autoconfigure them depending on the availability on the host. -default = [ - "wat", - "wast", - "cache", - "wasi", - "engine", - "emscripten", - "middlewares", -] +default = ["wat", "wast", "cache", "wasi", "engine", "emscripten", "middlewares"] engine = ["universal"] universal = [] cache = ["wasmer-cache"] @@ -103,10 +96,7 @@ wast = ["wasmer-wast"] wasi = ["wasmer-wasi"] emscripten = ["wasmer-emscripten"] wat = ["wasmer/wat"] -compiler = [ - "wasmer/compiler", - "wasmer-compiler/translator", -] +compiler = ["wasmer/compiler", "wasmer-compiler/translator"] singlepass = ["wasmer-compiler-singlepass", "compiler"] cranelift = ["wasmer-compiler-cranelift", "compiler"] llvm = ["wasmer-compiler-llvm", "compiler"] @@ -121,9 +111,7 @@ test-singlepass = ["singlepass"] test-cranelift = ["cranelift"] test-llvm = ["llvm"] -test-universal = [ - "test-generator/test-universal", -] +test-universal = ["test-generator/test-universal"] # Specifies that we're running in coverage testing mode. This disables tests # that raise signals because that interferes with tarpaulin. @@ -260,3 +248,8 @@ required-features = ["cranelift"] name = "features" path = "examples/features.rs" required-features = ["cranelift"] + +[[example]] +name = "http-dynamic-size" +path = "examples/http_dynamic_size.rs" +required-features = ["cranelift"] diff --git a/examples/http_dynamic_size.rs b/examples/http_dynamic_size.rs new file mode 100644 index 00000000000..fda8ec66f95 --- /dev/null +++ b/examples/http_dynamic_size.rs @@ -0,0 +1,122 @@ +use anyhow::Result; +use wasmer::{wat2wasm, imports, Instance, Module, Store, Memory, AsStoreRef, MemoryView, FunctionEnvMut, WasmPtr, FunctionEnv, Function}; + +// Utils +pub fn read_string(view: &MemoryView, start: u32, len: u32) -> Result { + Ok(WasmPtr::::new(start).read_utf8_string(view, len)?) +} + +// Environment +pub struct ExampleEnv { + memory: Option, +} + +impl ExampleEnv { + fn set_memory(&mut self, memory: Memory) { + self.memory = Some(memory); + } + + fn get_memory(&self) -> &Memory { + self.memory.as_ref().unwrap() + } + + fn view<'a>(&'a self, store: &'a impl AsStoreRef) -> MemoryView<'a> { + self.get_memory().view(store) + } +} + +fn http_get(mut ctx: FunctionEnvMut, url: u32, url_len: u32) -> u32 { + // Setup environment + let (response, memory_size) = { + // Read url from memory + let view = ctx.data().view(&ctx); + let memory_size = view.data_size() as usize; + let address = read_string(&view, url, url_len).unwrap(); + + // Get request + let response = ureq::get(&address).call().unwrap(); + let capacity = match response.header("Content-Length").map(|it| it.parse::()) { + Some(Ok(len)) => len, + _ => 1024, + }; + let mut buffer = Vec::with_capacity(capacity); + let mut reader = response.into_reader(); + reader.read_to_end(&mut buffer).unwrap(); + (buffer, memory_size) + }; + + // If the response is too big, grow memory + if 1114112 + response.len() > memory_size { + let delta = (1114112 + response.len() - memory_size) / wasmer::WASM_PAGE_SIZE + 1; + let memory = ctx.data().get_memory().clone(); + memory.grow(&mut ctx, delta as u32).unwrap(); + } + + // Write response as string [ptr, cap, len] to wasm memory and return pointer + let view = ctx.data().view(&ctx); + view.write(1114112, &u32::to_le_bytes(1114124)).unwrap(); + view.write(1114116, &u32::to_le_bytes(response.len() as u32)).unwrap(); + view.write(1114120, &u32::to_le_bytes(response.len() as u32)).unwrap(); + view.write(1114124, &response).unwrap(); + 1114112 +} + +fn main() -> Result<()> { + let wasm_bytes = wat2wasm( + br#" +(module + (type (;0;) (func (param i32 i32) (result i32))) + (type (;1;) (func (result i32))) + (type (;2;) (func)) + (import "env" "http_get" (func (;0;) (type 0))) + (func (;1;) (type 1) (result i32) + i32.const 1048576 + i32.const 45 + call 0 + i32.const 8 + i32.add + i32.load) + (func (;2;) (type 2)) + (func (;3;) (type 2) + call 2 + call 2) + (func (;4;) (type 1) (result i32) + call 1 + call 3) + (table (;0;) 1 1 funcref) + (memory (;0;) 17) + (global (;0;) (mut i32) (i32.const 1048576)) + (export "memory" (memory 0)) + (export "fetch" (func 4)) + (data (;0;) (i32.const 1048576) "https://postman-echo.com/bytes/5/mb?type=json")) +"#, + )?; + + // Load module + let mut store = Store::default(); + let module = Module::new(&store, wasm_bytes)?; + + // Add host functions + let function_env = FunctionEnv::new(&mut store, ExampleEnv { memory: None }); + let import_object = imports! { + // We use the default namespace "env". + "env" => { + // And call our function "http_get". + "http_get" => Function::new_typed_with_env(&mut store, &function_env, http_get), + } + }; + + // Create instance + let instance = Instance::new(&mut store, &module, &import_object)?; + let memory = instance.exports.get_memory("memory")?; + + // Give reference to memory + function_env.as_mut(&mut store).set_memory(memory.clone()); + + // Call function + let fetch = instance.exports.get_function("fetch")?; + let result = fetch.call(&mut store, &[])?; + println!("Response size: {result:?}"); + + Ok(()) +}