Skip to content
Open
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
40 changes: 18 additions & 22 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,29 @@ jobs:
build:
name: Build
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: wasm32-unknown-unknown
- target: wasm32-wasip2
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: wasm32-unknown-unknown
- name: Install Rust from rust-toolchain.toml
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --target wasm32-unknown-unknown
args: --target ${{ matrix.target }}
fmt:
name: Format Check
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rustfmt
target: wasm32-unknown-unknown
- name: Install Rust from rust-toolchain.toml
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
Expand All @@ -45,18 +41,18 @@ jobs:
clippy:
name: Clippy Check
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: wasm32-unknown-unknown
- target: wasm32-wasip2
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: clippy
target: wasm32-unknown-unknown
- name: Install Rust from rust-toolchain.toml
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --target wasm32-unknown-unknown -- -D warnings
args: --target ${{ matrix.target }} -- -D warnings
25 changes: 17 additions & 8 deletions .github/workflows/gzip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,26 @@ jobs:
test:
name: Integration Tests with gzip compression
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: wasm32-unknown-unknown
recipe: test-gzip-headless
- target: wasm32-wasip2
recipe: test-gzip-wasip2
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: wasm32-unknown-unknown
- name: Install Rust from rust-toolchain.toml
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
if: ${{ matrix.target == 'wasm32-unknown-unknown' }}
- name: Install wasmtime
run: |
curl https://wasmtime.dev/install.sh -sSf | bash
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
if: ${{ matrix.target == 'wasm32-wasip2' }}
- name: Install just
uses: extractions/setup-just@v1
- name: Install Protoc
Expand All @@ -29,5 +38,5 @@ jobs:
run: just build-gzip-test-server
- name: Run test `tonic-web` server
run: just start-gzip-test-server &
- name: Run headless browser test
run: just test-gzip-headless
- name: Run test ${{ matrix.recipe }}
run: just ${{ matrix.recipe }}
25 changes: 17 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,26 @@ jobs:
test:
name: Integration Tests
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: wasm32-unknown-unknown
recipe: test-headless
- target: wasm32-wasip2
recipe: test-wasip2
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: wasm32-unknown-unknown
- name: Install Rust from rust-toolchain.toml
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
if: ${{ matrix.target == 'wasm32-unknown-unknown' }}
- name: Install wasmtime
run: |
curl https://wasmtime.dev/install.sh -sSf | bash
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
if: ${{ matrix.target == 'wasm32-wasip2' }}
- name: Install just
uses: extractions/setup-just@v1
- name: Install Protoc
Expand All @@ -29,5 +38,5 @@ jobs:
run: just build-test-server
- name: Run test `tonic-web` server
run: just start-test-server &
- name: Run headless browser test
run: just test-headless
- name: Run test ${{ matrix.recipe }}
run: just ${{ matrix.recipe }}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
**/target
**/*.rs.bk
Cargo.lock
.cargo
/.cargo
.config
9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ readme = "README.md"
categories = ["web-programming", "network-programming", "asynchronous"]
keywords = ["grpc", "grpc-web", "tonic", "wasm"]
edition = "2021"
rust-version = "1.86"

[dependencies]
base64 = "0.22"
Expand All @@ -20,11 +21,14 @@ http = "1"
http-body = "1"
http-body-util = "0.1"
httparse = "1"
js-sys = "0.3"
pin-project = "1"
thiserror = "2"
tonic = { version = "0.14", default-features = false }
tower-service = "0.3"

# Browser-specific dependencies
[target.wasm32-unknown-unknown.dependencies]
js-sys = "0.3"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
wasm-streams = "0.4"
Expand All @@ -42,3 +46,6 @@ web-sys = { version = "0.3", features = [
"Response",
"ServiceWorkerGlobalScope",
] }

[target.wasm32-wasip2.dependencies]
wstd = "0.5"
36 changes: 32 additions & 4 deletions Justfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# Builds `tonic-web-wasm-client`
# Builds `tonic-web-wasm-client` for `wasm32-unknown-unknown` target
build:
@echo 'Building...'
cargo build --target wasm32-unknown-unknown

# Builds test `tonic-web` server
# Builds `tonic-web-wasm-client` for `wasm32-wasip2` target
build-wasip2:
@echo 'Building...'
cargo build --target wasm32-wasip2

# Builds test `tonic-web` server natively
build-test-server:
@echo 'Building test server...'
cd test-suite/simple/server && cargo build
Expand All @@ -23,12 +28,29 @@ test-headless:
@echo 'Testing...'
cd test-suite/simple/client && wasm-pack test --headless --chrome

# Builds test `tonic-web` server (with compression enabled: gzip)
# Checks wasmtime version, needed only for -wasip2 recipes
check-wasmtime-version:
#!/usr/bin/env bash
set -euo pipefail
version_output=$(wasmtime --version)
current_version=$(echo "${version_output}" | awk '{print $2}')
required_version="35.0.0"
if ! printf '%s\n' "${required_version}" "${current_version}" | sort --check=silent --version-sort; then
echo "Error: wasmtime version ${current_version} is installed."
echo "Version 35.0.0 or greater is required."
exit 1
fi

# Runs wasip2 tests in `wasmtime`
test-wasip2: check-wasmtime-version
cd test-suite/simple/client && cargo test --target wasm32-wasip2

# Builds test `tonic-web` server (with compression enabled: gzip) for `wasm32-unknown-unknown` target
build-gzip-test-server:
@echo 'Building test server...'
cd test-suite/gzip/server && cargo build

# Starts test `tonic-web` server (with compression enabled: gzip)
# Starts test `tonic-web` server (with compression enabled: gzip) natively
start-gzip-test-server:
@echo 'Starting test server...'
cd test-suite/gzip/server && cargo run
Expand All @@ -42,3 +64,9 @@ test-gzip:
test-gzip-headless:
@echo 'Testing...'
cd test-suite/gzip/client && wasm-pack test --headless --chrome

# Runs wasip2 tests (with compression enabled: gzip) in `wasmtime`
test-gzip-wasip2: check-wasmtime-version
@echo 'Testing...'
cd test-suite/gzip/client && cargo test --target wasm32-wasip2

4 changes: 4 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[toolchain]
channel = "1.86"
targets = ["wasm32-unknown-unknown", "wasm32-wasip2"]
components = ["rustfmt", "clippy", "rust-src", "rust-analyzer", "cargo"]
25 changes: 6 additions & 19 deletions src/body_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,20 @@ use std::{
};

use bytes::Bytes;
use futures_util::{stream::empty, Stream, TryStreamExt};
use futures_util::{stream::empty, Stream};
use http_body::{Body, Frame};
use js_sys::Uint8Array;
use wasm_streams::readable::IntoStream;

use crate::Error;

type BodyStreamPinned = Pin<Box<dyn Stream<Item = Result<Bytes, Error>>>>;

pub struct BodyStream {
body_stream: Pin<Box<dyn Stream<Item = Result<Bytes, Error>>>>,
body_stream: BodyStreamPinned,
}

impl BodyStream {
pub fn new(body_stream: IntoStream<'static>) -> Self {
let body_stream = body_stream
.map_ok(|js_value| {
let buffer = Uint8Array::new(&js_value);

let mut bytes_vec = vec![0; buffer.length() as usize];
buffer.copy_to(&mut bytes_vec);

bytes_vec.into()
})
.map_err(Error::js_error);

Self {
body_stream: Box::pin(body_stream),
}
pub fn new(body_stream: BodyStreamPinned) -> Self {
Self { body_stream }
}

pub fn empty() -> Self {
Expand Down
26 changes: 23 additions & 3 deletions src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ use http::{
use http_body_util::BodyExt;
use js_sys::{Array, Uint8Array};
use tonic::body::Body;
use wasm_bindgen::JsValue;
use wasm_bindgen::{JsCast as _, JsValue};
use web_sys::{Headers, RequestCredentials, RequestInit};

use crate::{fetch::fetch, options::FetchOptions, Error, ResponseBody};
use crate::{body_stream::BodyStream, fetch::fetch, options::FetchOptions, Error, ResponseBody};

pub async fn call(
pub(crate) async fn call(
mut base_url: String,
request: Request<Body>,
options: Option<FetchOptions>,
Expand All @@ -28,13 +28,33 @@ pub async fn call(
let (result, content_type) = set_response_headers(result, &response)?;

let content_type = content_type.ok_or(Error::MissingContentTypeHeader)?;

let body_stream = response.body().ok_or(Error::MissingResponseBody)?;
let body_stream = prepare_body_stream(
wasm_streams::ReadableStream::from_raw(body_stream.unchecked_into()).into_stream(),
);

let body = ResponseBody::new(body_stream, &content_type)?;

result.body(body).map_err(Into::into)
}

fn prepare_body_stream(body_stream: wasm_streams::readable::IntoStream<'static>) -> BodyStream {
use futures_util::TryStreamExt as _;
let body_stream = body_stream
.map_ok(|js_value| {
let buffer = js_sys::Uint8Array::new(&js_value);

let mut bytes_vec = vec![0; buffer.length() as usize];
buffer.copy_to(&mut bytes_vec);

bytes_vec.into()
})
.map_err(Error::js_error);

BodyStream::new(Box::pin(body_stream))
}

fn prepare_headers(header_map: &HeaderMap<HeaderValue>) -> Result<Headers, Error> {
// Construct default headers.
let headers = Headers::new().map_err(Error::js_error)?;
Expand Down
15 changes: 10 additions & 5 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use http::header::{InvalidHeaderName, InvalidHeaderValue, ToStrError};
use js_sys::Object;
use thiserror::Error;
use wasm_bindgen::{JsCast, JsValue};

/// Error type for `tonic-web-wasm-client`
#[derive(Debug, Error)]
Expand All @@ -28,6 +26,7 @@ pub enum Error {
#[error("invalid header value")]
InvalidHeaderValue(#[from] InvalidHeaderValue),
/// JS API error
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
#[error("js api error: {0}")]
JsError(String),
/// Malformed response
Expand All @@ -42,17 +41,23 @@ pub enum Error {
/// gRPC error
#[error("grpc error")]
TonicStatusError(#[from] tonic::Status),
#[cfg(all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"))]
#[error(transparent)]
WstdHttp(wstd::http::Error),
}

impl Error {
/// Initialize js error from js value
pub(crate) fn js_error(value: JsValue) -> Self {
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub(crate) fn js_error(value: wasm_bindgen::JsValue) -> Self {
let message = js_object_display(&value);
Self::JsError(message)
}
}

fn js_object_display(option: &JsValue) -> String {
let object: &Object = option.unchecked_ref();
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
fn js_object_display(option: &wasm_bindgen::JsValue) -> String {
use wasm_bindgen::JsCast;
let object: &js_sys::Object = option.unchecked_ref();
ToString::to_string(&object.to_string())
}
Loading
Loading