# Component composition example
This directory contains an example of how to use `wasm-compose`
to compose a component from other components.
## Directory layout
There are three subdirectories in this example:
* `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 a service component
and forward it the request.
Each service implements a `handler` interface defined in `service.wit` as:
```wit
interface handler {
record request {
headers: list<tuple<list<u8>, list<u8>>>,
body: list<u8>,
}
record response {
headers: list<tuple<list<u8>, list<u8>>>,
body: list<u8>
}
enum error {
bad-request,
}
execute: func(req: request) -> result<response, error>
}
```
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.
### Execution flow
The example implements a `service` component that initially executes like
this:
```mermaid
sequenceDiagram
participant server (native)
participant service (wasm)
server (native)->>service (wasm): execute(<request>)
Note right of service (wasm): echo request body
service (wasm)->>server (native): uncompressed response
```
With a simple change to how the `service` component is composed, we can
inject a `middleware` component that will gzip compress the response body,
altering the execution flow to this:
```mermaid
sequenceDiagram
participant server (native)
participant middleware (wasm)
participant service (wasm)
server (native)->>middleware (wasm): execute(<request>)
middleware (wasm)->>service (wasm): execute(<request>)
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!
## Getting started
The components in this example will be built with [`cargo component`](https://github.com/bytecodealliance/cargo-component).
Follow the [installation instructions](https://github.com/bytecodealliance/cargo-component#installation)
to install `cargo component` locally.
> *Note*: `cargo component` is under active development. This example has been tested
> with git sha [`df2eb26`](https://github.com/bytecodealliance/cargo-component/commit/df2eb2633fa6d0c234372fb0471b8e9867d135c6)
> and may not work with other versions of `cargo component`.
Additionally, it is assumed that `wasm-tools` has been installed from the
root of this repository.
## Building the components
To build the `service` component, use `cargo component build`:
```sh
cd service
cargo component build --release
```
To build the `middleware` component, use `cargo component build`:
```sh
cd middleware
cargo component build --release
```
## 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/target/wasm32-wasi/release/svc.wasm
```
This will start a HTTP server that listens at `http://localhost:8080`.
## Sending a request
Use `curl` to send a request to the service:
```
$ curl localhost:8080 -d 'Hello, world!' -H "Content-Type: text/plain" -v
```
This should output something like this:
```
* Trying 127.0.0.1:8080...
* connect to 127.0.0.1 port 8080 failed: Connection refused
* Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> POST / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.79.1
> Accept: */*
> Content-Type: text/plain
> Content-Length: 13
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-length: 35
< content-type: text/plain
< date: Fri, 05 Aug 2022 01:39:43 GMT
<
* Connection #0 to host localhost left intact
The request body was: Hello, world!
```
Note that the response body matches the request body, but it was not
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.
The `server/config.yml` file contains the configuration needed to compose a new
component from the `service` and `middleware` components.
Run `wasm-compose` to compose the new component:
```sh
cd server
wasm-tools compose -c config.yml -o service.wasm ../middleware/target/wasm32-wasi/release/middleware.wasm
```
This results in a new `service.wasm` in the `server` directory where the
`middleware` component is now used.
## Running the server again
If you haven't already, stop the currently running server by pressing `ctrl-c`.
Start the server again with `cargo run` with the newly composed component:
```sh
cd server
cargo run --release -- service.wasm
```
The server should again be listening for requests at `http://localhost:8080`.
## Sending one more request
Use `curl` to send a request to the service, but this time pass the
`--compressed` option:
```
$ curl localhost:8080 -d 'Hello, world!' -H "Content-Type: text/plain" -v --compressed
```
This should output something like this:
```
* Trying 127.0.0.1:8080...
* connect to 127.0.0.1 port 8080 failed: Connection refused
* Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> POST / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.79.1
> Accept: */*
> Accept-Encoding: deflate, gzip
> Content-Type: text/plain
> Content-Length: 13
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-encoding: gzip
< content-length: 58
< content-type: text/plain
< date: Fri, 05 Aug 2022 01:47:59 GMT
<
* Connection #0 to host localhost left intact
The request body was: Hello, world!
```
Note the response used `gzip` for the content encoding.
With such a small response body, the compressed size is actually _bigger_
than the uncompressed size.
## Towards the future
This example will hopefully continue to evolve with the component model
proposal.
Notably, requests and responses will be modeled as _resources_ once the
proposal (and Wasmtime) supports them.
This will enable the service to incrementally read data from the request
body instead of passing it all in at once, for example.
Additionally, the component model proposal will some day support
asynchronous calls. This will enable the service to be invoked
asynchronously rather than blocking the request thread like it does today.