various small improvements

This commit is contained in:
Toshit Chawda 2024-02-28 23:08:56 -08:00
parent 8b2a8a3eb3
commit 5be02151e6
No known key found for this signature in database
GPG key ID: 91480ED99E2B3D9D
11 changed files with 146 additions and 105 deletions

View file

@ -9,8 +9,12 @@ importScripts("epoxy-bundled.js");
const { EpoxyClient } = await epoxy(); const { EpoxyClient } = await epoxy();
let client = await new EpoxyClient("wss://localhost:4000", navigator.userAgent, 10); let client = await new EpoxyClient("wss://localhost:4000", navigator.userAgent, 10);
// You can view and change the user agent and redirect limit
console.log(client.userAgent);
client.redirect_limit = 5;
let response = await client.fetch("https://httpbin.org/get"); let response = await client.fetch("https://httpbin.org/get");
await response.text(); console.log(await response.text());
``` ```
See `client/demo.js` for more examples. See `client/demo.js` for more examples.

View file

@ -12,7 +12,7 @@ http = "1.0.0"
http-body-util = "0.1.0" http-body-util = "0.1.0"
hyper = { version = "1.1.0", features = ["client", "http1", "http2"] } hyper = { version = "1.1.0", features = ["client", "http1", "http2"] }
pin-project-lite = "0.2.13" pin-project-lite = "0.2.13"
wasm-bindgen = "0.2" wasm-bindgen = { version = "0.2.91", features = ["enable-interning"] }
wasm-bindgen-futures = "0.4.39" wasm-bindgen-futures = "0.4.39"
ws_stream_wasm = { version = "0.7.4", features = ["tokio_io"] } ws_stream_wasm = { version = "0.7.4", features = ["tokio_io"] }
futures-util = "0.3.30" futures-util = "0.3.30"

View file

@ -27,7 +27,7 @@ echo "module.exports = epoxy" >> epoxy-module-bundled.js
AUTOGENERATED_TYPEDEFS=$(<"out/epoxy_client.d.ts") AUTOGENERATED_TYPEDEFS=$(<"out/epoxy_client.d.ts")
AUTOGENERATED_TYPEDEFS=${AUTOGENERATED_TYPEDEFS%%export class IntoUnderlyingByteSource*} AUTOGENERATED_TYPEDEFS=${AUTOGENERATED_TYPEDEFS%%export class IntoUnderlyingByteSource*}
echo "$AUTOGENERATED_TYPEDEFS" >"epoxy-module-bundled.d.ts" echo "$AUTOGENERATED_TYPEDEFS" >"epoxy-module-bundled.d.ts"
echo "} export default function epoxy(): Promise<typeof wasm_bindgen>;" >> "epoxy-module-bundled.d.ts" echo "} export default function epoxy(maybe_memory?: WebAssembly.Memory): Promise<typeof wasm_bindgen>;" >> "epoxy-module-bundled.d.ts"
cp out/epoxy_client.js epoxy.js cp out/epoxy_client.js epoxy.js
cp out/epoxy_client.d.ts epoxy.d.ts cp out/epoxy_client.d.ts epoxy.d.ts

View file

