chimurai / http-proxy-middleware

:zap: The one-liner node.js http-proxy middleware for connect, express, next.js and more
MIT License
10.78k stars 853 forks source link

proxy to localhost ws/WebSocketServer results in Error: RSV1 must be clear #209

Open shellscape opened 7 years ago

shellscape commented 7 years ago

Expected behavior

Proxying to the locally running WebSocketServer on a different port succeeds.

Actual behavior

Proxying to the locally running WebSocketServer on a different port fails with an error of; Error: RSV1 must be clear. This is internally thrown by ws because the buffer that the server is receiving begins with bytes that are technically invalid. See below for triage.

Setup

proxy middleware configuration

const wsProxy = proxy('/', {
  target: 'http://localhost:9090',
  ws: true,
  logLevel: 'debug'
});

server mounting

const express = require('express')
const proxy = require('http-proxy-middleware');
const WebSocket = require('ws');

const wsProxy = proxy('/', {
  target: 'http://localhost:9090',
  ws: true,
  logLevel: 'debug'
});

const wss = new WebSocket.Server({ port: 9090 });

wss.on('connection', (socket) => {
  console.log('socket server connected');
  socket.on('message', (message) => {
    console.log('socket server message:', message);
    socket.send(`response: ${message}`);
  });
});

const app = express();
app.use('/', express.static(__dirname));
app.use(wsProxy);

const server = app.listen(8080, () => {

  const internalWss = new WebSocket.Server({ server });

  const client = new WebSocket('ws://localhost:8080');

  client.on('message', (message) => {
    console.log('client message:', message);
  });

  client.on('open', () => {
    console.log('client open');
    client.send('challenge');
  });
});

server.on('upgrade', wsProxy.upgrade);

This code block reproduces the issue with 100% of test runs locally here. If you comment out the line;

const internalWss = new WebSocket.Server({ server });

then the example runs successfully without error. There's some kind of caveat/edge case with there being a WebSocketServer connected to the server on port 8080, as well as bound to localhost:9090. I did attempt to work my way through the http-proxy-middleware source to debug the cause but I came up empty. I also spent a fair amount of time within the source for ws to make sure that it wasn't an error there; in which I couldn't pinpoint a cause either. Given that the addition/removal of the WebSocketServer on port 8080 (same as the server) caused failure/pass, I figured I'd start with an issue here.

chimurai commented 7 years ago

What kind of client are you using to setup the connection? Did you try different ones to see if you see any difference?

shellscape commented 7 years ago

@chimurai not sure I understand the question. I'm using the ws WebSocket client here, which is standards compliant with browser implementations. The code above was taken directly from the websocket example here and modified to match the failing case.

If you're referencing the const client... line and related code; I ran a test via the browser with that const client = and related code commented out, leaving the internalWss active, and this was the result:

Imgur

As soon as I commented out the internalWss line, everything worked as per usual. So here again, it appears to be a conflict between http-proxy-middleware and multiple websocket servers running on localhost on different ports, with proxying enabled.

chimurai commented 7 years ago

Thanks for the detailed report @shellscape. I'll have to spend some time investigating this.

chimurai commented 7 years ago

Created a mini version of http-proxy-middleware to isolate the issue:

const httpProxyMiddleware = function (opts) {
  const proxyServer = httpProxy.createProxyServer(opts)

  const middleware = function (req, res, next) {
    proxyServer.web(req, res, opts)
  }

  middleware.upgrade = function (req, socket, head) {
    proxyServer.ws(req, socket, head)
  }

  return middleware
}

const wsProxy = proxy({
  target: 'http://localhost:9090',
  ws: true,
  logLevel: 'debug'
});

RSV1 must be clear error still present with this setup.

const express = require('express')
const WebSocket = require('ws');

const httpProxyMiddleware = function (opts) {
  const proxyServer = httpProxy.createProxyServer(opts)

  const middleware = function (req, res, next) {
    proxyServer.web(req, res, opts)
  }

  middleware.upgrade = function (req, socket, head) {
    proxyServer.ws(req, socket, head)
  }

  return middleware
}

const wsProxy = proxy({
  target: 'http://localhost:9090',
  ws: true,
  logLevel: 'debug'
});

const wss = new WebSocket.Server({ port: 9090 });

wss.on('connection', (socket) => {
  console.log('socket server connected');
  socket.on('message', (message) => {
    console.log('socket server message:', message);
    socket.send(`response: ${message}`);
  });
});

const app = express();
app.use('/', express.static(__dirname));
app.use(wsProxy);

const server = app.listen(8080, () => {

  const internalWss = new WebSocket.Server({ server });

  const client = new WebSocket('ws://localhost:8080');

  client.on('message', (message) => {
    console.log('client message:', message);
  });

  client.on('open', () => {
    console.log('client open');
    client.send('challenge');
  });
});

server.on('upgrade', wsProxy.upgrade);

Error doesn't show when wrapping a setTimeout around const internalWss = new WebSocket.Server({ server });

  setTimeout(() => {
    const internalWss = new WebSocket.Server({ server })
  }, 100)
shellscape commented 7 years ago

@chimurai interesting. I wonder though if that setTimeout setup only succeeded without error because the internalWss wasn't yet created and listening when the client message was sent.

Konard commented 1 year ago

@chimurai I have the same issue here: https://github.com/deep-foundation/deepcase-app/issues/223

Any ideas how to fix it now? We use the latest 2.0.6 version.