0

I am using Express and HPM to proxy all requests to my website. This is all wrapped together into a little tool I call ws-proxy (ws for web server, not websocket).

One of the things proxied is my PVE/Proxmox Virtual Environment node, which uses secure WebSockets for the xterm.js and NoVNC consoles.

ws-proxy mre

What is weird about this, is after starting ws-proxy, I have about 30 seconds to open a console which will be sustained, but connections after this time will be closed with a 404 Not Found error. In the console, I see

[HPM] Upgrading to WebSocket
[HPM] Upgrading to WebSocket (sometimes up to 4 times)
[HPM] Client disconnected

In my browser, I see the connection returned as 404.

With websocat, I get:

websocat: WebSocketError: Received unexpected status code (404 Not Found)
websocat: error running

After additional debugging, I see something in the stack is sending a 404 and closing the connection, where just afterwards PVE sends the 101 Switching Protocols. This also sometimes causes a write after end error, sometimes socket hangup.

I've spent months looking into this and I have nowhere else to look at this point.

http-proxy-middleware#826 (by me)

404 in inspect element:

error shown in inspect element

error log in console after a recent attempt (error will change)

console showing errors

Full list of steps between client and server:

  • Cloudflare
  • DigitalOcean w/ ssh-forward (not the problem)
  • ws-proxy
  • server

Non-websocket (HTTP) requests work fine. This is with HPM v2 and Node.js v16.


Update 1 After Ryker's answer, I attempted the solution which should have fixed it, but I see something else of concern after setting the logLevel to debug:

0|ws-proxy  | pve.internal.0xlogn.dev ::1 - - [02/Nov/2022:23:17:14 +0000] "POST /api2/json/nodes/proxmox/lxc/105/termproxy HTTP/1.1" 200 487 "https://pve.internal.0xlogn.dev/?console=lxc&vmid=105&node=proxmox&resize=scale&xtermjs=1" "Mozilla/5.0 (X11; Linux x86_64; rv:106.0) Gecko/20100101 Firefox/106.0"
0|ws-proxy  | Upgrade request for vhost pve.internal.0xlogn.dev, proxy out
0|ws-proxy  | [HPM] GET /api2/json/nodes/proxmox/lxc/105/vncwebsocket?port=5900&vncticket=REDACTED -> https://10.0.1.2:8006
0|ws-proxy  | [HPM] GET /api2/json/nodes/proxmox/lxc/105/vncwebsocket?port=5900&vncticket=REDACTED -> http://10.0.1.108:80
0|ws-proxy  | [HPM] Upgrading to WebSocket
0|ws-proxy  | [HPM] Upgrading to WebSocket
0|ws-proxy  | [HPM] Client disconnected
0|ws-proxy  | [HPM] GET /api2/json/cluster/resources -> https://10.0.1.2:8006

Notice the two GET requests? Something is duplicating the request.

My 'upgrade' event listener:

httpsServer.on('upgrade', (req, socket, head) => {
    if (!req.headers.host) {
        console.log('No vhost specified in upgrade request. Ignoring.');
        socket.end();
        return;
    } else {
        console.log(`Upgrade request for vhost ${req.headers.host}, proxy out`);
        vhostProxyMiddlewareList[req.headers.host].upgrade(req, socket, head);
    }
})

What's even weirder here, is after restarting, I get a short time where the request isn't duplicated. Plus, there is a normal HTTP request anyway.


Update 2 After noticing the dual requests, I believe it is possible the module vhost is causing a weird wildcard and sending the request to two target nodes. I will update shortly.


Update 3 After further work I believe this is true. However, vhost is not at fault, rather something is implicitly calling next().


Update 4 This is still an issue, even after multiple attempts at changing this. I have not heard anything back from HPM.

0xLogN
  • 3,289
  • 1
  • 14
  • 35

1 Answers1

1

http-proxy-middleware relies on a initial http request in order to listen to the http upgrade event by default. To proxy WebSockets without the initial http request, you can subscribe to the server's http upgrade event manually.

Add this listener to your http server

const wsProxy = createProxyMiddleware({ target: targetURL, onError, ...PROXY_DEFAULT_OPTIONS, ...addlProxyOptions });

httpsServer.on('upgrade', wsProxy.upgrade); // <-- subscribe to http 'upgrade'
Ryker
  • 792
  • 4
  • 11
  • What do you mean by 'relies on an initial http request'? Could I have an example of how the HTTP flow is expected to go? Trying it now – 0xLogN Nov 02 '22 at 22:39
  • The problem is that there are also multiple other vhosts with websockets, so I need to check the vhost in the httpsServer 'upgrade' event as well. – 0xLogN Nov 02 '22 at 22:40
  • Appears to be buried deep [in the README](https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade). But don't all ws connections require an initial request anyway? – 0xLogN Nov 02 '22 at 22:46
  • I have implemented your solution and the issue is still present. See my edits. – 0xLogN Nov 02 '22 at 23:17