http-proxy-middleware icon indicating copy to clipboard operation
http-proxy-middleware copied to clipboard

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

Open shellscape opened this issue 8 years ago • 6 comments

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

  • http-proxy-middleware: 0.17.4
  • server: express@latest
  • express: latest
  • ws: latest

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.

shellscape avatar Oct 18 '17 20:10 shellscape

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

chimurai avatar Oct 18 '17 20:10 chimurai

@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.

shellscape avatar Oct 18 '17 21:10 shellscape

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

chimurai avatar Oct 19 '17 20:10 chimurai

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)

chimurai avatar Oct 21 '17 14:10 chimurai

@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.

shellscape avatar Oct 21 '17 14:10 shellscape

@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.

konard avatar Nov 13 '23 20:11 konard