Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat(shuttle-next): POC of axum wasm router #472

Merged
merged 19 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 93 additions & 55 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ chrono = { version = "0.4.22", features = ["serde"] }
comfy-table = { version = "6.1.0", optional = true }
crossterm = { version = "0.25.0", optional = true }
http = { version = "0.2.8", optional = true }
http-serde = { version = "1.1.2", optional = true }
hyper = { version = "0.14.23", optional = true }
once_cell = "1.13.1"
rmp-serde = { version = "1.1.1", optional = true }
rustrict = "0.5.0"
serde = { version = "1.0.143", features = ["derive"] }
serde_json = { version = "1.0.85", optional = true }
Expand All @@ -24,3 +27,4 @@ default = ["models"]

models = ["display", "serde_json", "http"]
display = ["comfy-table", "crossterm"]
axum-wasm = ["http-serde", "hyper", "rmp-serde"]
2 changes: 2 additions & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pub mod log;
#[cfg(feature = "models")]
pub mod models;
pub mod project;
#[cfg(feature = "axum-wasm")]
pub mod wasm;

use serde::{Deserialize, Serialize};
use uuid::Uuid;
Expand Down
160 changes: 160 additions & 0 deletions common/src/wasm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use hyper::http::{HeaderMap, Method, Request, Response, StatusCode, Uri, Version};
use rmps::Serializer;
use serde::{Deserialize, Serialize};

extern crate rmp_serde as rmps;

// todo: add http extensions field
#[derive(Serialize, Deserialize, Debug)]
pub struct RequestWrapper {
#[serde(with = "http_serde::method")]
pub method: Method,

#[serde(with = "http_serde::uri")]
pub uri: Uri,

#[serde(with = "http_serde::version")]
pub version: Version,

#[serde(with = "http_serde::header_map")]
pub headers: HeaderMap,
}

impl From<hyper::http::request::Parts> for RequestWrapper {
fn from(parts: hyper::http::request::Parts) -> Self {
RequestWrapper {
method: parts.method,
uri: parts.uri,
version: parts.version,
headers: parts.headers,
}
}
}

impl RequestWrapper {
/// Serialize a RequestWrapper to the Rust MessagePack data format
pub fn into_rmp(self) -> Vec<u8> {
let mut buf = Vec::new();
self.serialize(&mut Serializer::new(&mut buf)).unwrap();

buf
}

/// Consume the wrapper and return a request builder with `Parts` set
pub fn into_request_builder(self) -> hyper::http::request::Builder {
let mut request = Request::builder()
.method(self.method)
.version(self.version)
.uri(self.uri);

request
.headers_mut()
.unwrap()
.extend(self.headers.into_iter());

request
}
}

// todo: add http extensions field
#[derive(Serialize, Deserialize, Debug)]
pub struct ResponseWrapper {
#[serde(with = "http_serde::status_code")]
pub status: StatusCode,

#[serde(with = "http_serde::version")]
pub version: Version,

#[serde(with = "http_serde::header_map")]
pub headers: HeaderMap,
}

impl From<hyper::http::response::Parts> for ResponseWrapper {
fn from(parts: hyper::http::response::Parts) -> Self {
ResponseWrapper {
status: parts.status,
version: parts.version,
headers: parts.headers,
}
}
}

impl ResponseWrapper {
/// Serialize a ResponseWrapper into the Rust MessagePack data format
pub fn into_rmp(self) -> Vec<u8> {
let mut buf = Vec::new();
self.serialize(&mut Serializer::new(&mut buf)).unwrap();

buf
}

/// Consume the wrapper and return a response builder with `Parts` set
pub fn into_response_builder(self) -> hyper::http::response::Builder {
let mut response = Response::builder()
.status(self.status)
.version(self.version);

response
.headers_mut()
.unwrap()
.extend(self.headers.into_iter());

response
}
}

#[cfg(test)]
mod test {
use super::*;
use hyper::body::Body;
use hyper::http::HeaderValue;

#[test]
fn request_roundtrip() {
let request: Request<Body> = Request::builder()
.method(Method::PUT)
.version(Version::HTTP_11)
.header("test", HeaderValue::from_static("request"))
.uri(format!("https://axum-wasm.example/hello"))
.body(Body::empty())
.unwrap();

let (parts, _) = request.into_parts();
let rmp = RequestWrapper::from(parts).into_rmp();

let back: RequestWrapper = rmps::from_slice(&rmp).unwrap();

assert_eq!(
back.headers.get("test").unwrap(),
HeaderValue::from_static("request")
);
assert_eq!(back.method, Method::PUT);
assert_eq!(back.version, Version::HTTP_11);
assert_eq!(
back.uri.to_string(),
"https://axum-wasm.example/hello".to_string()
);
}

#[test]
fn response_roundtrip() {
let response: Response<Body> = Response::builder()
.version(Version::HTTP_11)
.header("test", HeaderValue::from_static("response"))
.status(StatusCode::NOT_MODIFIED)
.body(Body::empty())
.unwrap();

let (parts, _) = response.into_parts();
let rmp = ResponseWrapper::from(parts).into_rmp();

let back: ResponseWrapper = rmps::from_slice(&rmp).unwrap();

assert_eq!(
back.headers.get("test").unwrap(),
HeaderValue::from_static("response")
);
assert_eq!(back.status, StatusCode::NOT_MODIFIED);
assert_eq!(back.version, Version::HTTP_11);
}
}
6 changes: 6 additions & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ anyhow = "1.0.62"
async-trait = "0.1.58"
cap-std = "0.26.0"
clap ={ version = "4.0.18", features = ["derive"] }
hyper = { version = "0.14.23", features = ["full"] }
rmp-serde = { version = "1.1.1" }
serenity = { version = "0.11.5", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }
thiserror = "1.0.37"
tokio = { version = "=1.20.1", features = ["full"] }
Expand All @@ -35,3 +37,7 @@ version = "0.7.0"
default-features = false
features = ["loader"]
path = "../service"

