Skip to content

Commit ea2cff6

Browse files
authored
Merge pull request #28 from Perseus101/fix-wasm-compilation
Fix wasm compilation and send body and headers from WasmClient
2 parents f50f436 + 97de55b commit ea2cff6

File tree

3 files changed

+94
-31
lines changed

3 files changed

+94
-31
lines changed

.github/workflows/ci.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,23 @@ jobs:
6565

6666
- name: Docs
6767
run: cargo doc
68+
69+
check_wasm:
70+
name: Check wasm targets
71+
runs-on: ubuntu-latest
72+
73+
steps:
74+
- uses: actions/checkout@master
75+
76+
- name: Install nightly with wasm32-unknown-unknown
77+
uses: actions-rs/toolchain@v1
78+
with:
79+
toolchain: nightly
80+
target: wasm32-unknown-unknown
81+
override: true
82+
83+
- name: check
84+
uses: actions-rs/cargo@v1
85+
with:
86+
command: check
87+
args: --target wasm32-unknown-unknown --no-default-features --features "native_client,wasm_client"

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ wasm_client = ["js-sys", "web-sys", "wasm-bindgen", "wasm-bindgen-futures"]
2525

2626
[dependencies]
2727
futures = { version = "0.3.1" }
28-
http-types = "2.0.1"
28+
http-types = "2.3.0"
2929
log = "0.4.7"
3030

3131
# h1-client

src/wasm.rs

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
//! http-client implementation for fetch
22
3-
use super::{Body, Error, HttpClient, Request, Response};
3+
use super::{http_types::Headers, Body, Error, HttpClient, Request, Response};
44

55
use futures::future::BoxFuture;
66
use futures::prelude::*;
77

8-
use std::io;
8+
use std::convert::TryFrom;
99
use std::pin::Pin;
1010
use std::task::{Context, Poll};
1111

@@ -31,17 +31,16 @@ impl Clone for WasmClient {
3131
impl HttpClient for WasmClient {
3232
fn send(&self, req: Request) -> BoxFuture<'static, Result<Response, Error>> {
3333
let fut = Box::pin(async move {
34-
let url = format!("{}", req.uri());
35-
let req = fetch::new(req.method().as_str(), &url);
34+
let req: fetch::Request = fetch::Request::new(req).await?;
3635
let mut res = req.send().await?;
3736

3837
let body = res.body_bytes();
39-
let mut response = Response::new(Body::from(body));
40-
*response.status_mut() = http::StatusCode::from_u16(res.status()).unwrap();
41-
38+
let mut response =
39+
Response::new(http_types::StatusCode::try_from(res.status()).unwrap());
40+
response.set_body(Body::from(body));
4241
for (name, value) in res.headers() {
43-
let name: http::header::HeaderName = name.parse().unwrap();
44-
response.headers_mut().insert(name, value.parse().unwrap());
42+
let name: http_types::headers::HeaderName = name.parse().unwrap();
43+
response.insert_header(&name, value);
4544
}
4645

4746
Ok(response)
@@ -51,17 +50,16 @@ impl HttpClient for WasmClient {
5150
}
5251
}
5352

54-
// This type e
5553
struct InnerFuture {
56-
fut: Pin<Box<dyn Future<Output = Result<Response, io::Error>> + 'static>>,
54+
fut: Pin<Box<dyn Future<Output = Result<Response, Error>> + 'static>>,
5755
}
5856

5957
// This is safe because WASM doesn't have threads yet. Once WASM supports threads we should use a
6058
// thread to park the blocking implementation until it's been completed.
6159
unsafe impl Send for InnerFuture {}
6260

6361
impl Future for InnerFuture {
64-
type Output = Result<Response, io::Error>;
62+
type Output = Result<Response, Error>;
6563

6664
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
6765
// This is safe because we're only using this future as a pass-through for the inner
@@ -75,42 +73,87 @@ mod fetch {
7573
use js_sys::{Array, ArrayBuffer, Reflect, Uint8Array};
7674
use wasm_bindgen::JsCast;
7775
use wasm_bindgen_futures::JsFuture;
78-
use web_sys::window;
79-
use web_sys::RequestInit;
76+
use web_sys::{window, RequestInit};
8077

81-
use std::io;
8278
use std::iter::{IntoIterator, Iterator};
79+
use std::pin::Pin;
80+
81+
use http_types::StatusCode;
82+
83+
use crate::Error;
8384

8485
/// Create a new fetch request.
85-
pub(crate) fn new(method: impl AsRef<str>, url: impl AsRef<str>) -> Request {
86-
Request::new(method, url)
87-
}
8886
8987
/// An HTTP Fetch Request.
9088
pub(crate) struct Request {
91-
init: RequestInit,
92-
url: String,
89+
request: web_sys::Request,
90+
/// This field stores the body of the request to ensure it stays allocated as long as the request needs it.
91+
#[allow(dead_code)]
92+
body_buf: Pin<Vec<u8>>,
9393
}
9494

9595
impl Request {
9696
/// Create a new instance.
97-
pub(crate) fn new(method: impl AsRef<str>, url: impl AsRef<str>) -> Self {
98-
let mut init = web_sys::RequestInit::new();
99-
init.method(method.as_ref());
100-
Self {
101-
init,
102-
url: url.as_ref().to_owned(),
97+
pub(crate) async fn new(mut req: super::Request) -> Result<Self, Error> {
98+
// create a fetch request initaliser
99+
let mut init = RequestInit::new();
100+
101+
// set the fetch method
102+
init.method(req.method().as_ref());
103+
104+
let uri = req.url().to_string();
105+
let body = req.take_body();
106+
107+
// convert the body into a uint8 array
108+
// needs to be pinned and retained inside the Request because the Uint8Array passed to
109+
// js is just a portal into WASM linear memory, and if the underlying data is moved the
110+
// js ref will become silently invalid
111+
let body_buf = body.into_bytes().await.map_err(|_| {
112+
Error::from_str(StatusCode::BadRequest, "could not read body into a buffer")
113+
})?;
114+
let body_pinned = Pin::new(body_buf);
115+
if body_pinned.len() > 0 {
116+
let uint_8_array = unsafe { js_sys::Uint8Array::view(&body_pinned) };
117+
init.body(Some(&uint_8_array));
103118
}
119+
120+
let request = web_sys::Request::new_with_str_and_init(&uri, &init).map_err(|e| {
121+
Error::from_str(
122+
StatusCode::BadRequest,
123+
format!("failed to create request: {:?}", e),
124+
)
125+
})?;
126+
127+
// add any fetch headers
128+
let headers: &mut super::Headers = req.as_mut();
129+
for (name, value) in headers.iter() {
130+
let name = name.as_str();
131+
let value = value.as_str();
132+
133+
request.headers().set(name, value).map_err(|_| {
134+
Error::from_str(
135+
StatusCode::BadRequest,
136+
format!("could not add header: {} = {}", name, value),
137+
)
138+
})?;
139+
}
140+
141+
Ok(Self {
142+
request,
143+
body_buf: body_pinned,
144+
})
104145
}
105146

106147
/// Submit a request
107148
// TODO(yoshuawuyts): turn this into a `Future` impl on `Request` instead.
108-
pub(crate) async fn send(self) -> Result<Response, io::Error> {
149+
pub(crate) async fn send(self) -> Result<Response, Error> {
109150
// Send the request.
110151
let window = window().expect("A global window object could not be found");
111-
let request = web_sys::Request::new_with_str_and_init(&self.url, &self.init).unwrap();
112-
let promise = window.fetch_with_request(&request);
113-
let resp = JsFuture::from(promise).await.unwrap();
152+
let promise = window.fetch_with_request(&self.request);
153+
let resp = JsFuture::from(promise)
154+
.await
155+
.map_err(|e| Error::from_str(StatusCode::BadRequest, format!("{:?}", e)))?;
156+
114157
debug_assert!(resp.is_instance_of::<web_sys::Response>());
115158
let res: web_sys::Response = resp.dyn_into().unwrap();
116159

0 commit comments

Comments
 (0)