node.jsnginxwebsocketnginx-reverse-proxy

Websocket call lost in nginx rerouting


I am trying to set up a websocket call from my Javascript app (running on a public IP address) to my NodeJS server.

For this I am using pm2 and nginx for the server routing, however I believe that I am doing something wrong, as I am unable to get the server to write any of the websocket call into the log, despite the websocket receiving a 200 return message.

The log does write down the starting information, such as which port each thing is starting on, but after that nothing.

My file in /etc/nginx/sites-available/my_app.com (this file is also soft linked to /etc/nginx/sites-enabled):

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

upstream websocket {
   server localhost:3010;
}

upstream backend {
    server localhost:3000;
}

server {
    listen                      80;
    listen                      [::]:80;
    server_name                 my_app.com www.my_app.com --My website ip address for testing--;

    # these are allowed to use unsecured connection...
    location /                  { proxy_pass http://backend; }
     location /ws {
         proxy_pass http://websocket;
         proxy_http_version 1.1;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection $connection_upgrade;
         proxy_set_header Host $host;
     }
}

On my server I have the following files: app.js

const express = require("express"         )
const http = require("http"            )
const { log } = require("./system/global" )
const app = express()
const http_server = http.createServer(app)
const PORT = 3000
require("./system/serve_path")(http_server, 'frontend', app, express)
require("./system/serve_socket").start(PORT)
http_server.listen(PORT, ()=> log("Listening on port " + PORT))

./system/globals.js

const fs = require('fs')
exports.log = (msg) => {
    let now = new Date().toISOString()
    let log_msg = `${now}: ${msg}`
    console.log(log_msg)
    let log_folder = './logs'
    if (!fs.existsSync(log_folder)) { fs.mkdirSync(log_folder) }
    let log_file = `${log_folder}/log.log`
    if (!fs.existsSync(log_file)) {
        fs.writeFileSync(log_file, '') 
    } else {
        let stats = fs.statSync(log_file)
        let fileSize = stats.size
        if (fileSize > 1073741824) {
            let now = new Date().toISOString()
            let new_file_name = `${log_folder}/log_${now}.log`
            fs.renameSync(log_file, new_file_name)
            fs.writeFileSync(log_file, '')
        }
    }
    fs.appendFile(log_file, '\n' + log_msg, (err) => {
        if (err) { console.log(err) }
    })
}
exports.load_system = (module_name) => {
    return require(`./system/${module_name}`)
}
exports.load_socket = (module_name) => {
    return require(`./socket/${module_name}`)
}

./system/serve_path.js

module.exports = (server, path, app, express) => {
    app.use(express.static(path))
}

./system/serve_socket.js

const websocket = require('ws')
const { log } = require('./global')
exports.start = (port) => {
    let socket_port = port + 10
    log("Socket started on port: " + socket_port)
    let wss = new websocket.Server({port: socket_port})
    wss.on('connection', function(ws) {
        log('Socket connected')
        ws.on('open', on_message)
        ws.on('error', on_message)
        ws.on('message', on_message)
    })
}
const on_message = (msg) => {
    console.log('Received from client: %s', msg);
    log('Socket Message: %s', msg);
    ws.send('Socket Message: ' + msg);
}
const on_error = (err) => { log('Socket Error: %s', err); }
const on_open = () => { log('Socket connection opened'); }

Solution

  • Your nginx configuration is the problem, you have to put location / after location / otherwise it will catch every route, Digital Ocean wrote a good article about this.