diff --git a/crates/wasm-compose/example/README.md b/crates/wasm-compose/example/README.md index fc7b67a2da..2c0f86783f 100644 --- a/crates/wasm-compose/example/README.md +++ b/crates/wasm-compose/example/README.md @@ -19,31 +19,33 @@ The server will listen for `POST` requests at `http://localhost:8080`. 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: +Each service implements a `handler` interface defined in `service.wit` as: ```wit -record request { - headers: list, list>>, - body: list, +interface handler { + record request { + headers: list, list>>, + body: list, + } + + record response { + headers: list, list>>, + body: list + } + + enum error { + bad-request, + } + + execute: func(req: request) -> result } - -record response { - headers: list, list>>, - body: list -} - -enum error { - bad-request, -} - -execute: func(req: request) -> expected ``` -A service will be passed a `request` containing only the headers and body and -respond with a `response` containing only the headers and body. +A service handler will be passed a `request` containing only the headers and +body and respond with a `response` containing only the headers and body. -Note that this is an overly-simplistic (and inefficient) interface for describing -HTTP request processing. +Note that this is an overly-simplistic (and inefficient) interface for +describing HTTP request processing. ### Execution flow @@ -154,9 +156,9 @@ compressed. ## Composing with a middleware -If we want to instead compress the response bodies for the service, we can easily -compose a new component that sends requests through the `middleware` component -without rebuilding any of the previously built components. +If we want to instead compress the response bodies for the service, we can +easily compose a new component that sends requests through the `middleware` +component without rebuilding any of the previously built components. The `server/config.yml` file contains the configuration needed to compose a new component from the `service` and `middleware` components. diff --git a/crates/wasm-compose/example/middleware/Cargo-component.lock b/crates/wasm-compose/example/middleware/Cargo-component.lock new file mode 100644 index 0000000000..00bc239dbb --- /dev/null +++ b/crates/wasm-compose/example/middleware/Cargo-component.lock @@ -0,0 +1,3 @@ +# This file is automatically generated by cargo-component. +# It is not intended for manual editing. +version = 1 diff --git a/crates/wasm-compose/example/middleware/Cargo.toml b/crates/wasm-compose/example/middleware/Cargo.toml index f7060edc77..113c1abab0 100644 --- a/crates/wasm-compose/example/middleware/Cargo.toml +++ b/crates/wasm-compose/example/middleware/Cargo.toml @@ -6,18 +6,13 @@ publish = false [dependencies] flate2 = "1.0.24" -wit-bindgen-guest-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", default_features = false } +wit-bindgen = { version = "0.3.0", default_features = false } [lib] crate-type = ["cdylib"] -[package.metadata.component] -direct-export = "service" - -[package.metadata.component.imports] -backend = "../service.wit" - -[package.metadata.component.exports] -service = "../service.wit" +[package.metadata.component.target] +path = "../service.wit" +world = "service.middleware" [workspace] diff --git a/crates/wasm-compose/example/middleware/src/lib.rs b/crates/wasm-compose/example/middleware/src/lib.rs index 77cb6fb367..fa292cebf9 100644 --- a/crates/wasm-compose/example/middleware/src/lib.rs +++ b/crates/wasm-compose/example/middleware/src/lib.rs @@ -1,13 +1,13 @@ use bindings::{ - backend, - service::{Error, Request, Response, Service}, + downstream, + handler::{Error, Handler, Request, Response}, }; use flate2::{write::GzEncoder, Compression}; use std::io::Write; struct Component; -impl Service for Component { +impl Handler for Component { fn execute(req: Request) -> Result { let headers: Vec<_> = req .headers @@ -15,8 +15,8 @@ impl Service for Component { .map(|(k, v)| (k.as_slice(), v.as_slice())) .collect(); - // Send the request to the backend - let mut response = backend::execute(backend::Request { + // Send the request to the downstream service + let mut response = downstream::execute(downstream::Request { headers: &headers, body: &req.body, }) @@ -25,7 +25,7 @@ impl Service for Component { body: r.body, }) .map_err(|e| match e { - backend::Error::BadRequest => Error::BadRequest, + downstream::Error::BadRequest => Error::BadRequest, })?; // If the response is already encoded, leave it alone diff --git a/crates/wasm-compose/example/server/Cargo.toml b/crates/wasm-compose/example/server/Cargo.toml index 1d53eab868..9ba66fbc1e 100644 --- a/crates/wasm-compose/example/server/Cargo.toml +++ b/crates/wasm-compose/example/server/Cargo.toml @@ -6,9 +6,10 @@ publish = false [dependencies] async-std = { version = "1.12.0", features = ["attributes"] } -clap = { version = "3.2.16", features = ["derive"] } +clap = { version = "3.2.23", features = ["derive"] } driftwood = "0.0.6" tide = "0.16.0" -wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "b61e678", features = ["component-model"] } +wasmtime = { version = "6.0.0", features = ["component-model"] } +wasmtime-component-macro = "6.0.0" [workspace] diff --git a/crates/wasm-compose/example/server/config.yml b/crates/wasm-compose/example/server/config.yml index e00301175c..f370858b7a 100644 --- a/crates/wasm-compose/example/server/config.yml +++ b/crates/wasm-compose/example/server/config.yml @@ -4,4 +4,4 @@ search-paths: instantiations: $input: arguments: - backend: svc + downstream: svc diff --git a/crates/wasm-compose/example/server/src/main.rs b/crates/wasm-compose/example/server/src/main.rs index 7dab651a69..a9108c0d97 100644 --- a/crates/wasm-compose/example/server/src/main.rs +++ b/crates/wasm-compose/example/server/src/main.rs @@ -10,6 +10,11 @@ use tide::{ }; use wasmtime::{component::*, Config, Engine, Store}; +wasmtime_component_macro::bindgen!({ + path: "../service.wit", + world: "service" +}); + /// Represents state stored in the tide application context. /// /// This is so that a component is only parsed and compiled once. @@ -33,42 +38,10 @@ impl State { } } -#[derive(ComponentType, Lower)] -#[component(record)] -struct ServiceRequest { - headers: Vec<(Vec, Vec)>, - body: Vec, -} - -impl ServiceRequest { - async fn new(mut req: Request) -> tide::Result { - // Convert the tide request to a service request. - let headers = req - .iter() - .map(|(n, v)| { - ( - n.as_str().as_bytes().to_vec(), - v.as_str().as_bytes().to_vec(), - ) - }) - .collect(); - let body = req.take_body().into_bytes().await?; - - Ok(Self { headers, body }) - } -} - -#[derive(ComponentType, Lift)] -#[component(record)] -struct ServiceResponse { - headers: Vec<(Vec, Vec)>, - body: Vec, -} - -impl TryFrom for tide::Response { +impl TryFrom for tide::Response { type Error = tide::Error; - fn try_from(r: ServiceResponse) -> Result { + fn try_from(r: handler::Response) -> Result { // Convert the service response to a tide response let mut builder = tide::Response::builder(StatusCode::Ok); for (name, value) in r.headers { @@ -82,25 +55,16 @@ impl TryFrom for tide::Response { } } -#[derive(ComponentType, Lift)] -#[component(enum)] -enum ServiceError { - #[component(name = "bad-request")] - BadRequest, -} - -impl From for tide::Error { - fn from(e: ServiceError) -> Self { - match e { - ServiceError::BadRequest => { +impl handler::Error { + fn into_tide(self) -> tide::Error { + match self { + Self::BadRequest => { tide::Error::from_str(StatusCode::BadRequest, "bad service request") } } } } -type ServiceResult = Result; - /// WebAssembly component server. /// /// A demonstration server that executes WebAssembly components. @@ -144,24 +108,29 @@ impl ServerApp { app.listen(address).await.map_err(Into::into) } - async fn process_request(req: Request) -> tide::Result { - let state = req.state(); + async fn process_request(mut req: Request) -> tide::Result { + let body = req.body_bytes().await?; + let headers = req + .iter() + .map(|(n, v)| (n.as_str().as_bytes(), v.as_str().as_bytes())) + .collect::>(); // Create a new store for the request - let mut store = Store::new(&state.engine, ()); + let state = req.state(); let linker: Linker<()> = Linker::new(&state.engine); - - // 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")?; - - // Call the `execute` export with the request and translate the response - execute - .call(&mut store, (ServiceRequest::new(req).await?,))? - .0 - .map_err(Into::into) - .and_then(TryInto::try_into) + let mut store = Store::new(&state.engine, ()); + let (service, _) = Service::instantiate(&mut store, &state.component, &linker)?; + service + .handler + .call_execute( + &mut store, + handler::Request { + headers: &headers, + body: &body, + }, + )? + .map(TryInto::try_into) + .map_err(handler::Error::into_tide)? } } diff --git a/crates/wasm-compose/example/service.wit b/crates/wasm-compose/example/service.wit index 4bc65ac492..f4bbc805fd 100644 --- a/crates/wasm-compose/example/service.wit +++ b/crates/wasm-compose/example/service.wit @@ -1,15 +1,26 @@ -record request { - headers: list, list>>, - body: list, -} +interface handler { + record request { + headers: list, list>>, + body: list, + } + + record response { + headers: list, list>>, + body: list + } -record response { - headers: list, list>>, - body: list + enum error { + bad-request, + } + + execute: func(req: request) -> result } -enum error { - bad-request, +default world service { + export handler: self.handler } -execute: func(req: request) -> result +world middleware { + import downstream: self.handler + export handler: self.handler +} diff --git a/crates/wasm-compose/example/service/Cargo-component.lock b/crates/wasm-compose/example/service/Cargo-component.lock new file mode 100644 index 0000000000..00bc239dbb --- /dev/null +++ b/crates/wasm-compose/example/service/Cargo-component.lock @@ -0,0 +1,3 @@ +# This file is automatically generated by cargo-component. +# It is not intended for manual editing. +version = 1 diff --git a/crates/wasm-compose/example/service/Cargo.toml b/crates/wasm-compose/example/service/Cargo.toml index ff19219126..c44a814bbe 100644 --- a/crates/wasm-compose/example/service/Cargo.toml +++ b/crates/wasm-compose/example/service/Cargo.toml @@ -5,15 +5,12 @@ edition = "2021" publish = false [dependencies] -wit-bindgen-guest-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", default_features = false } +wit-bindgen = { version = "0.3.0", default_features = false } [lib] crate-type = ["cdylib"] -[package.metadata.component] -direct-export = "service" - -[package.metadata.component.exports] -service = "../service.wit" +[package.metadata.component.target] +path = "../service.wit" [workspace] diff --git a/crates/wasm-compose/example/service/src/lib.rs b/crates/wasm-compose/example/service/src/lib.rs index 63730b31d1..0d05a240c1 100644 --- a/crates/wasm-compose/example/service/src/lib.rs +++ b/crates/wasm-compose/example/service/src/lib.rs @@ -1,9 +1,9 @@ -use bindings::service::{Error, Request, Response, Service}; +use bindings::handler::{Error, Handler, Request, Response}; use std::str; struct Component; -impl Service for Component { +impl Handler for Component { fn execute(req: Request) -> Result { // The content should be plain text let content_type = req @@ -11,7 +11,8 @@ impl Service for Component { .iter() .find(|(k, _)| k == b"content-type") .map(|(_, v)| v) - .ok_or_else(|| Error::BadRequest)?; + .ok_or(Error::BadRequest)?; + if content_type != b"text/plain" { return Err(Error::BadRequest); }