fix: scope websocket upgrades to the proxy prefix - #475
Conversation
The plugin registers a single server-wide `upgrade` listener that dispatches every WebSocket upgrade through `fastify.routing`, regardless of the prefix the proxy is mounted on. When the proxy shares the server with another `upgrade` handler (for example `@fastify/websocket`), it consumes and answers upgrades it does not own, breaking those unrelated WebSocket endpoints. Only handle upgrades whose path falls within a registered proxy prefix when the proxy coexists with other `upgrade` listeners. When the proxy is the only listener, behaviour is unchanged: every upgrade is routed as before, so out-of-prefix requests are still rejected normally. Closes fastify#414
|
|
||
| // The root proxy owns every path, so an arbitrary path is still proxied. | ||
| assert.strictEqual(await connect(port, '/anything'), 'proxied:hello') | ||
| }) |
There was a problem hiding this comment.
| }) | |
| }) | |
| test('multiple proxies each own their own prefix', async (t) => { | |
| const upstream = await createEchoUpstream(t) | |
| const server = Fastify() | |
| server.register(proxy, { prefix: '/pub', upstream, websocket: true }) | |
| server.register(proxy, { prefix: '/api', upstream, websocket: true }) | |
| await server.listen({ port: 0, host: '127.0.0.1' }) | |
| t.after(() => { server.close() }) | |
| const port = server.server.address().port | |
| mountStandalone(t, server, '/standalone') | |
| assert.strictEqual(await connect(port, '/standalone'), 'standalone:hello') | |
| assert.strictEqual(await connect(port, '/pub/x'), 'proxied:hello') | |
| assert.strictEqual(await connect(port, '/api/x'), 'proxied:hello') | |
| }) |
There was a problem hiding this comment.
Added this in 508cc3a, thanks. The new test covers two prefixed websocket proxies on the same Fastify server plus a standalone /standalone upgrade listener, so /pub/x and /api/x both stay proxied while the standalone path is left alone.
Validation:
node --test test/ws-upgrade-prefix.jsnpm run lint -- test/ws-upgrade-prefix.js
|
I checked this one again. If I keep only the tests and drop the Do you mean you want this split as a test-only PR first, or is there another existing implementation path you want the test to cover? |
|
Thanks for the review. I rechecked the branch: if I drop the change and keep only the tests, the new websocket-prefix cases fail with , so the current behavior does not cover this scenario yet. Do you want me to split this into a test-only PR first, or is there another implementation path you had in mind for the websocket-prefix case? |
|
Thanks for the review. I rechecked the branch: if I drop the index.js change and keep only the tests, the new websocket-prefix cases fail with Unexpected server response: 404, so the current main behavior does not cover this scenario yet. Do you want me to split this into a test-only PR first, or is there another implementation path you had in mind for the websocket-prefix case? |
Fixes #414.
@fastify/http-proxyregisters a single server-wideupgradelistener that dispatches every WebSocket upgrade throughfastify.routing, regardless of the prefix the proxy is mounted on. When the proxy shares its server with anotherupgradehandler (e.g.@fastify/websocket), it consumes and answers upgrades it does not own, corrupting those unrelated WebSocket endpoints (the reporter's "existing handlers stop working").This scopes the shared upgrade listener: when the proxy coexists with other
upgradelisteners, it only handles upgrades whose path falls within a registered proxy prefix and leaves the rest untouched for the owning handler. When the proxy is the soleupgradelistener, behaviour is unchanged — every upgrade is routed exactly as before, so out-of-prefix requests are still rejected normally (existingws-prefix-rewritebehaviour is preserved).Added
test/ws-upgrade-prefix.js, which fails onmain(an out-of-prefix upgrade is answered with a404by the proxy) and passes with this change. Full suite green at 100% coverage.Note for reviewers:
@fastify/websockethas the mirror-image global upgrade listener, so a proxy route and a@fastify/websocketroute on the same path still conflict — that needs a reciprocal change on their side. This PR fixes the http-proxy side: it no longer steals upgrades outside its own prefix.Closes #414