rewrite client

This commit is contained in:
Toshit Chawda 2024-06-12 11:51:06 -07:00
parent 273063ec28
commit 177a0d2167
No known key found for this signature in database
GPG key ID: 91480ED99E2B3D9D
13 changed files with 1338 additions and 1710 deletions

View file

@ -1,118 +1,25 @@
use crate::*;
use std::{
pin::Pin,
task::{Context, Poll},
};
use rustls_pki_types::Der;
use wasm_bindgen::prelude::*;
use bytes::Bytes;
use futures_util::{Future, Stream};
use http::{HeaderValue, Uri};
use hyper::{body::Body, rt::Executor};
use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array};
use pin_project_lite::pin_project;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use hyper::rt::Executor;
use js_sys::ArrayBuffer;
use std::future::Future;
use wisp_mux::{extensions::udp::UdpProtocolExtensionBuilder, WispError};
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console, js_name = debug)]
pub fn console_debug(s: &str);
#[wasm_bindgen(js_namespace = console, js_name = log)]
pub fn console_log(s: &str);
#[wasm_bindgen(js_namespace = console, js_name = error)]
pub fn console_error(s: &str);
}
macro_rules! debug {
($($t:tt)*) => (utils::console_debug(&format_args!($($t)*).to_string()))
}
macro_rules! log {
($($t:tt)*) => (utils::console_log(&format_args!($($t)*).to_string()))
}
#[allow(unused_macros)]
macro_rules! error {
($($t:tt)*) => (utils::console_error(&format_args!($($t)*).to_string()))
}
macro_rules! jerr {
($expr:expr) => {
JsError::new($expr)
};
}
macro_rules! jval {
($expr:expr) => {
JsValue::from($expr)
};
}
pub trait ReplaceErr {
type Ok;
fn replace_err(self, err: &str) -> Result<Self::Ok, JsError>;
fn replace_err_jv(self, err: &str) -> Result<Self::Ok, JsValue>;
}
impl<T, E: std::fmt::Debug> ReplaceErr for Result<T, E> {
type Ok = T;
fn replace_err(self, err: &str) -> Result<<Self as ReplaceErr>::Ok, JsError> {
self.map_err(|x| jerr!(&format!("{}, original error: {:?}", err, x)))
}
fn replace_err_jv(self, err: &str) -> Result<<Self as ReplaceErr>::Ok, JsValue> {
self.map_err(|x| jval!(&format!("{}, original error: {:?}", err, x)))
}
}
impl<T> ReplaceErr for Option<T> {
type Ok = T;
fn replace_err(self, err: &str) -> Result<<Self as ReplaceErr>::Ok, JsError> {
self.ok_or_else(|| jerr!(err))
}
fn replace_err_jv(self, err: &str) -> Result<<Self as ReplaceErr>::Ok, JsValue> {
self.ok_or_else(|| jval!(err))
}
}
// the... BOOLINATOR!
impl ReplaceErr for bool {
type Ok = ();
fn replace_err(self, err: &str) -> Result<(), JsError> {
if !self {
Err(jerr!(err))
} else {
Ok(())
}
}
fn replace_err_jv(self, err: &str) -> Result<(), JsValue> {
if !self {
Err(jval!(err))
} else {
Ok(())
}
}
}
// the... BOOLINATOR!
pub trait Boolinator {
fn flatten(self, err: &str) -> Result<(), JsError>;
}
impl Boolinator for Result<bool, JsValue> {
fn flatten(self, err: &str) -> Result<(), JsError> {
self.replace_err(err)?.replace_err(err)
}
}
use crate::EpoxyError;
pub trait UriExt {
fn get_redirect(&self, location: &HeaderValue) -> Result<Uri, JsError>;
fn get_redirect(&self, location: &HeaderValue) -> Result<Uri, EpoxyError>;
}
impl UriExt for Uri {
fn get_redirect(&self, location: &HeaderValue) -> Result<Uri, JsError> {
fn get_redirect(&self, location: &HeaderValue) -> Result<Uri, EpoxyError> {
let new_uri = location.to_str()?.parse::<hyper::Uri>()?;
let mut new_parts: http::uri::Parts = new_uri.into();
if new_parts.scheme.is_none() {
@ -141,8 +48,75 @@ where
}
}
pin_project! {
pub struct IncomingBody {
#[pin]
incoming: hyper::body::Incoming,
}
}
impl IncomingBody {
pub fn new(incoming: hyper::body::Incoming) -> IncomingBody {
IncomingBody { incoming }
}
}
impl Stream for IncomingBody {
type Item = std::io::Result<Bytes>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.project();
let ret = this.incoming.poll_frame(cx);
match ret {
Poll::Ready(item) => Poll::<Option<Self::Item>>::Ready(match item {
Some(frame) => frame
.map(|x| {
x.into_data()
.map_err(|_| std::io::Error::other("not data frame"))
})
.ok(),
None => None,
}),
Poll::Pending => Poll::<Option<Self::Item>>::Pending,
}
}
}
pub fn is_redirect(code: u16) -> bool {
[301, 302, 303, 307, 308].contains(&code)
}
pub fn is_null_body(code: u16) -> bool {
[101, 204, 205, 304].contains(&code)
}
pub fn object_get(obj: &Object, key: &str) -> Option<JsValue> {
Reflect::get(obj, &key.into()).ok()
}
pub fn object_set(obj: &Object, key: &JsValue, value: &JsValue) -> Result<(), EpoxyError> {
if Reflect::set(obj, key, value).map_err(|_| EpoxyError::RawHeaderSetFailed)? {
Ok(())
} else {
Err(EpoxyError::RawHeaderSetFailed)
}
}
pub async fn convert_body(val: JsValue) -> Result<(Uint8Array, web_sys::Request), JsValue> {
let req = web_sys::Request::new_with_str_and_init(
"/",
web_sys::RequestInit::new().method("POST").body(Some(&val)),
)?;
Ok((
JsFuture::from(req.array_buffer()?)
.await?
.dyn_into::<ArrayBuffer>()
.map(|x| Uint8Array::new(&x))?,
req,
))
}
pub fn entries_of_object(obj: &Object) -> Vec<Vec<String>> {
js_sys::Object::entries(obj)
Object::entries(obj)
.to_vec()
.iter()
.filter_map(|val| {
@ -157,124 +131,10 @@ pub fn entries_of_object(obj: &Object) -> Vec<Vec<String>> {
pub fn define_property_obj(value: JsValue, writable: bool) -> Result<Object, JsValue> {
let entries: Array = [
Array::of2(&jval!("value"), &value),
Array::of2(&jval!("writable"), &jval!(writable)),
Array::of2(&"value".into(), &value),
Array::of2(&"writable".into(), &writable.into()),
]
.iter()
.collect::<Array>();
Object::from_entries(&entries)
}
pub fn is_redirect(code: u16) -> bool {
[301, 302, 303, 307, 308].contains(&code)
}
pub fn is_null_body(code: u16) -> bool {
[101, 204, 205, 304].contains(&code)
}
pub fn get_is_secure(url: &Uri) -> Result<bool, JsError> {
let url_scheme_str = url.scheme_str().replace_err("URL must have a scheme")?;
match url_scheme_str {
"https" | "wss" => Ok(true),
_ => Ok(false),
}
}
pub fn get_url_port(url: &Uri) -> Result<u16, JsError> {
if let Some(port) = url.port() {
Ok(port.as_u16())
} else if get_is_secure(url)? {
Ok(443)
} else {
Ok(80)
}
}
pub async fn make_mux(
url: &str,
) -> Result<
(
ClientMux,
impl Future<Output = Result<(), WispError>> + Send,
),
WispError,
> {
let (wtx, wrx) =
WebSocketWrapper::connect(url, vec![]).map_err(|_| WispError::WsImplSocketClosed)?;
wtx.wait_for_open().await;
Ok(
ClientMux::create(wrx, wtx, Some(&[Box::new(UdpProtocolExtensionBuilder())]))
.await?
.with_no_required_extensions(),
)
}
pub fn spawn_mux_fut(
mux: Arc<RwLock<ClientMux>>,
fut: impl Future<Output = Result<(), WispError>> + Send + 'static,
url: String,
) {
wasm_bindgen_futures::spawn_local(async move {
debug!("epoxy: mux future started");
if let Err(e) = fut.await {
log!("epoxy: error in mux future, restarting: {:?}", e);
while let Err(e) = replace_mux(mux.clone(), &url).await {
log!("epoxy: failed to restart mux future: {:?}", e);
wasmtimer::tokio::sleep(std::time::Duration::from_millis(500)).await;
}
}
debug!("epoxy: mux future exited");
});
}
pub async fn replace_mux(mux: Arc<RwLock<ClientMux>>, url: &str) -> Result<(), WispError> {
let (mux_replace, fut) = make_mux(url).await?;
let mut mux_write = mux.write().await;
let _ = mux_write.close().await;
*mux_write = mux_replace;
drop(mux_write);
spawn_mux_fut(mux, fut, url.into());
Ok(())
}
pub async fn jval_to_u8_array(val: JsValue) -> Result<Uint8Array, JsValue> {
JsFuture::from(
web_sys::Request::new_with_str_and_init(
"/",
web_sys::RequestInit::new().method("POST").body(Some(&val)),
)?
.array_buffer()?,
)
.await?
.dyn_into::<ArrayBuffer>()
.map(|x| Uint8Array::new(&x))
}
pub async fn jval_to_u8_array_req(val: JsValue) -> Result<(Uint8Array, web_sys::Request), JsValue> {
let req = web_sys::Request::new_with_str_and_init(
"/",
web_sys::RequestInit::new().method("POST").body(Some(&val)),
)?;
Ok((
JsFuture::from(req.array_buffer()?)
.await?
.dyn_into::<ArrayBuffer>()
.map(|x| Uint8Array::new(&x))?,
req,
))
}
pub fn object_to_trustanchor(obj: JsValue) -> Result<TrustAnchor<'static>, JsValue> {
let subject: Uint8Array = Reflect::get(&obj, &jval!("subject"))?.dyn_into()?;
let pub_key_info: Uint8Array =
Reflect::get(&obj, &jval!("subject_public_key_info"))?.dyn_into()?;
let name_constraints: Option<Uint8Array> = Reflect::get(&obj, &jval!("name_constraints"))
.and_then(|x| x.dyn_into())
.ok();
Ok(TrustAnchor {
subject: Der::from(subject.to_vec()),
subject_public_key_info: Der::from(pub_key_info.to_vec()),
name_constraints: name_constraints.map(|x| Der::from(x.to_vec())),
})
}