Notes on reverse proxying websocket servers
At work this week, I’ve had need to set up a modern JavaScript application as part of an existing legacy web app (in this case built using IIS and Asp.Net MVC on the old .Net, but the details aren’t especially important). As part of this, I want to be able to use the Vite development server for fast turn around times when making changes to the front end.
For unimportant reasons this needs to be a reverse proxy from IIS to Vite. Simple enough to setup with some rewrite rules in general. The fiddly part has been getting WebSockets playing nice. Websockets are a ubiquitous technology now, and are handled properly by all the major webservers, including with reverse proxy setups, so this was surprising.
The complicating factor is that Vite wants to listen for it’s WebSocket connection on /
, which is already (obviously) a path owned by the legacy application. Were Vite the sole service being reversed proxied this wouldn’t be a problem. Just map /
across and it’ll be fine.
You might think that this would be easy, after all, the WebSocket connection is initiated with a wss://…
(or ws://…
if you somehow live in an unsecured world), surely you can just use that protocol as part of the rewrite rule. But of course, the connection is being made to an http
server, and the protocol has already been extracted from the URL in order to make the request.
The correct thing to do is to intercept the WebSocket handshake negotiation with the rewrite rule. This is done by:
- Matching the path (in my case
/
) for the WebSocket endpoint - Matching the
Upgrade
header and the valuewebsocket
- Matching the
Connection
and the valueUpgrade
. Note thatConnection
can have additional values (such askeep-alive
), so you cannot do an exact match.