express icon indicating copy to clipboard operation
express copied to clipboard

WebSocket GET handshake (upgrade) routing

Open ibc opened this issue 9 years ago • 12 comments

I don't meam an "integrated WebSocket server" but the possibility of performing express routing for WebSocket handshake requests.

Node httpServer emits an "upgrade" event for HTTP GET requests including a "Upgrade" header. Express may provide a new "method" called "websocket" for handling them (if the "Upgrade" header has value "websocket"), so the GET request may be processed as any other HTTP verb:

var app = express();

app.websocket('/websocket/:service', function(req, res, next) {
  [...]
});

In this way, Cookies present in the HTTP request may be checked using express middlewares (for example).

ibc avatar Mar 14 '15 20:03 ibc

Perhaps, but Express is not a HTTP server, it is only a function. You just pass an Express app yo the requestListener osf whatever HTTP server module you choose to use.

All an app is is app(req, res, next). You can even typeof on your app variable. Because of this, Express has no way to actually listen to any events on the HTTP server.

dougwilson avatar Mar 14 '15 20:03 dougwilson

As for using Express middleware with websockets, this is already possible without any effort from express. Many many people already do this today, especially with express-session.

dougwilson avatar Mar 14 '15 20:03 dougwilson

I'm not dismissing this (or I would have closed the issue), but if you or someone could put together a PR that is even just a WIP towards this against the 5.x branch, that would have the best chance of getting in :)

dougwilson avatar Mar 14 '15 21:03 dougwilson

FYI I don't think it's necessary to listen to the event, that is just a convenience for other libraries (i.e. socket.io). We just need to check if the Upgrade: header is present and act accordingly.

LinusU avatar Mar 21 '15 01:03 LinusU

Please make a PR :)

dougwilson avatar Mar 21 '15 01:03 dougwilson

IMHO this is not so easy. AFAIK the httpServer will emit upgrade upon receipt of a GET request with header "Upgrade", and it is in that event where existing WS libraries (such as ws or websocket-node) react to perform the handshake or notify the app. What I mean is that, at that point ("upgrade" event fired) the WS server as already control over the request. Express should keep the "upgrade" event until a middleware handles it. Otherwise it should be replied with 404 as usual, but for that something should listen for the "upgrade" event and invoke the app.handle() method somehow...

ibc avatar Mar 22 '15 11:03 ibc

Sure. The problem is I really don't use WebSockets, so I'm really looking for someone to offer a PR against the 5.x branch for this idea is all, otherwise if no on I going to make a PR, I may have to close this issue since it's not being acted on.

The PR can even be a WIP, as I said above. I'm just looking for code to demonstrate your idea.

dougwilson avatar Mar 22 '15 15:03 dougwilson

+1 I'm currently using a custom handleUpgrade to do authentication before passing the request to https://github.com/websockets/ws/ but it would be nice to be able to use express middleware / routing. Not interested in using socket.io

olalonde avatar May 18 '15 20:05 olalonde

Just wrote this module: https://github.com/olalonde/express-websocket

var http = require('http');
var ws = require('ws');

module.exports = function (app, wss) {
  if (!wss) {
    wss = new ws.Server({ noServer: true });
  }

  // https://github.com/websockets/ws/blob/master/lib/WebSocketServer.js#L77
  return function (req, socket, upgradeHead) {
    var res = new http.ServerResponse(req);
    res.assignSocket(socket);

    res.websocket = function (cb) {
      var head = new Buffer(upgradeHead.length);
      upgradeHead.copy(head);
      wss.handleUpgrade(req, socket, head, function (client) {
        //client.req = req; res.req
        wss.emit('connection'+req.url, client);
        wss.emit('connection', client);
        cb(client);
      });
    };

    return app(req, res);
  };
};

Basically, it routes the request to an express app and exposes the res.websocket() method which starts the websocket connection.

olalonde avatar May 18 '15 23:05 olalonde

There hasn't been much discussion in here for a month. Any thoughts on issuing a PR to discuss over? Otherwise, I may need to close this for becoming stale.

dougwilson avatar Jun 19 '15 04:06 dougwilson

It'd be awesome to do something like the above that @olalonde has done, and I might be adopting his solution (if slightly modified). As it currently stands, it feels very strangely architectured IMHO.

edit: my solution:

let http = require('http')
let express = require('express')

let app = express()
let server = http.createServer(...)

// ... etc
server.on('upgrade', (req, socket) => {
  let res = new http.ServerResponse(req)
  res.assignSocket(socket)

  res.on('finish', () => res.socket.destroy())
  app(req, res)
})

// elsewhere
  router.use('/', (req, res, next) => {
    if (!req.headers ||
      req.headers.upgrade === undefined ||
      req.headers.upgrade.toLowerCase() !== 'websocket') return next()

    wss.handleUpgrade(req, req.socket, undefined, (socket) => {
      console.log('upgraded!')
      socket.send('woo')
      socket.close()
    })
  })

dcousens avatar Jun 28 '16 07:06 dcousens

I just ran across this old thread. Perhaps the following library is relevant now? https://www.npmjs.com/package/express-ws

toomim avatar May 10 '17 05:05 toomim