node.jsstreampipe

Node js, variable scope. Event handler always refer to the first socket created


I'm trying to implement a proxy like app using nodejs. When a client connect, it will connect to 2 servers A and B. If client send "A", it will send to Server A, otherwise send to Server B. And all response from server A and B will pipe back to client. I have used the liner module on this website https://strongloop.com/strongblog/practical-examples-of-the-new-node-js-streams-api/ to split the incoming message to lines.

My code works if only have one client connected to the server. However if there is multiple clients connects. Other client message always sent to the server socket that established for the first client. The liner readable event seems always refer to the first server sockets created but I thought it should be local variables.

Anyone know what's wrong in my code.

Many Thanks.

var net = require('net');
var fs = require('fs');
var util = require('util');
var liner = require('./liner');

var server = net.createServer(function (clientSocket) { 
  console.log('client connected');
  var serverSocket2 = null;
  var serverSocket = net.connect({ host: '127.0.0.1', port: 9001 }, 
    function () {
      console.log('connected to server:' + serverSocket.remoteAddress);

      serverSocket2 = net.connect({ host: '127.0.0.1', port: 9002 }, 
        function () {
          console.log('connected to server ' + serverSocket2.remoteAddress);

          // pipe the client input to 2 server
          clientSocket.pipe(liner);
          // pipe server output to client socket
          serverSocket.pipe(clientSocket);
          serverSocket2.pipe(clientSocket);
      });
  });

  liner.on('readable', function () {
    var line;
    while (null !== (line = liner.read())) {
        if (line == "A") {
            serverSocket2.write(line + "\r\n");
        }
        if (line == "B") {
            serverSocket.write(line + "\r\n");

        }
    }
  });    
  clientSocket.on('end', function () {
    console.log('client disconnected');
    serverSocket.end();
    serverSocket2.end();
  });
});

server.listen(8123, function () { //'listening' listener
  console.log('server bound');
});

Solution

  • The solution is simple: don't share a global liner instance for all connections. Instead, convert the liner example in the article you linked to a constructor that inherits from stream.Transform and instantiate using that. For example (using the code from the linked article):

    var inherits = require('util').inherits;
    var stream = require('stream');
    
    function Liner() {
      if (!(this instanceof Liner))
        return new Liner();
      stream.Transform.call(this, { objectMode: true });
    }
    inherits(Liner, stream.Transform);
     
    Liner.prototype._transform = function(chunk, encoding, done) {
      var data = chunk.toString();
      if (this._lastLineData)
        data = this._lastLineData + data;
    
      var lines = data.split('\n');
      this._lastLineData = lines.splice(lines.length-1,1)[0];
    
      lines.forEach(this.push.bind(this));
      done();
    };
     
    Liner.prototype._flush = function (done) {
      if (this._lastLineData)
        this.push(this._lastLineData);
      this._lastLineData = null;
      done();
    };
     
    module.exports = Liner;
    

    Then use it like:

    var Liner = require('./liner');
    
    var server = net.createServer(function(clientSocket) {
      var liner = new Liner();
    
      // ...
    });
    

    I should also point out that a "class" such as this is not necessary as the same thing can be achieved with the built-in readline module. For example:

    var readline = require('readline');
    var net = require('net');
    
    var server = net.createServer(function(clientSocket) {
      console.log('client connected');
    
      var rl;
      var serverSocket2 = null;
      var serverSocket = net.connect({ host: '127.0.0.1', port: 9001 }, 
        function () {
          console.log('connected to server:' + serverSocket.remoteAddress);
    
          serverSocket2 = net.connect({ host: '127.0.0.1', port: 9002 }, 
            function () {
              console.log('connected to server ' + serverSocket2.remoteAddress);
    
              // pipe the client input to 2 server
              rl = readline.createInterface({
                input: clientSocket
              });
              rl.on('line', lineHandler);
    
              // pipe server output to client socket
              serverSocket.pipe(clientSocket);
              serverSocket2.pipe(clientSocket);
            }
          );
        }
      );
    
      function lineHandler(line) {
        if (line === "A") {
          serverSocket2.write(line + "\r\n");
        } else if (line === "B") {
          serverSocket.write(line + "\r\n");
        }
      }
    
      clientSocket.on('end', function() {
        console.log('client disconnected');
        serverSocket.end();
        serverSocket2.end();
      });
    });
    
    server.listen(8123, function() {
      console.log('server bound');
    });