Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebSocket proxy fails if HMR is running on same port #6994

Open
bminer opened this issue Sep 28, 2021 · 9 comments · May be fixed by #9432
Open

WebSocket proxy fails if HMR is running on same port #6994

bminer opened this issue Sep 28, 2021 · 9 comments · May be fixed by #9432
Labels

Comments

@bminer
Copy link

bminer commented Sep 28, 2021

🐛 bug report

When running parcel serve where the --port is the same as the --hmr-port (the default behavior AFAIK), the WebSocket proxy (i.e. .proxyrc) no longer functions properly.

🎛 Configuration

$ cat .proxyrc.json
{
	"/ws": {
		"target": "http://localhost:1235/",
		"ws": true
	}
}

Running this command exhibits the bug:

parcel index.html

🤔 Expected Behavior

WebSocket proxy should work. When the browser connects to ws://localhost:1234/ws, it should be proxied to localhost:1235/ws using http-proxy-middleware

😯 Current Behavior

When the browser establishes the WebSocket connection to ws://localhost:1234/ws, parcel indicates that the web browser immediately disconnects:

$ parcel --port 1234 --hmr-port 1234 index.html

Server running at http://localhost:1234
✨ Built in 1.76s
console: [HPM] Upgrading to WebSocket
console: [HPM] Client disconnected

On the WebSocket server running in Go on port 1235, I get an error that seems to indicate that the socket is closed:

failed to get reader: failed to read frame header: EOF

On the browser (Firefox), I get a message like this:

The connection to ws://localhost:1234/ws was interrupted while the page was loading.

which occurs on the line where I create the new WebSocket(...).

💁 Possible Solution

Everything runs fine without parcel. If I parcel build and bypass the parcel server and HMR server, it works.

Running Parcel with a different HMR port also seems to work (note that HMR port is set to 1236):

parcel --port 1234 --hmr-port 1236 index.html
$ parcel --port 1234 --hmr-port 1236 index.html

Server running at http://localhost:1234
✨ Built in 392ms
console: [HPM] Upgrading to WebSocket

... and in this case, it all works splendidly.

This seems to indicate that parcel serve does not properly handle WebSocket proxying with http-proxy-middleware when the HMR server is running on the same port as the normal parcel HTTP server.

🔦 Context

I'm just trying to run parcel serve [entry_point] and have it work (ideally without a separate HMR port).

💻 Code Sample

I can provide a sample upon request, but it will take time.

🌍 Your Environment

Software Version(s)
Parcel 2.0.0-rc.0
Node v14.17.6
npm/Yarn npm 6.14.15
Operating System Linux Mint 20.2
Browser Firefox 92.0.1
Go 1.16.6
Go WebSocket lib nhooyr.io/websocket v1.8.7
@eirikb
Copy link

eirikb commented Dec 3, 2021

Thank you. Had the exact same issue, your solution worked perfectly.

@mistakenot
Copy link

+1 here also. I'm on parcel ^2.0.1 and socket.io-client ^4.3.2, changing the hmr socket worked for me.

@nunof07
Copy link

nunof07 commented Jan 26, 2022

Thank you. Your solution worked for me.

@dispalt
Copy link

dispalt commented Apr 28, 2022

Thank you so much! I was getting something like below (in case others run into this) on the server side (Scala/Java).

io.netty.handler.codec.http.websocketx.CorruptedWebSocketFrameException: bytes are not UTF-8

@github-actions github-actions bot added the Stale Inactive issues label Oct 26, 2022
@bminer
Copy link
Author

bminer commented Nov 7, 2022

AFAIK, this is still an issue. Either a documentation change should be made to inform users of this, or the bug should be fixed.

@github-actions github-actions bot removed the Stale Inactive issues label Nov 7, 2022
@github-actions github-actions bot added the Stale Inactive issues label May 7, 2023
@xiemeilong
Copy link

This issue is still exist in the latest version.

@github-actions github-actions bot removed the Stale Inactive issues label May 7, 2023
@mischnic
Copy link
Member

mischnic commented May 7, 2023

This is the relevant code for the case of --port === --hmr-port.

let hmrServerOptions = {
port: serveOptions.port,
host: hmrOptions.host,
devServer,
addMiddleware: handler => {
server?.middleware.push(handler);
},
logger,
};
hmrServer = new HMRServer(hmrServerOptions);

this.wss = new WebSocket.Server({server});

The HTTP server creation including proxyrc is here (the server here becomes devServer in HMRServer above):

const app = connect();
app.use((req, res, next) => {
setHeaders(res);
next();
});
await this.applyProxyTable(app);
app.use(finalHandler);
let {server, stop} = await createHTTPServer({
cacheDir: this.options.cacheDir,
https: this.options.https,
inputFS: this.options.inputFS,
listener: app,
outputFS: this.options.outputFS,
host: this.options.host,
});
this.stopServer = stop;
server.listen(this.options.port, this.options.host);
return new Promise((resolve, reject) => {
server.once('error', err => {
this.options.logger.error(
({
message: serverErrors(err, this.options.port),
}: Diagnostic),
);
reject(err);
});
server.once('listening', () => {
resolve(server);
});
});

@bminer
Copy link
Author

bminer commented May 26, 2023

I think the issue is that the HMR server uses ws to create a new WebSocket server with the server option set:

this.wss = new WebSocket.Server({server});

This basically ensures that this.wss now handles all WebSocket requests for this.options.devServer; thus, only HMR WebSocket requests work and WebSocket proxy requests using http-middleware-proxy are essentially closed preemptively by the HMR WebSocket upgrade handler. Here's the relevant code from the ws library:
https://github.com/websockets/ws/blob/06728e444d8f54aa5602b51360f4f98794cb1754/lib/websocket-server.js#L260-L263

The solution here is to tell the HMR WebSocket.Server to run in noServer mode and only handle upgrade events when it's for the HMR. See ws docs here. So line 89 above would change to:

this.wss = new WebSocket.Server({noServer: true});

and then the server's upgrade event would need to be handled manually as shown in this ws usage example:

server.on('upgrade', function upgrade(req, socket, head) {
  let {pathname} = url.parse(req.originalUrl || req.url);
  if (pathname != null && pathname.startsWith(HMR_ENDPOINT)) {
    this.wss.handleUpgrade(req, socket, head, function done(ws) {
      this.wss.emit('connection', ws, req);
    });
  }
  // else, we simply do nothing and hope the request got proxied
});

@bminer
Copy link
Author

bminer commented May 26, 2023

Anyway, hope this is an easy fix. If you want, I can try my hand at a PR for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants