use super::{http_types::Headers, Body, Error, HttpClient, Request, Response};
use futures::prelude::*;
use std::convert::TryFrom;
use std::pin::Pin;
use std::task::{Context, Poll};
#[derive(Debug)]
pub struct WasmClient {
_priv: (),
}
impl WasmClient {
pub fn new() -> Self {
Self { _priv: () }
}
}
impl HttpClient for WasmClient {
fn send<'a, 'async_trait>(
&'a self,
req: Request,
) -> Pin<Box<dyn Future<Output = Result<Response, Error>> + Send + 'async_trait>>
where
'a: 'async_trait,
Self: 'async_trait,
{
InnerFuture::new(async move {
let req: fetch::Request = fetch::Request::new(req).await?;
let mut res = req.send().await?;
let body = res.body_bytes();
let mut response =
Response::new(http_types::StatusCode::try_from(res.status()).unwrap());
response.set_body(Body::from(body));
for (name, value) in res.headers() {
let name: http_types::headers::HeaderName = name.parse().unwrap();
response.insert_header(&name, value);
}
Ok(response)
})
}
}
struct InnerFuture {
fut: Pin<Box<dyn Future<Output = Result<Response, Error>> + 'static>>,
}
impl InnerFuture {
fn new<F: Future<Output = Result<Response, Error>> + 'static>(fut: F) -> Pin<Box<Self>> {
Box::pin(Self { fut: Box::pin(fut) })
}
}
unsafe impl Send for InnerFuture {}
impl Future for InnerFuture {
type Output = Result<Response, Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe { Pin::new_unchecked(&mut self.fut).poll(cx) }
}
}
mod fetch {
use js_sys::{Array, ArrayBuffer, Reflect, Uint8Array};
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{window, RequestInit};
use std::iter::{IntoIterator, Iterator};
use std::pin::Pin;
use http_types::StatusCode;
use crate::Error;
pub(crate) struct Request {
request: web_sys::Request,
#[allow(dead_code)]
body_buf: Pin<Vec<u8>>,
}
impl Request {
pub(crate) async fn new(mut req: super::Request) -> Result<Self, Error> {
let mut init = RequestInit::new();
init.method(req.method().as_ref());
let uri = req.url().to_string();
let body = req.take_body();
let body_buf = body.into_bytes().await.map_err(|_| {
Error::from_str(StatusCode::BadRequest, "could not read body into a buffer")
})?;
let body_pinned = Pin::new(body_buf);
if body_pinned.len() > 0 {
let uint_8_array = unsafe { js_sys::Uint8Array::view(&body_pinned) };
init.body(Some(&uint_8_array));
}
let request = web_sys::Request::new_with_str_and_init(&uri, &init).map_err(|e| {
Error::from_str(
StatusCode::BadRequest,
format!("failed to create request: {:?}", e),
)
})?;
let headers: &mut super::Headers = req.as_mut();
for (name, value) in headers.iter() {
let name = name.as_str();
let value = value.as_str();
request.headers().set(name, value).map_err(|_| {
Error::from_str(
StatusCode::BadRequest,
format!("could not add header: {} = {}", name, value),
)
})?;
}
Ok(Self {
request,
body_buf: body_pinned,
})
}
pub(crate) async fn send(self) -> Result<Response, Error> {
let window = window().expect("A global window object could not be found");
let promise = window.fetch_with_request(&self.request);
let resp = JsFuture::from(promise)
.await
.map_err(|e| Error::from_str(StatusCode::BadRequest, format!("{:?}", e)))?;
debug_assert!(resp.is_instance_of::<web_sys::Response>());
let res: web_sys::Response = resp.dyn_into().unwrap();
let promise = res.array_buffer().unwrap();
let resp = JsFuture::from(promise).await.unwrap();
debug_assert!(resp.is_instance_of::<js_sys::ArrayBuffer>());
let buf: ArrayBuffer = resp.dyn_into().unwrap();
let slice = Uint8Array::new(&buf);
let mut body: Vec<u8> = vec![0; slice.length() as usize];
slice.copy_to(&mut body);
Ok(Response::new(res, body))
}
}
pub(crate) struct Response {
res: web_sys::Response,
body: Option<Vec<u8>>,
}
impl Response {
fn new(res: web_sys::Response, body: Vec<u8>) -> Self {
Self {
res,
body: Some(body),
}
}
pub(crate) fn headers(&self) -> Headers {
Headers {
headers: self.res.headers(),
}
}
pub(crate) fn body_bytes(&mut self) -> Vec<u8> {
self.body.take().unwrap_or_else(|| vec![])
}
pub(crate) fn status(&self) -> u16 {
self.res.status()
}
}
pub(crate) struct Headers {
headers: web_sys::Headers,
}
impl IntoIterator for Headers {
type Item = (String, String);
type IntoIter = HeadersIter;
fn into_iter(self) -> Self::IntoIter {
HeadersIter {
iter: js_sys::try_iter(&self.headers).unwrap().unwrap(),
}
}
}
pub(crate) struct HeadersIter {
iter: js_sys::IntoIter,
}
impl Iterator for HeadersIter {
type Item = (String, String);
fn next(&mut self) -> Option<Self::Item> {
let pair = self.iter.next()?;
let array: Array = pair.unwrap().into();
let vals = array.values();
let prop = String::from("value").into();
let key = Reflect::get(&vals.next().unwrap(), &prop).unwrap();
let value = Reflect::get(&vals.next().unwrap(), &prop).unwrap();
Some((
key.as_string().to_owned().unwrap(),
value.as_string().to_owned().unwrap(),
))
}
}
}