diff --git a/core/archipelago/src/appgate/mod.rs b/core/archipelago/src/appgate/mod.rs index 14cb2499..44b71657 100644 --- a/core/archipelago/src/appgate/mod.rs +++ b/core/archipelago/src/appgate/mod.rs @@ -480,6 +480,44 @@ async fn proxy_to_app( parts.headers.remove(header::AUTHORIZATION); } + // Websocket upgrades need splicing, not request forwarding. hyper::Client + // alone completes the app's 101 handshake and then DROPS the upgraded + // connection, so every ws-driven app (mempool's entire UI is one) loaded + // fine through the gate and then died with close code 1006 on connect — + // reported from a browser console, 2026-08-09, after two other layers of + // the same symptom had already been fixed. The gate's server side already + // accepts client upgrades (.with_upgrades() in listener.rs); this is the + // missing upstream half: hand the handshake to the app and, on 101, bridge + // the two upgraded connections byte-for-byte. Header stripping above still + // applies — the app sees the same sanitized headers on a ws handshake as + // on any other request. + if parts.headers.contains_key(header::UPGRADE) { + // The client side's OnUpgrade handle rides in the request extensions; + // take it before the parts become the upstream request. It resolves + // once serve_connection's with_upgrades hands us the raw socket after + // we return the 101. + let client_upgrade = parts.extensions.remove::(); + let upstream_req = Request::from_parts(parts, Body::empty()); + let client = hyper::Client::new(); + let mut upstream_resp = match client.request(upstream_req).await { + Ok(resp) => resp, + Err(_) => return bad_gateway(), + }; + if upstream_resp.status() == StatusCode::SWITCHING_PROTOCOLS { + if let Some(client_upgrade) = client_upgrade { + let upstream_upgrade = hyper::upgrade::on(&mut upstream_resp); + tokio::spawn(async move { + if let (Ok(mut client_io), Ok(mut app_io)) = + (client_upgrade.await, upstream_upgrade.await) + { + let _ = tokio::io::copy_bidirectional(&mut client_io, &mut app_io).await; + } + }); + } + } + return upstream_resp; + } + let client = hyper::Client::new(); match client.request(Request::from_parts(parts, body)).await { Ok(resp) => resp,