[features]
shuttle-axum = ["shuttle-common/axum-wasm"]

4 changes: 4 additions & 0 deletions runtime/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ wasm:
cd ../tmp/wasm; cargo build --target wasm32-wasi
cp ../tmp/wasm/target/wasm32-wasi/debug/shuttle_serenity.wasm bot.wasm

axum:
cd ../tmp/axum-wasm; cargo build --target wasm32-wasi
cp ../tmp/axum-wasm/target/wasm32-wasi/debug/shuttle_axum.wasm axum.wasm

test: wasm
cargo test -- --nocapture

Expand Down
55 changes: 47 additions & 8 deletions runtime/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## How to run
# How to run

## shuttle-next
```bash
$ make wasm
$ DISCORD_TOKEN=xxx cargo run
Expand All @@ -8,9 +9,47 @@ $ DISCORD_TOKEN=xxx cargo run
In another terminal:

``` bash
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic", "path": "runtime/bot.wasm"}' localhost:8000 runtime.Runtime/Load
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic"}' localhost:8000 runtime.Runtime/Start
grpcurl -plaintext -import-path ../proto -proto runtime.proto localhost:8000 runtime.Runtime/SubscribeLogs
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic", "path": "runtime/bot.wasm"}' localhost:6001 runtime.Runtime/Load
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic"}' localhost:6001 runtime.Runtime/Start
grpcurl -plaintext -import-path ../proto -proto runtime.proto localhost:6001 runtime.Runtime/SubscribeLogs
```

## axum-wasm

Compile the wasm axum router:

```bash
make axum
```

Run the test:

```bash
cargo test axum --features shuttle-axum -- --nocapture
```

Load and run:

```bash
cargo run --features shuttle-axum -- --axum --provisioner-address http://localhost:8000
```

In another terminal:

``` bash
# a full, absolute path from home was needed for me in the load request
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic", "path": "runtime/axum.wasm"}' localhost:6001 runtime.Runtime/Load

grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic"}' localhost:6001 runtime.Runtime/Start

# grpcurl -plaintext -import-path ../proto -proto runtime.proto localhost:6001 runtime.Runtime/SubscribeLogs
```

Curl the service:
```bash
curl localhost:7002/hello

curl localhost:7002/goodbye
```
## shuttle-legacy

Expand All @@ -33,16 +72,16 @@ Or directly (this is the path hardcoded in `deployer::start`):
# first, make sure the shuttle-runtime binary is built
cargo build
# then
/home/<path to shuttle repo>/target/debug/shuttle-runtime --legacy --provisioner-address http://localhost:8000
/home/<path to shuttle repo>/target/debug/shuttle-runtime --legacy --provisioner-address http://localhost:6001
```

Pass the path to `deployer::start`
Then in another shell, load a `.so` file and start it up:

``` bash
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic", "path": "examples/rocket/hello-world/target/debug/libhello_world.so"}' localhost:8000 runtime.Runtime/Load
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic"}' localhost:8000 runtime.Runtime/Start
grpcurl -plaintext -import-path ../proto -proto runtime.proto localhost:8000 runtime.Runtime/SubscribeLogs
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic", "path": "examples/rocket/hello-world/target/debug/libhello_world.so"}' localhost:6001 runtime.Runtime/Load
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic"}' localhost:6001 runtime.Runtime/Start
grpcurl -plaintext -import-path ../proto -proto runtime.proto localhost:6001 runtime.Runtime/SubscribeLogs
```

## Running the tests
Expand Down
6 changes: 5 additions & 1 deletion runtime/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ pub struct Args {
pub provisioner_address: Endpoint,

/// Is this runtime for a legacy service
#[clap(long)]
#[clap(long, conflicts_with("axum"))]
pub legacy: bool,

/// Is this runtime for an axum-wasm service
#[clap(long, conflicts_with("legacy"))]
pub axum: bool,
}
Loading