add protocol header v2 to server and client

This commit is contained in:
Toshit Chawda 2024-10-24 00:16:11 -07:00
parent cda7ed2190
commit 1ae3986a82
No known key found for this signature in database
GPG key ID: 91480ED99E2B3D9D
8 changed files with 84 additions and 63 deletions

View file

@ -16,6 +16,7 @@ import initEpoxy, { EpoxyClient, EpoxyClientOptions, EpoxyHandlers, info as epox
const test_url = params.get("url"); const test_url = params.get("url");
const wisp_url = params.get("wisp") || "ws://localhost:4000/"; const wisp_url = params.get("wisp") || "ws://localhost:4000/";
const wisp_v1 = params.has("v1"); const wisp_v1 = params.has("v1");
const wisp_udp = params.has("udp_extension");
console.log( console.log(
"%cWASM is significantly slower with DevTools open!", "%cWASM is significantly slower with DevTools open!",
"color:red;font-size:3rem;font-weight:bold" "color:red;font-size:3rem;font-weight:bold"
@ -33,6 +34,7 @@ import initEpoxy, { EpoxyClient, EpoxyClientOptions, EpoxyHandlers, info as epox
let epoxy_client_options = new EpoxyClientOptions(); let epoxy_client_options = new EpoxyClientOptions();
epoxy_client_options.user_agent = navigator.userAgent; epoxy_client_options.user_agent = navigator.userAgent;
epoxy_client_options.wisp_v2 = !wisp_v1; epoxy_client_options.wisp_v2 = !wisp_v1;
epoxy_client_options.udp_extension_required = wisp_udp;
let epoxy_client; let epoxy_client;

View file

@ -27,8 +27,8 @@ use stream_provider::{StreamProvider, StreamProviderService};
use thiserror::Error; use thiserror::Error;
use utils::{ use utils::{
asyncread_to_readablestream, convert_body, entries_of_object, from_entries, is_null_body, asyncread_to_readablestream, convert_body, entries_of_object, from_entries, is_null_body,
is_redirect, object_get, object_set, object_truthy, IncomingBody, UriExt, WasmExecutor, is_redirect, object_get, object_set, object_truthy, ws_protocol, IncomingBody, UriExt,
WispTransportRead, WispTransportWrite, WasmExecutor, WispTransportRead, WispTransportWrite,
}; };
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
@ -118,8 +118,8 @@ pub enum EpoxyError {
#[error("Webpki: {0:?} ({0})")] #[error("Webpki: {0:?} ({0})")]
Webpki(#[from] webpki::Error), Webpki(#[from] webpki::Error),
#[error("Wisp WebSocket failed to connect")] #[error("Wisp WebSocket failed to connect: {0}")]
WebSocketConnectFailed, WebSocketConnectFailed(String),
#[error("Custom Wisp transport: {0}")] #[error("Custom Wisp transport: {0}")]
WispTransport(String), WispTransport(String),
@ -357,14 +357,20 @@ impl EpoxyClient {
let ws_protocols = options.websocket_protocols.clone(); let ws_protocols = options.websocket_protocols.clone();
Arc::new(StreamProvider::new( Arc::new(StreamProvider::new(
Box::new(move || { Box::new(move |wisp_v2| {
let wisp_url = wisp_url.clone(); let wisp_url = wisp_url.clone();
let ws_protocols = ws_protocols.clone(); let mut ws_protocols = ws_protocols.clone();
if wisp_v2 {
// send some random data to ask the server for v2
ws_protocols.push(ws_protocol());
}
Box::pin(async move { Box::pin(async move {
let (write, read) = WebSocketWrapper::connect(&wisp_url, &ws_protocols)?; let (write, read) = WebSocketWrapper::connect(&wisp_url, &ws_protocols)?;
if !write.wait_for_open().await { if !write.wait_for_open().await {
return Err(EpoxyError::WebSocketConnectFailed); return Err(EpoxyError::WebSocketConnectFailed(
"websocket did not open".to_string(),
));
} }
Ok(( Ok((
Box::new(read) as Box<dyn WebSocketRead + Send>, Box::new(read) as Box<dyn WebSocketRead + Send>,
@ -377,7 +383,7 @@ impl EpoxyClient {
} else if let Some(wisp_transport) = transport.dyn_ref::<Function>() { } else if let Some(wisp_transport) = transport.dyn_ref::<Function>() {
let wisp_transport = SendWrapper::new(wisp_transport.clone()); let wisp_transport = SendWrapper::new(wisp_transport.clone());
Arc::new(StreamProvider::new( Arc::new(StreamProvider::new(
Box::new(move || { Box::new(move |_| {
let wisp_transport = wisp_transport.clone(); let wisp_transport = wisp_transport.clone();
Box::pin(SendWrapper::new(async move { Box::pin(SendWrapper::new(async move {
let transport = wisp_transport let transport = wisp_transport
@ -431,10 +437,8 @@ impl EpoxyClient {
.http1_max_headers(options.header_limit); .http1_max_headers(options.header_limit);
#[cfg(feature = "full")] #[cfg(feature = "full")]
builder builder.http2_max_concurrent_reset_streams(10); // set back to default, on wasm it is 0
.http2_max_concurrent_reset_streams(10); // set back to default, on wasm it is 0 let client = builder.build(service);
let client = builder
.build(service);
Ok(Self { Ok(Self {
stream_provider, stream_provider,

View file

@ -31,7 +31,7 @@ pub type ProviderUnencryptedAsyncRW = MuxStreamAsyncRW;
pub type ProviderTlsAsyncRW = IgnoreCloseNotify; pub type ProviderTlsAsyncRW = IgnoreCloseNotify;
pub type ProviderAsyncRW = Either<ProviderTlsAsyncRW, ProviderUnencryptedAsyncRW>; pub type ProviderAsyncRW = Either<ProviderTlsAsyncRW, ProviderUnencryptedAsyncRW>;
pub type ProviderWispTransportGenerator = Box< pub type ProviderWispTransportGenerator = Box<
dyn Fn() -> Pin< dyn Fn(bool) -> Pin<
Box< Box<
dyn Future< dyn Future<
Output = Result< Output = Result<
@ -124,7 +124,7 @@ impl StreamProvider {
None None
}; };
let (read, write) = (self.wisp_generator)().await?; let (read, write) = (self.wisp_generator)(self.wisp_v2).await?;
let client = ClientMux::create(read, write, extensions).await?; let client = ClientMux::create(read, write, extensions).await?;
let (mux, fut) = if self.udp_extension { let (mux, fut) = if self.udp_extension {

View file

@ -417,6 +417,13 @@ export function ws_key() {
return btoa(String.fromCharCode.apply(null, key)); return btoa(String.fromCharCode.apply(null, key));
} }
export function ws_protocol() {
return (
[1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,
c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
export function from_entries(entries){ export function from_entries(entries){
var ret = {}; var ret = {};
for(var i = 0; i < entries.length; i++) ret[entries[i][0]] = entries[i][1]; for(var i = 0; i < entries.length; i++) ret[entries[i][0]] = entries[i][1];
@ -433,6 +440,7 @@ extern "C" {
fn entries_of_object_inner(obj: &Object) -> Vec<Array>; fn entries_of_object_inner(obj: &Object) -> Vec<Array>;
pub fn define_property(obj: &Object, key: &str, val: JsValue); pub fn define_property(obj: &Object, key: &str, val: JsValue);
pub fn ws_key() -> String; pub fn ws_key() -> String;
pub fn ws_protocol() -> String;
#[wasm_bindgen(catch)] #[wasm_bindgen(catch)]
pub fn from_entries(iterable: &JsValue) -> Result<Object, JsValue>; pub fn from_entries(iterable: &JsValue) -> Result<Object, JsValue>;

View file

@ -153,7 +153,7 @@ impl WebSocketWrapper {
.into(), .into(),
) )
} }
.map_err(|_| EpoxyError::WebSocketConnectFailed)?; .map_err(|x| EpoxyError::WebSocketConnectFailed(format!("{:?}", x)))?;
ws.set_binary_type(BinaryType::Arraybuffer); ws.set_binary_type(BinaryType::Arraybuffer);
ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref())); ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
ws.set_onopen(Some(onopen.as_ref().unchecked_ref())); ws.set_onopen(Some(onopen.as_ref().unchecked_ref()));

View file

@ -211,7 +211,7 @@ async fn handle_stream(
} }
} }
pub async fn handle_wisp(stream: WispResult, id: String) -> anyhow::Result<()> { pub async fn handle_wisp(stream: WispResult, is_v2: bool, id: String) -> anyhow::Result<()> {
let (read, write) = stream; let (read, write) = stream;
cfg_if! { cfg_if! {
if #[cfg(feature = "twisp")] { if #[cfg(feature = "twisp")] {
@ -232,11 +232,16 @@ pub async fn handle_wisp(stream: WispResult, id: String) -> anyhow::Result<()> {
} }
} }
let (mux, fut) = ServerMux::create(read, write, buffer_size, extensions) let (mux, fut) = ServerMux::create(
.await read,
.context("failed to create server multiplexor")? write,
.with_required_extensions(&required_extensions) buffer_size,
.await?; if is_v2 { extensions } else { None },
)
.await
.context("failed to create server multiplexor")?
.with_required_extensions(&required_extensions)
.await?;
let mux = Arc::new(mux); let mux = Arc::new(mux);
debug!( debug!(

View file

@ -208,7 +208,7 @@ fn handle_stream(stream: ServerRouteResult, id: String) {
tokio::spawn(async move { tokio::spawn(async move {
CLIENTS.insert(id.clone(), (DashMap::new(), false)); CLIENTS.insert(id.clone(), (DashMap::new(), false));
let res = match stream { let res = match stream {
ServerRouteResult::Wisp(stream) => handle_wisp(stream, id.clone()).await, ServerRouteResult::Wisp(stream, is_v2) => handle_wisp(stream, is_v2, id.clone()).await,
ServerRouteResult::WsProxy(ws, path, udp) => { ServerRouteResult::WsProxy(ws, path, udp) => {
handle_wsproxy(ws, id.clone(), path, udp).await handle_wsproxy(ws, id.clone(), path, udp).await
} }

View file

@ -5,8 +5,8 @@ use bytes::Bytes;
use fastwebsockets::{upgrade::UpgradeFut, FragmentCollector}; use fastwebsockets::{upgrade::UpgradeFut, FragmentCollector};
use http_body_util::Full; use http_body_util::Full;
use hyper::{ use hyper::{
body::Incoming, server::conn::http1::Builder, service::service_fn, HeaderMap, Request, body::Incoming, header::SEC_WEBSOCKET_PROTOCOL, server::conn::http1::Builder,
Response, StatusCode, service::service_fn, HeaderMap, Request, Response, StatusCode,
}; };
use hyper_util::rt::TokioIo; use hyper_util::rt::TokioIo;
use log::{debug, error, trace}; use log::{debug, error, trace};
@ -25,6 +25,25 @@ use crate::{
CONFIG, CONFIG,
}; };
pub type WispResult = (
Box<dyn WebSocketRead + Send>,
Box<dyn WebSocketWrite + Send>,
);
pub enum ServerRouteResult {
Wisp(WispResult, bool),
WsProxy(WebSocketStreamWrapper, String, bool),
}
impl Display for ServerRouteResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Wisp(..) => write!(f, "Wisp"),
Self::WsProxy(_, path, udp) => write!(f, "WsProxy path {:?} udp {:?}", path, udp),
}
}
}
type Body = Full<Bytes>; type Body = Full<Bytes>;
fn non_ws_resp() -> anyhow::Result<Response<Body>> { fn non_ws_resp() -> anyhow::Result<Response<Body>> {
Ok(Response::builder() Ok(Response::builder()
@ -57,7 +76,7 @@ fn get_header(headers: &HeaderMap, header: &str) -> Option<String> {
} }
enum HttpUpgradeResult { enum HttpUpgradeResult {
Wisp, Wisp(bool),
WsProxy(String, bool), WsProxy(String, bool),
} }
@ -90,7 +109,7 @@ where
let (resp, fut) = fastwebsockets::upgrade::upgrade(&mut req)?; let (resp, fut) = fastwebsockets::upgrade::upgrade(&mut req)?;
// replace body of Empty<Bytes> with Full<Bytes> // replace body of Empty<Bytes> with Full<Bytes>
let resp = Response::from_parts(resp.into_parts().0, Body::new(Bytes::new())); let mut resp = Response::from_parts(resp.into_parts().0, Body::new(Bytes::new()));
let headers = req.headers(); let headers = req.headers();
let ip_header = if CONFIG.server.use_real_ip_headers { let ip_header = if CONFIG.server.use_real_ip_headers {
@ -99,25 +118,27 @@ where
None None
}; };
if req let ws_protocol = headers.get(SEC_WEBSOCKET_PROTOCOL);
.uri() let req_path = req.uri().path().to_string();
.path()
.starts_with(&(CONFIG.wisp.prefix.clone() + "/")) if req_path.starts_with(&(CONFIG.wisp.prefix.clone() + "/")) {
{ let has_ws_protocol = ws_protocol.is_some();
tokio::spawn(async move { tokio::spawn(async move {
if let Err(err) = (callback)(fut, HttpUpgradeResult::Wisp, ip_header).await { if let Err(err) =
(callback)(fut, HttpUpgradeResult::Wisp(has_ws_protocol), ip_header).await
{
error!("error while serving client: {:?}", err); error!("error while serving client: {:?}", err);
} }
}); });
if let Some(protocol) = ws_protocol {
resp.headers_mut()
.append(SEC_WEBSOCKET_PROTOCOL, protocol.clone());
}
} else if CONFIG.wisp.allow_wsproxy { } else if CONFIG.wisp.allow_wsproxy {
let udp = req.uri().query().unwrap_or_default() == "?udp"; let udp = req.uri().query().unwrap_or_default() == "?udp";
tokio::spawn(async move { tokio::spawn(async move {
if let Err(err) = (callback)( if let Err(err) =
fut, (callback)(fut, HttpUpgradeResult::WsProxy(req_path, udp), ip_header).await
HttpUpgradeResult::WsProxy(req.uri().path().to_string(), udp),
ip_header,
)
.await
{ {
error!("error while serving client: {:?}", err); error!("error while serving client: {:?}", err);
} }
@ -162,7 +183,7 @@ pub async fn route(
ws.set_auto_pong(false); ws.set_auto_pong(false);
match res { match res {
HttpUpgradeResult::Wisp => { HttpUpgradeResult::Wisp(is_v2) => {
let (read, write) = ws.split(|x| { let (read, write) = ws.split(|x| {
let parts = x let parts = x
.into_inner() .into_inner()
@ -173,10 +194,10 @@ pub async fn route(
}); });
(callback)( (callback)(
ServerRouteResult::Wisp(( ServerRouteResult::Wisp(
Box::new(read), (Box::new(read), Box::new(write)),
Box::new(write), is_v2,
)), ),
maybe_ip, maybe_ip,
) )
} }
@ -208,29 +229,10 @@ pub async fn route(
let write = GenericWebSocketWrite::new(FramedWrite::new(write, codec)); let write = GenericWebSocketWrite::new(FramedWrite::new(write, codec));
(callback)( (callback)(
ServerRouteResult::Wisp((Box::new(read), Box::new(write))), ServerRouteResult::Wisp((Box::new(read), Box::new(write)), true),
None, None,
); );
} }
} }
Ok(()) Ok(())
} }
pub type WispResult = (
Box<dyn WebSocketRead + Send>,
Box<dyn WebSocketWrite + Send>,
);
pub enum ServerRouteResult {
Wisp(WispResult),
WsProxy(WebSocketStreamWrapper, String, bool),
}
impl Display for ServerRouteResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Wisp(_) => write!(f, "Wisp"),
Self::WsProxy(_, path, udp) => write!(f, "WsProxy path {:?} udp {:?}", path, udp),
}
}
}