Search code examples
node.jsexpresswebsocketamazon-elastic-beanstalkpeerjs

node.js server with websockets on elastic beanstalk ALB without socket.io


I'm trying to get a node.js server (using express) working using websockets in elastic beanstalk (EB) using application load balancer (ALB) but without using socket.io (because peerjs-server is the server I'm trying to get running and it's not written with socket.io).

I've seen a couple of articles suggesting you have to use socket.io (or another lib that doesn't just rely on websockets), but Amazon says ALB supports websockets directly.

My server is both a create-react-app server and a peerjs-server. It runs fine in dev on port 9000 for both the web UI and the peerjs ws connections.

I've tried all of the different approaches I've found, but I haven't gotten this to work, and I've even seen things written that suggest it can't be done, but it seems like I'm close. Has anyone gotten this to work, and if so, how?


Solution

  • Okay, I got it to work. Here's what I've done to get everything working on port 9000.

    In EB, create an application, then begin creating an environment.

    In the configuration of the environment, go into the Software section and tell it that you're going to use npm run prod to start your server.:

    enter image description here

    Now, go into the Load Balancers section and, as shown in the pic below:

    1. add a listener on port 9000
    2. create a process on port 9000 and enable stickiness (I called mine peerjsServer)
    3. add Rules for each URL path you want to use to access the server, and have each of those rules assigned to the process you created (peerjsServer). Also point the default to that process, so that health checks on 80 get through successfully to your server

    enter image description here

    You may need to hop over to the EC2 dashboard in the AWS UI in order to make sure that the necessary Security Groups are defined there. I think I created the first two on the and the last two were default creations, but I don't recall. Anyway, they need to have port 9000 open for inbound and outbound (port 80 is there by default all the time): enter image description here

    Back to the EB configuration, go to the Instances section and make sure that your instance has the Security Groups assigned to it: enter image description here

    I ran react-scripts build to make the /build directory containing the production version of the server UI(this is create-react-apps stuff that I'm not covering here).

    In my code, I start the server using a server.js that makes a server that runs both the peerjs-server ws server and an http server.

    const express = require("express");
    const bodyParser = require("body-parser");
    const path = require('path');
    
    const passport = require("passport");
    const users = require("./routes/api/users");
    const games = require("./routes/api/games");
    const Game = require("./src/models/Game");
    
    const WebSocket = require('ws');
    const ExpressPeerServer = require('peerjs-server').ExpressPeerServer;
    
    const app = express();
    const url = require('url');
    
    const port = process.env.PEERSERVERPORT || 9000; 
    
    // WebSocket for making db updates to client.
    const wsserver = require('./wsserver').wsserver
    
    // Server that gets all requests: lobby UI, peerserver, db websocket
    const server = require('http').createServer(app);
    
    wsserver.on('connection', function connection(ws) {
      ws.on('message', msg => {
        ws.send(msg)
      })
    
      ws.on('close', () => {
        console.log('WebSocket was closed')
      })
    });
    
    server.on('upgrade', function upgrade(request, socket, head) {
      const pathname = url.parse(request.url).pathname;
    
      if (pathname === '/ws') {
        wsserver.handleUpgrade(request, socket, head, function done(ws) {
          wsserver.emit('connection', ws, request);
        });    
      }
    });
    
    // Bodyparser middleware
    app.use(
      bodyParser.urlencoded({
        extended: false
      })
    );
    app.use(bodyParser.json());
    
    // Passport middleware
    app.use(passport.initialize());
    // Passport config
    require("./config/passport")(passport);
    
    // Routes -- the /api/* path is defined in the EB load balancer
    app.use("/api/users", users);
    app.use("/api/games", games);
    
    // This is the create-react-apps /build directory of UI code
    app.use(express.static(path.join(__dirname, 'build')));
    app.get('/login', function (req, res) {
      res.sendFile(path.join(__dirname, 'build', 'index.html'));
    });
    
    // These are the paths that are defined in the EB load balancer
    app.get('/logout', function (req, res) {
      res.sendFile(path.join(__dirname, 'build', 'index.html'));
    });
    app.get('/register', function (req, res) {
      res.sendFile(path.join(__dirname, 'build', 'index.html'));
    });
    app.get('/dashboard', function (req, res) {
      res.sendFile(path.join(__dirname, 'build', 'index.html'));
    });
    
    // Peer server for making WebRTC connections between game clients.
    const options = { 
        debug: true
    }
    const peerserver = ExpressPeerServer(server, options);
    app.use('/peerserver', peerserver);
    
    app.use(express.static(path.join(__dirname, 'src')));
    
    app.get('*', function(req, res) {
      res.sendFile(path.join(__dirname, 'src', 'index.html'));
    });
    
    peerserver.on('disconnect', (client) => {
      console.log('Delete the game if the host client disconnects');
      Game.deleteMany({ hostPeerId: client })
        .then(() => {
          wsserver.clients.forEach(function each(client) {
            if (client.readyState === WebSocket.OPEN) {
              client.send("refreshGames");
            }
          })
        })
        .catch(err => console.log(err));
    });
    
    server.listen( port, () => console.log(`Server is up and running on port ${port} !`))
    

    And then in my package.json, I set the script for prod to run the above server.js to start the server, and the npm run prod I put in the Software section earlier is what calls this to make the server go:

      ...
      "scripts": {
        "start": "set PORT=8081&& react-scripts start",
        "prod": "node server.js",
      ...
    

    After doing all that, I now have a running EB server using ALB and handling both websocket (ws) and UI (http) traffic on port 9000.