@ -1,6 +1,6 @@
importScripts("epoxy-bundled.js"); importScripts("epoxy-bundled.js");
onmessage = async (msg) => { onmessage = async (msg) => {
console.debug("recieved:", msg); console.debug("recieved demo:", msg);
let [should_feature_test, should_multiparallel_test, should_parallel_test, should_multiperf_test, should_perf_test, should_ws_test, should_tls_test] = msg.data; let [should_feature_test, should_multiparallel_test, should_parallel_test, should_multiperf_test, should_perf_test, should_ws_test, should_tls_test] = msg.data;
console.log( console.log(
"%cWASM is significantly slower with DevTools open!", "%cWASM is significantly slower with DevTools open!",
@ -8,10 +8,15 @@ onmessage = async (msg) => {
); );
const log = (str) => { const log = (str) => {
console.warn(str); console.log(str);
postMessage(str); postMessage(str);
} }
const plog = (str) => {
console.log(str);
postMessage(JSON.stringify(str, null, 4));
}
const { EpoxyClient } = await epoxy(); const { EpoxyClient } = await epoxy();
const tconn0 = performance.now(); const tconn0 = performance.now();
@ -20,6 +25,11 @@ onmessage = async (msg) => {
const tconn1 = performance.now(); const tconn1 = performance.now();
log(`conn establish took ${tconn1 - tconn0} ms or ${(tconn1 - tconn0) / 1000} s`); log(`conn establish took ${tconn1 - tconn0} ms or ${(tconn1 - tconn0) / 1000} s`);
// epoxy classes are inspectable
console.log(epoxy_client);
// you can change the user agent and redirect limit in JS
epoxy_client.redirectLimit = 15;
const test_mux = async (url) => { const test_mux = async (url) => {
const t0 = performance.now(); const t0 = performance.now();
await epoxy_client.fetch(url); await epoxy_client.fetch(url);
@ -138,23 +148,24 @@ onmessage = async (msg) => {
log(`avg mux - avg native (${num_tests}): ${total_mux - total_native} ms or ${(total_mux - total_native) / 1000} s`); log(`avg mux - avg native (${num_tests}): ${total_mux - total_native} ms or ${(total_mux - total_native) / 1000} s`);
} else if (should_ws_test) { } else if (should_ws_test) {
let ws = await epoxy_client.connect_ws( let ws = await epoxy_client.connect_ws(
() => console.log("opened"), () => log("opened"),
() => console.log("closed"), () => log("closed"),
err => console.error(err), err => console.error(err),
msg => console.log(msg), msg => log(msg),
"wss://echo.websocket.events", "wss://echo.websocket.events",
[], [],
"localhost" "localhost"
); );
while (true) { while (true) {
log("sending `data`");
await ws.send("data"); await ws.send("data");
await (new Promise((res, _) => setTimeout(res, 100))); await (new Promise((res, _) => setTimeout(res, 50)));
} }
} else if (should_tls_test) { } else if (should_tls_test) {
let decoder = new TextDecoder(); let decoder = new TextDecoder();
let ws = await epoxy_client.connect_tls( let ws = await epoxy_client.connect_tls(
() => console.log("opened"), () => log("opened"),
() => console.log("closed"), () => log("closed"),
err => console.error(err), err => console.error(err),
msg => { console.log(msg); console.log(decoder.decode(msg)) }, msg => { console.log(msg); console.log(decoder.decode(msg)) },
"alicesworld.tech:443", "alicesworld.tech:443",
@ -163,8 +174,8 @@ onmessage = async (msg) => {
await ws.close(); await ws.close();
} else { } else {
let resp = await epoxy_client.fetch("https://httpbin.org/get"); let resp = await epoxy_client.fetch("https://httpbin.org/get");
console.warn(resp, Object.fromEntries(resp.headers)); console.log(resp, Object.fromEntries(resp.headers));
console.warn(await resp.text()); plog(await resp.json());
} }
log("done"); log("done");
}; };

View file

@ -3,8 +3,9 @@
<head> <head>
<title>epoxy</title> <title>epoxy</title>
<style> <style>
body { font-family: sans-serif } body { font-family: monospace; font-weight: bold; width: 100%; height: 100%; padding: 0; margin: 0 }
#logs > * { font-family: monospace } body > div { padding: 1em; }
#logs > * { margin: 0; font-weight: normal; }
</style> </style>
<script> <script>
const params = (new URL(window.location.href)).searchParams; const params = (new URL(window.location.href)).searchParams;
@ -17,8 +18,8 @@
const should_tls_test = params.has("rawtls_test"); const should_tls_test = params.has("rawtls_test");
const worker = new Worker("demo.js"); const worker = new Worker("demo.js");
worker.onmessage = (msg) => { worker.onmessage = (msg) => {
let el = document.createElement("div"); let el = document.createElement("pre");
el.textContent = msg.data; el.innerHTML = msg.data;
document.getElementById("logs").appendChild(el); document.getElementById("logs").appendChild(el);
window.scrollTo(0, document.body.scrollHeight); window.scrollTo(0, document.body.scrollHeight);
}; };
@ -28,9 +29,10 @@
<body> <body>
<div> <div>
running... (wait for the browser alert if not running ws test) <div>running... (note: WASM is significantly slower when DevTools is open)</div>
<div>logs:</div>
<div id="logs"></div>
</div> </div>
<div id="logs"></div>
</body> </body>
</html> </html>

View file

@ -6,7 +6,7 @@ mod websocket;
mod wrappers; mod wrappers;
use tls_stream::EpxTlsStream; use tls_stream::EpxTlsStream;
use utils::{ReplaceErr, UriExt}; use utils::{Boolinator, ReplaceErr, UriExt};
use websocket::EpxWebSocket; use websocket::EpxWebSocket;
use wrappers::{IncomingBody, TlsWispService}; use wrappers::{IncomingBody, TlsWispService};
@ -25,7 +25,7 @@ use tokio_util::{
either::Either, either::Either,
io::{ReaderStream, StreamReader}, io::{ReaderStream, StreamReader},
}; };
use wasm_bindgen::prelude::*; use wasm_bindgen::{intern, prelude::*};
use web_sys::TextEncoder; use web_sys::TextEncoder;
use wisp_mux::{tokioio::TokioIo, tower::ServiceWrapper, ClientMux, MuxStreamIo, StreamType}; use wisp_mux::{tokioio::TokioIo, tower::ServiceWrapper, ClientMux, MuxStreamIo, StreamType};
use ws_stream_wasm::{WsMessage, WsMeta, WsStream}; use ws_stream_wasm::{WsMessage, WsMeta, WsStream};
@ -52,13 +52,16 @@ fn init() {
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
} }
#[wasm_bindgen]
#[wasm_bindgen(inspectable)]
pub struct EpoxyClient { pub struct EpoxyClient {
rustls_config: Arc<rustls::ClientConfig>, rustls_config: Arc<rustls::ClientConfig>,
mux: Arc<ClientMux<SplitSink<WsStream, WsMessage>>>, mux: Arc<ClientMux<SplitSink<WsStream, WsMessage>>>,
hyper_client: Client<TlsWispService<SplitSink<WsStream, WsMessage>>, HttpBody>, hyper_client: Client<TlsWispService<SplitSink<WsStream, WsMessage>>, HttpBody>,
useragent: String, #[wasm_bindgen(getter_with_clone)]
redirect_limit: usize, pub useragent: String,
#[wasm_bindgen(js_name = "redirectLimit")]
pub redirect_limit: usize,
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -81,10 +84,11 @@ impl EpoxyClient {
} }
debug!("connecting to ws {:?}", ws_url); debug!("connecting to ws {:?}", ws_url);
let (_, ws) = WsMeta::connect(ws_url, vec!["wisp-v1"]) let (_, ws) = WsMeta::connect(ws_url, vec![])
.await .await
.replace_err("Failed to connect to websocket")?; .replace_err("Failed to connect to websocket")?;
debug!("connected!"); debug!("connected!");
let (wtx, wrx) = ws.split(); let (wtx, wrx) = ws.split();
let (mux, fut) = ClientMux::new(wrx, wtx).await?; let (mux, fut) = ClientMux::new(wrx, wtx).await?;
let mux = Arc::new(mux); let mux = Arc::new(mux);
@ -128,19 +132,17 @@ impl EpoxyClient {
.replace_err("Failed to create multiplexor channel")? .replace_err("Failed to create multiplexor channel")?
.into_io() .into_io()
.into_asyncrw(); .into_asyncrw();
let cloned_uri = url_host.to_string().clone();
let connector = TlsConnector::from(self.rustls_config.clone()); let connector = TlsConnector::from(self.rustls_config.clone());
debug!("connecting channel");
let io = connector let io = connector
.connect( .connect(
cloned_uri url_host
.to_string()
.try_into() .try_into()
.replace_err("Failed to parse URL (rustls)")?, .replace_err("Failed to parse URL (rustls)")?,
channel, channel,
) )
.await .await
.replace_err("Failed to perform TLS handshake")?; .replace_err("Failed to perform TLS handshake")?;
debug!("connected channel");
Ok(io) Ok(io)
} }
@ -155,24 +157,18 @@ impl EpoxyClient {
None None
}; };
debug!("sending req");
let res = self let res = self
.hyper_client .hyper_client
.request(req) .request(req)
.await .await
.replace_err("Failed to send request"); .replace_err("Failed to send request");
debug!("recieved res");
match res { match res {
Ok(res) => { Ok(res) => {
if utils::is_redirect(res.status().as_u16()) if utils::is_redirect(res.status().as_u16())
&& let Some(mut new_req) = new_req && let Some(mut new_req) = new_req
&& let Some(location) = res.headers().get("Location") && let Some(location) = res.headers().get("Location")
&& let Ok(redirect_url) = new_req.uri().get_redirect(location) && let Ok(redirect_url) = new_req.uri().get_redirect(location)
&& let Some(redirect_url_authority) = redirect_url && let Some(redirect_url_authority) = redirect_url.clone().authority()
.clone()
.authority()
.replace_err("Redirect URL must have an authority")
.ok()
{ {
*new_req.uri_mut() = redirect_url; *new_req.uri_mut() = redirect_url;
new_req.headers_mut().insert( new_req.headers_mut().insert(
@ -246,19 +242,18 @@ impl EpoxyClient {
let uri = url.parse::<uri::Uri>().replace_err("Failed to parse URL")?; let uri = url.parse::<uri::Uri>().replace_err("Failed to parse URL")?;
let uri_scheme = uri.scheme().replace_err("URL must have a scheme")?; let uri_scheme = uri.scheme().replace_err("URL must have a scheme")?;
if *uri_scheme != uri::Scheme::HTTP && *uri_scheme != uri::Scheme::HTTPS { if *uri_scheme != uri::Scheme::HTTP && *uri_scheme != uri::Scheme::HTTPS {
return Err(jerr!("Scheme must be either `http` or `https`")); return Err(jerri!("Scheme must be either `http` or `https`"));
} }
let uri_host = uri.host().replace_err("URL must have a host")?; let uri_host = uri.host().replace_err("URL must have a host")?;
let req_method_string: String = match Reflect::get(&options, &jval!("method")) { let req_method_string: String = match Reflect::get(&options, &jvali!("method")) {
Ok(val) => val.as_string().unwrap_or("GET".to_string()), Ok(val) => val.as_string().unwrap_or("GET".to_string()),
Err(_) => "GET".to_string(), Err(_) => "GET".to_string(),
}; };
let req_method: http::Method = let req_method: http::Method = http::Method::try_from(req_method_string.as_str())
http::Method::try_from(<String as AsRef<str>>::as_ref(&req_method_string)) .replace_err("Invalid http method")?;
.replace_err("Invalid http method")?;
let req_should_redirect = match Reflect::get(&options, &jval!("redirect")) { let req_should_redirect = match Reflect::get(&options, &jvali!("redirect")) {
Ok(val) => !matches!( Ok(val) => !matches!(
val.as_string().unwrap_or_default().as_str(), val.as_string().unwrap_or_default().as_str(),
"error" | "manual" "error" | "manual"
@ -266,7 +261,7 @@ impl EpoxyClient {
Err(_) => true, Err(_) => true,
}; };
let body_jsvalue: Option<JsValue> = Reflect::get(&options, &jval!("body")).ok(); let body_jsvalue: Option<JsValue> = Reflect::get(&options, &jvali!("body")).ok();
let body = if let Some(val) = body_jsvalue { let body = if let Some(val) = body_jsvalue {
if val.is_string() { if val.is_string() {
let str = val let str = val
@ -288,7 +283,7 @@ impl EpoxyClient {
None => Bytes::new(), None => Bytes::new(),
}; };
let headers: Option<Vec<Vec<String>>> = Reflect::get(&options, &jval!("headers")) let headers = Reflect::get(&options, &jvali!("headers"))
.map(|val| { .map(|val| {
if val.is_truthy() { if val.is_truthy() {
Some(utils::entries_of_object(&Object::from(val))) Some(utils::entries_of_object(&Object::from(val)))
@ -301,12 +296,12 @@ impl EpoxyClient {
let mut builder = Request::builder().uri(uri.clone()).method(req_method); let mut builder = Request::builder().uri(uri.clone()).method(req_method);
let headers_map = builder.headers_mut().replace_err("Failed to get headers")?; let headers_map = builder.headers_mut().replace_err("Failed to get headers")?;
headers_map.insert("Accept-Encoding", HeaderValue::from_str("gzip, br")?); headers_map.insert("Accept-Encoding", HeaderValue::from_static("gzip, br"));
headers_map.insert("Connection", HeaderValue::from_str("keep-alive")?); headers_map.insert("Connection", HeaderValue::from_static("keep-alive"));
headers_map.insert("User-Agent", HeaderValue::from_str(&self.useragent)?); headers_map.insert("User-Agent", HeaderValue::from_str(&self.useragent)?);
headers_map.insert("Host", HeaderValue::from_str(uri_host)?); headers_map.insert("Host", HeaderValue::from_str(uri_host)?);
if body_bytes.is_empty() { if body_bytes.is_empty() {
headers_map.insert("Content-Length", HeaderValue::from_str("0")?); headers_map.insert("Content-Length", HeaderValue::from_static("0"));
} }
if let Some(headers) = headers { if let Some(headers) = headers {
@ -314,7 +309,7 @@ impl EpoxyClient {
headers_map.insert( headers_map.insert(
HeaderName::from_bytes(hdr[0].as_bytes()) HeaderName::from_bytes(hdr[0].as_bytes())
.replace_err("Failed to get hdr name")?, .replace_err("Failed to get hdr name")?,
HeaderValue::from_str(hdr[1].clone().as_ref()) HeaderValue::from_bytes(hdr[1].as_bytes())
.replace_err("Failed to get hdr value")?, .replace_err("Failed to get hdr value")?,
); );
} }
@ -387,45 +382,41 @@ impl EpoxyClient {
Object::define_property( Object::define_property(
&resp, &resp,
&jval!("url"), &jvali!("url"),
&utils::define_property_obj(jval!(resp_uri.to_string()), false) &utils::define_property_obj(jval!(resp_uri.to_string()), false)
.replace_err("Failed to make define_property object for url")?, .replace_err("Failed to make define_property object for url")?,
); );
Object::define_property( Object::define_property(
&resp, &resp,
&jval!("redirected"), &jvali!("redirected"),
&utils::define_property_obj(jval!(req_redirected), false) &utils::define_property_obj(jval!(req_redirected), false)
.replace_err("Failed to make define_property object for redirected")?, .replace_err("Failed to make define_property object for redirected")?,
); );
let raw_headers = Object::new(); let raw_headers = Object::new();
for (k, v) in resp_headers_raw.iter() { for (k, v) in resp_headers_raw.iter() {
if let Ok(jv) = Reflect::get(&raw_headers, &jval!(k.to_string())) { let k = jval!(k.to_string());
let v = jval!(v.to_str()?.to_string());
if let Ok(jv) = Reflect::get(&raw_headers, &k) {
if jv.is_array() { if jv.is_array() {
let arr = Array::from(&jv); let arr = Array::from(&jv);
arr.push(&jval!(v.to_str()?.to_string())); arr.push(&v);
let _ = Reflect::set(&raw_headers, &jval!(k.to_string()), &arr); Reflect::set(&raw_headers, &k, &arr).flatten("Failed to set rawHeader")?;
} else if jv.is_truthy() { } else if jv.is_truthy() {
let _ = Reflect::set( Reflect::set(&raw_headers, &k, &Array::of2(&jv, &v))
&raw_headers, .flatten("Failed to set rawHeader")?;
&jval!(k.to_string()),
&Array::of2(&jv, &jval!(v.to_str()?.to_string())),
);
} else { } else {
let _ = Reflect::set( Reflect::set(&raw_headers, &k, &v).flatten("Failed to set rawHeader")?;
&raw_headers,
&jval!(k.to_string()),
&jval!(v.to_str()?.to_string()),
);
} }
} }
} }
Object::define_property( Object::define_property(
&resp, &resp,
&jval!("rawHeaders"), &jvali!("rawHeaders"),
&utils::define_property_obj(jval!(&raw_headers), false).replace_err("wjat!!")?, &utils::define_property_obj(jval!(&raw_headers), false)
.replace_err("Failed to make define_property object for rawHeaders")?,
); );
Ok(resp) Ok(resp)

View file

@ -4,10 +4,12 @@ use js_sys::Function;
use tokio::io::{split, AsyncWriteExt, WriteHalf}; use tokio::io::{split, AsyncWriteExt, WriteHalf};
use tokio_util::io::ReaderStream; use tokio_util::io::ReaderStream;
#[wasm_bindgen] #[wasm_bindgen(inspectable)]
pub struct EpxTlsStream { pub struct EpxTlsStream {
tx: WriteHalf<EpxIoTlsStream>, tx: WriteHalf<EpxIoTlsStream>,
onerror: Function, onerror: Function,
#[wasm_bindgen(readonly, getter_with_clone)]
pub url: String,
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -51,7 +53,7 @@ impl EpxTlsStream {
.call0(&Object::default()) .call0(&Object::default())
.replace_err("Failed to call onopen")?; .replace_err("Failed to call onopen")?;
Ok(Self { tx, onerror }) Ok(Self { tx, onerror, url: url.to_string() })
} }
.await; .await;
if let Err(ret) = ret { if let Err(ret) = ret {

View file

@ -1,4 +1,4 @@
use wasm_bindgen::prelude::*; use wasm_bindgen::{intern, prelude::*};
use hyper::rt::Executor; use hyper::rt::Executor;
use hyper::{header::HeaderValue, Uri}; use hyper::{header::HeaderValue, Uri};
@ -15,7 +15,6 @@ extern "C" {
pub fn console_error(s: &str); pub fn console_error(s: &str);
} }
#[allow(unused_macros)]
macro_rules! debug { macro_rules! debug {
($($t:tt)*) => (utils::console_debug(&format_args!($($t)*).to_string())) ($($t:tt)*) => (utils::console_debug(&format_args!($($t)*).to_string()))
} }
@ -25,25 +24,34 @@ macro_rules! log {
($($t:tt)*) => (utils::console_log(&format_args!($($t)*).to_string())) ($($t:tt)*) => (utils::console_log(&format_args!($($t)*).to_string()))
} }
#[allow(unused_macros)]
macro_rules! error { macro_rules! error {
($($t:tt)*) => (utils::console_error(&format_args!($($t)*).to_string())) ($($t:tt)*) => (utils::console_error(&format_args!($($t)*).to_string()))
} }
#[allow(unused_macros)]
macro_rules! jerr { macro_rules! jerr {
($expr:expr) => { ($expr:expr) => {
JsError::new($expr) JsError::new($expr)
}; };
} }
#[allow(unused_macros)]
macro_rules! jval { macro_rules! jval {
($expr:expr) => { ($expr:expr) => {
JsValue::from($expr) JsValue::from($expr)
}; };
} }
macro_rules! jerri {
($expr:expr) => {
JsError::new(intern($expr))
};
}
macro_rules! jvali {
($expr:expr) => {
JsValue::from(intern($expr))
};
}
pub trait ReplaceErr { pub trait ReplaceErr {
type Ok; type Ok;
@ -55,11 +63,11 @@ impl<T, E: std::fmt::Debug> ReplaceErr for Result<T, E> {
type Ok = T; type Ok = T;
fn replace_err(self, err: &str) -> Result<<Self as ReplaceErr>::Ok, JsError> { fn replace_err(self, err: &str) -> Result<<Self as ReplaceErr>::Ok, JsError> {
self.map_err(|oe| jerr!(&format!("{}, original error: {:?}", err, oe))) self.map_err(|_| jerri!(err))
} }
fn replace_err_jv(self, err: &str) -> Result<<Self as ReplaceErr>::Ok, JsValue> { fn replace_err_jv(self, err: &str) -> Result<<Self as ReplaceErr>::Ok, JsValue> {
self.map_err(|oe| jval!(&format!("{}, original error: {:?}", err, oe))) self.map_err(|_| jvali!(err))
} }
} }
@ -67,17 +75,52 @@ impl<T> ReplaceErr for Option<T> {
type Ok = T; type Ok = T;
fn replace_err(self, err: &str) -> Result<<Self as ReplaceErr>::Ok, JsError> { fn replace_err(self, err: &str) -> Result<<Self as ReplaceErr>::Ok, JsError> {
self.ok_or_else(|| jerr!(err)) self.ok_or_else(|| jerri!(err))
} }
fn replace_err_jv(self, err: &str) -> Result<<Self as ReplaceErr>::Ok, JsValue> { fn replace_err_jv(self, err: &str) -> Result<<Self as ReplaceErr>::Ok, JsValue> {
self.ok_or_else(|| jval!(err)) self.ok_or_else(|| jvali!(err))
}
}
// the... BOOLINATOR!
impl ReplaceErr for bool {
type Ok = ();
fn replace_err(self, err: &str) -> Result<(), JsError> {
if !self {
Err(jerri!(err))
} else {
Ok(())
}
}
fn replace_err_jv(self, err: &str) -> Result<(), JsValue> {
if !self {
Err(jvali!(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> {
if !self.replace_err(err)? {
Err(jerri!(err))
} else {
Ok(())
}
} }
} }
pub trait UriExt { pub trait UriExt {
fn get_redirect(&self, location: &HeaderValue) -> Result<Uri, JsError>; fn get_redirect(&self, location: &HeaderValue) -> Result<Uri, JsError>;
fn is_same_host(&self, other: &Uri) -> bool;
} }
impl UriExt for Uri { impl UriExt for Uri {
@ -93,9 +136,6 @@ impl UriExt for Uri {
Ok(Uri::from_parts(new_parts)?) Ok(Uri::from_parts(new_parts)?)
} }
fn is_same_host(&self, other: &Uri) -> bool {
self.host() == other.host() && self.port() == other.port()
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -129,8 +169,8 @@ pub fn entries_of_object(obj: &Object) -> Vec<Vec<String>> {
pub fn define_property_obj(value: JsValue, writable: bool) -> Result<Object, JsValue> { pub fn define_property_obj(value: JsValue, writable: bool) -> Result<Object, JsValue> {
let entries: Array = [ let entries: Array = [
Array::of2(&jval!("value"), &jval!(value)), Array::of2(&jval!(intern("value")), &value),
Array::of2(&jval!("writable"), &jval!(writable)), Array::of2(&jval!(intern("writable")), &jval!(writable)),
] ]
.iter() .iter()
.collect::<Array>(); .collect::<Array>();

View file

@ -15,10 +15,16 @@ use js_sys::Function;
use std::str::from_utf8; use std::str::from_utf8;
use tokio::io::WriteHalf; use tokio::io::WriteHalf;
#[wasm_bindgen] #[wasm_bindgen(inspectable)]
pub struct EpxWebSocket { pub struct EpxWebSocket {
tx: Arc<Mutex<WebSocketWrite<WriteHalf<TokioIo<Upgraded>>>>>, tx: Arc<Mutex<WebSocketWrite<WriteHalf<TokioIo<Upgraded>>>>>,
onerror: Function, onerror: Function,
#[wasm_bindgen(readonly, getter_with_clone)]
pub url: String,
#[wasm_bindgen(readonly, getter_with_clone)]
pub protocols: Vec<String>,
#[wasm_bindgen(readonly, getter_with_clone)]
pub origin: String,
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -53,7 +59,7 @@ impl EpxWebSocket {
.method("GET") .method("GET")
.uri(url.clone()) .uri(url.clone())
.header("Host", host) .header("Host", host)
.header("Origin", origin) .header("Origin", origin.clone())
.header(UPGRADE, "websocket") .header(UPGRADE, "websocket")
.header(CONNECTION, "upgrade") .header(CONNECTION, "upgrade")
.header("Sec-WebSocket-Key", key) .header("Sec-WebSocket-Key", key)
@ -109,7 +115,7 @@ impl EpxWebSocket {
.call0(&Object::default()) .call0(&Object::default())
.replace_err("Failed to call onopen")?; .replace_err("Failed to call onopen")?;
Ok(Self { tx, onerror }) Ok(Self { tx, onerror, origin, protocols, url: url.to_string() })
} }
.await; .await;
if let Err(ret) = ret { if let Err(ret) = ret {

View file

@ -9,8 +9,7 @@ use fastwebsockets::{
}; };
use futures_util::{SinkExt, StreamExt, TryFutureExt}; use futures_util::{SinkExt, StreamExt, TryFutureExt};
use hyper::{ use hyper::{
body::Incoming, header::HeaderValue, server::conn::http1, service::service_fn, Request, body::Incoming, server::conn::http1, service::service_fn, Request, Response, StatusCode,
Response, StatusCode,
}; };
use hyper_util::rt::TokioIo; use hyper_util::rt::TokioIo;
use tokio::net::{TcpListener, TcpStream, UdpSocket}; use tokio::net::{TcpListener, TcpStream, UdpSocket};
@ -88,24 +87,10 @@ async fn accept_http(
if upgrade::is_upgrade_request(&req) if upgrade::is_upgrade_request(&req)
&& let Some(uri) = uri.strip_prefix(&prefix) && let Some(uri) = uri.strip_prefix(&prefix)
{ {
let (mut res, fut) = upgrade::upgrade(&mut req)?; let (res, fut) = upgrade::upgrade(&mut req)?;
if let Some(protocols) = req.headers().get("Sec-Websocket-Protocol").and_then(|x| { if uri.is_empty() || uri == "/" {
Some(
x.to_str()
.ok()?
.split(',')
.map(|x| x.trim())
.collect::<Vec<&str>>(),
)
}) && protocols.contains(&"wisp-v1")
&& (uri.is_empty() || uri == "/")
{
tokio::spawn(async move { accept_ws(fut, addr.clone()).await }); tokio::spawn(async move { accept_ws(fut, addr.clone()).await });
res.headers_mut().insert(
"Sec-Websocket-Protocol",
HeaderValue::from_str("wisp-v1").unwrap(),
);
} else { } else {
let uri = uri.strip_prefix('/').unwrap_or(uri).to_string(); let uri = uri.strip_prefix('/').unwrap_or(uri).to_string();
tokio::spawn(async move { accept_wsproxy(fut, uri, addr.clone()).await }); tokio::spawn(async move { accept_wsproxy(fut, uri, addr.clone()).await });
@ -119,7 +104,7 @@ async fn accept_http(
println!("random request to path {:?}", uri); println!("random request to path {:?}", uri);
Ok(Response::builder() Ok(Response::builder()
.status(StatusCode::OK) .status(StatusCode::OK)
.body(HttpBody::new(":3".to_string().into())) .body(HttpBody::new(":3".into()))
.unwrap()) .unwrap())
} }
} }

View file

@ -74,7 +74,7 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
}; };
let req = Request::builder() let req = Request::builder()
.method("GET") .method("GET")
.uri(format!("wss://{}:{}/", &addr, addr_port)) .uri("/")
.header("Host", &addr) .header("Host", &addr)
.header(UPGRADE, "websocket") .header(UPGRADE, "websocket")
.header(CONNECTION, "upgrade") .header(CONNECTION, "upgrade")