Skip to content

Instantly share code, notes, and snippets.

@izissise
Created December 24, 2019 10:50
Show Gist options
  • Save izissise/dcfe5325826e716f98223c8087942e15 to your computer and use it in GitHub Desktop.
Save izissise/dcfe5325826e716f98223c8087942e15 to your computer and use it in GitHub Desktop.
Ruma stdweb XmlHttpRequest client
//! Implement ruma_client::HttpRequester with XmlHttpRequest
//! so it works in a browser context
use futures::channel::oneshot::channel;
use futures_core::future::Future;
use http::{Request as HttpRequest, Response as HttpResponse, StatusCode};
use ruma_client::{Client, HttpRequester, HttpRequesterError};
use std::pin::Pin;
use stdweb::traits::IEvent;
use stdweb::web::{event::ReadyStateChangeEvent, IEventTarget, XhrReadyState, XmlHttpRequest};
use stdweb::Reference;
#[derive(Debug)]
pub struct XmlHttpRequester;
/// Wrapper type for ruma client using XmlHttpRequest
pub type XmlHttpClient = Client<XmlHttpRequester>;
impl HttpRequester<Vec<u8>> for XmlHttpRequester {
fn request(
&self,
req: HttpRequest<Vec<u8>>,
) -> Pin<Box<dyn Future<Output = Result<HttpResponse<Vec<u8>>, HttpRequesterError>> + Send + '_>>
{
let xhr = XmlHttpRequest::new();
let body = req.body();
let method = req.method().as_str();
let endpoint = req.uri().to_string();
match xhr.open(method, &endpoint) {
Ok(_) => {}
Err(_) => return Box::pin(async move { Err(HttpRequesterError) }),
};
let (sender, fut) = channel::<HttpResponse<Vec<u8>>>();
// https://users.rust-lang.org/t/moving-a-sender-into-a-fn-closure/33875/5
let mut sender = Some(sender);
xhr.add_event_listener(move |e: ReadyStateChangeEvent| {
// Magically retrieve the request object through js and stdweb
let xhr = Reference::from(e.target().unwrap())
.downcast::<XmlHttpRequest>()
.unwrap();
match xhr.ready_state() {
XhrReadyState::Done => {
// FIXME Waiting merge of https://github.com/koute/stdweb/pull/381 so headers can be put into the hyper response
let raw_headers: Option<String> = None /*xhr.get_all_response_headers()*/;
let headers = match raw_headers {
Some(rh) => {
let mut headers = http::header::HeaderMap::new();
for h in rh.split("\r\n") {
if h.len() == 0 {
continue;
}
let parts: Vec<&str> = h.split(": ").collect();
let k = parts[0];
let k = http::header::HeaderName::from_bytes(k.as_bytes()).unwrap();
let v = parts[1..].join(": ");
let v = http::header::HeaderValue::from_str(&v).unwrap();
headers.append(k, v);
}
headers
}
None => http::header::HeaderMap::new(),
};
let _type = xhr.response_type();
let tmp_response = HttpResponse::new("");
let (mut parts, _body) = tmp_response.into_parts();
let body = match xhr.raw_response().into_string() {
Some(b) => b,
None => "".to_owned(),
};
let body = body.as_bytes().to_owned();
parts.status = StatusCode::from_u16(xhr.status()).unwrap();
parts.version = http::version::Version::HTTP_11;
parts.headers = headers;
let rep = HttpResponse::from_parts(parts, body);
if let Some(tx) = sender.take() {
tx.send(rep).unwrap();
} else {
panic!("Unreachable");
}
}
_ => { /*debug!("Request on-going");*/ }
};
});
match xhr.send_with_bytes(body) {
Ok(_) => {}
Err(_) => return Box::pin(async move { Err(HttpRequesterError) }),
};
Box::pin(async move { fut.await.map_err(|_e| HttpRequesterError) })
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment