From efab2bd99c6b0f882cb5e5d53379f673b2430320 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 20 Oct 2022 17:03:16 -0700 Subject: [PATCH] wasm-compose: simplify the example. This commit simplifies the example by removing an unnecessary service layer from the compose component. Now the example either directly talks to a "service" component or composes a new service component that injects a "middleware" component into the execution graph. It also updates the server in the example to Wasmtime 2.0.0. --- crates/wasm-compose/example/README.md | 102 +++++------------- .../example/backend/.vscode/settings.json | 3 - .../wasm-compose/example/backend/Cargo.toml | 19 ---- .../wasm-compose/example/backend/src/lib.rs | 30 ------ crates/wasm-compose/example/server/Cargo.toml | 2 +- crates/wasm-compose/example/server/config.yml | 12 +-- .../wasm-compose/example/server/src/main.rs | 5 +- .../wasm-compose/example/service/.gitignore | 1 - .../wasm-compose/example/service/Cargo.toml | 3 - .../wasm-compose/example/service/src/lib.rs | 34 +++--- 10 files changed, 52 insertions(+), 159 deletions(-) delete mode 100644 crates/wasm-compose/example/backend/.vscode/settings.json delete mode 100644 crates/wasm-compose/example/backend/Cargo.toml delete mode 100644 crates/wasm-compose/example/backend/src/lib.rs delete mode 100644 crates/wasm-compose/example/service/.gitignore diff --git a/crates/wasm-compose/example/README.md b/crates/wasm-compose/example/README.md index de5a79be49..fc7b67a2da 100644 --- a/crates/wasm-compose/example/README.md +++ b/crates/wasm-compose/example/README.md @@ -5,21 +5,18 @@ to compose a component from other components. ## Directory layout -There are four subdirectories in this example: +There are three subdirectories in this example: -* `backend` - a simple HTTP backend component that responds with the original - request body. -* `middleware` - a middleware component that gzip compresses response bodies. -* `service` - a service component that is configured with a backend component - to send requests to. -* `server` - a custom HTTP server that instantiates a composed `service` - component for each HTTP request. +* `service` - a service component that responds with the original request body. +* `middleware` - a middleware component that compresses response bodies. +* `server` - a custom HTTP server that instantiates a service component for + each HTTP request. ## Overview The server will listen for `POST` requests at `http://localhost:8080`. -When it receives a request, the server will instantiate the `service` component +When it receives a request, the server will instantiate a service component and forward it the request. Each component implements a `service` interface defined in `service.wit` as: @@ -50,18 +47,15 @@ HTTP request processing. ### Execution flow -The example will compose a `service` component that initially executes like +The example implements a `service` component that initially executes like this: ```mermaid sequenceDiagram participant server (native) participant service (wasm) - participant backend (wasm) server (native)->>service (wasm): execute() - service (wasm)->>backend (wasm): execute() - Note right of backend (wasm): echo request body - backend (wasm)->>service (wasm): uncompressed response + Note right of service (wasm): echo request body service (wasm)->>server (native): uncompressed response ``` @@ -72,16 +66,13 @@ altering the execution flow to this: ```mermaid sequenceDiagram participant server (native) - participant service (wasm) participant middleware (wasm) - participant backend (wasm) - server (native)->>service (wasm): execute() - service (wasm)->>middleware (wasm): execute() - middleware (wasm)->>backend (wasm): execute() - Note right of backend (wasm): echo request body - backend (wasm)->>middleware (wasm): uncompressed response - middleware (wasm)->>service (wasm): compressed response - service (wasm)->>server (native): compressed response + participant service (wasm) + server (native)->>middleware (wasm): execute() + middleware (wasm)->>service (wasm): execute() + Note right of service (wasm): echo request body + service (wasm)->>middleware (wasm): uncompressed response + middleware (wasm)->>server (native): compressed response ``` All this without having to rebuild any of the original components! @@ -98,10 +89,10 @@ root of this repository. ## Building the components -To build the `backend` component, use `cargo component build`: +To build the `service` component, use `cargo component build`: ```sh -cd backend +cd service cargo component build --release ``` @@ -112,41 +103,16 @@ cd middleware cargo component build --release ``` -Finally, to build the `service` component, use `cargo component build`: - -```sh -cd service -cargo component build --release -``` - -## Composing the `service` component - -Initially, we will compose a service component that directly sends requests -to the backend service. - -The `server/config.yml` configuration file instructs `wasm-compose` to -search for dependencies from the expected output paths of the components we -built previously. - -Based on this, `wasm-compose` will automatically satisfy the `backend` -dependency of the `service` component with the `backend` component. - -To compose the `service` component, run `wasm-tools compose`: - -```sh -cd server -wasm-tools compose -c config.yml -o service.wasm ../service/target/wasm32-unknown-unknown/release/svc.wasm -``` - -There should now be a `service.wasm` in the `server` directory. - ## Running the server +Initially, we will run the server with the `service` component that responds +with an uncompressed response body. + The server can be run with `cargo run`: ```sh cd server -cargo run --release -- service.wasm +cargo run --release -- ../service/target/wasm32-unknown-unknown/release/svc.wasm ``` This will start a HTTP server that listens at `http://localhost:8080`. @@ -186,34 +152,20 @@ The request body was: Hello, world! Note that the response body matches the request body, but it was not compressed. -## Changing the composition +## Composing with a middleware If we want to instead compress the response bodies for the service, we can easily -change the composition to send requests through the `middleware` component +compose a new component that sends requests through the `middleware` component without rebuilding any of the previously built components. -To change how the `service` component is composed, edit `server/config.yml` -and uncomment the specified lines: - -```yml -instantiations: - $component: - arguments: - backend: middleware -``` - -This provides an explicit dependency of `middleware` for the `backend` argument -of the composed component. - -`wasm-compose` will therefore use the `middleware` component to satisfy the -dependency; the `backend` dependency of the `middleware` component will automatically -be satisfied by the `backend` component. +The `server/config.yml` file contains the configuration needed to compose a new +component from the `service` and `middleware` components. -And run `wasm-compose` again: +Run `wasm-compose` to compose the new component: ```sh cd server -wasm-tools compose -c config.yml -o service.wasm ../service/target/wasm32-unknown-unknown/release/svc.wasm +wasm-tools compose -c config.yml -o service.wasm ../middleware/target/wasm32-unknown-unknown/release/middleware.wasm ``` This results in a new `service.wasm` in the `server` directory where the @@ -223,7 +175,7 @@ This results in a new `service.wasm` in the `server` directory where the If you haven't already, stop the currently running server by pressing `ctrl-c`. -Start the server again with `cargo run`: +Start the server again with `cargo run` with the newly composed component: ```sh cd server diff --git a/crates/wasm-compose/example/backend/.vscode/settings.json b/crates/wasm-compose/example/backend/.vscode/settings.json deleted file mode 100644 index 09b1909af8..0000000000 --- a/crates/wasm-compose/example/backend/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.server.extraEnv": { "CARGO": "cargo-component" } -} diff --git a/crates/wasm-compose/example/backend/Cargo.toml b/crates/wasm-compose/example/backend/Cargo.toml deleted file mode 100644 index 2b522c4f85..0000000000 --- a/crates/wasm-compose/example/backend/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "backend" -version = "0.1.0" -edition = "2021" -publish = false - -[dependencies] -wit-bindgen-guest-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", default_features = false } - -[lib] -crate-type = ["cdylib"] - -[package.metadata.component] -direct-interface-export = "service" - -[package.metadata.component.exports] -service = "../service.wit" - -[workspace] diff --git a/crates/wasm-compose/example/backend/src/lib.rs b/crates/wasm-compose/example/backend/src/lib.rs deleted file mode 100644 index 76c33575dd..0000000000 --- a/crates/wasm-compose/example/backend/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -use service::{Error, Request, Response, Service}; -use std::str; - -struct Component; - -impl Service for Component { - fn execute(req: Request) -> Result { - // The content should be plain text - let content_type = req - .headers - .iter() - .find(|(k, _)| k == b"content-type") - .map(|(_, v)| v) - .ok_or_else(|| Error::BadRequest)?; - if content_type != b"text/plain" { - return Err(Error::BadRequest); - } - - // We assume the body is UTF-8 encoded - let body = str::from_utf8(&req.body).map_err(|_| Error::BadRequest)?; - - // Echo the body back in the response - Ok(Response { - headers: vec![(b"content-type".to_vec(), b"text/plain".to_vec())], - body: format!("The request body was: {body}").into_bytes(), - }) - } -} - -service::export!(Component); diff --git a/crates/wasm-compose/example/server/Cargo.toml b/crates/wasm-compose/example/server/Cargo.toml index 113953e036..1cb21fa030 100644 --- a/crates/wasm-compose/example/server/Cargo.toml +++ b/crates/wasm-compose/example/server/Cargo.toml @@ -9,6 +9,6 @@ async-std = { version = "1.12.0", features = ["attributes"] } clap = { version = "3.2.16", features = ["derive"] } driftwood = "0.0.6" tide = "0.16.0" -wasmtime = { features = ["component-model"], git = "https://github.com/bytecodealliance/wasmtime", rev = "e45577e097b064797def3554468cffa5fdd443d3" } +wasmtime = { version = "2.0.0", features = ["component-model"] } [workspace] diff --git a/crates/wasm-compose/example/server/config.yml b/crates/wasm-compose/example/server/config.yml index c0142d16b6..22a80cb67a 100644 --- a/crates/wasm-compose/example/server/config.yml +++ b/crates/wasm-compose/example/server/config.yml @@ -1,9 +1,7 @@ search-paths: - - ../backend/target/wasm32-unknown-unknown/release - - ../middleware/target/wasm32-unknown-unknown/release + - ../service/target/wasm32-unknown-unknown/release -# Remove the comments below to compose with the middleware component -# instantiations: -# $component: -# arguments: -# backend: middleware +instantiations: + $component: + arguments: + backend: svc diff --git a/crates/wasm-compose/example/server/src/main.rs b/crates/wasm-compose/example/server/src/main.rs index 633b5c4174..7dab651a69 100644 --- a/crates/wasm-compose/example/server/src/main.rs +++ b/crates/wasm-compose/example/server/src/main.rs @@ -154,12 +154,13 @@ impl ServerApp { // Instantiate the service component and get its `execute` export let instance = linker.instantiate(&mut store, &state.component)?; let execute = instance - .get_typed_func::<(ServiceRequest,), (ServiceResult,), _ >(&mut store, "execute")?; + .get_typed_func::<(ServiceRequest,), (ServiceResult,), _>(&mut store, "execute")?; // Call the `execute` export with the request and translate the response execute .call(&mut store, (ServiceRequest::new(req).await?,))? - .0.map_err(Into::into) + .0 + .map_err(Into::into) .and_then(TryInto::try_into) } } diff --git a/crates/wasm-compose/example/service/.gitignore b/crates/wasm-compose/example/service/.gitignore deleted file mode 100644 index af4ffc84cf..0000000000 --- a/crates/wasm-compose/example/service/.gitignore +++ /dev/null @@ -1 +0,0 @@ -composed.wasm diff --git a/crates/wasm-compose/example/service/Cargo.toml b/crates/wasm-compose/example/service/Cargo.toml index 9085a408bd..7b55a82732 100644 --- a/crates/wasm-compose/example/service/Cargo.toml +++ b/crates/wasm-compose/example/service/Cargo.toml @@ -13,9 +13,6 @@ crate-type = ["cdylib"] [package.metadata.component] direct-interface-export = "service" -[package.metadata.component.imports] -backend = "../service.wit" - [package.metadata.component.exports] service = "../service.wit" diff --git a/crates/wasm-compose/example/service/src/lib.rs b/crates/wasm-compose/example/service/src/lib.rs index 51fa032e28..76c33575dd 100644 --- a/crates/wasm-compose/example/service/src/lib.rs +++ b/crates/wasm-compose/example/service/src/lib.rs @@ -1,30 +1,28 @@ use service::{Error, Request, Response, Service}; +use std::str; struct Component; impl Service for Component { fn execute(req: Request) -> Result { - // Right now, generated types aren't shared for bindings, so we - // have to manually convert between the different types; eventually - // this will not be necessary and we can call `backend::execute` - // with the request passed to us. - let headers: Vec<_> = req + // The content should be plain text + let content_type = req .headers .iter() - .map(|(k, v)| (k.as_slice(), v.as_slice())) - .collect(); + .find(|(k, _)| k == b"content-type") + .map(|(_, v)| v) + .ok_or_else(|| Error::BadRequest)?; + if content_type != b"text/plain" { + return Err(Error::BadRequest); + } - // Send the request to the backend and convert the response - backend::execute(backend::Request { - headers: &headers, - body: req.body.as_slice(), - }) - .map(|r| Response { - headers: r.headers, - body: r.body, - }) - .map_err(|e| match e { - backend::Error::BadRequest => Error::BadRequest, + // We assume the body is UTF-8 encoded + let body = str::from_utf8(&req.body).map_err(|_| Error::BadRequest)?; + + // Echo the body back in the response + Ok(Response { + headers: vec![(b"content-type".to_vec(), b"text/plain".to_vec())], + body: format!("The request body was: {body}").into_bytes(), }) } }