4

My requirement is a little bit different, don't know even if it's achievable.

I am using Node.js for developing backend application server. This server basically does two jobs:

(1) Serving clients: My clients are all mobile phones who will be sending HTTP(S) request and after receiving the response will close the session.

(2) Calling some other asynchronously working service: The server, on the other hand, will be connected to some other server which works over just TCP/IP connection and not HTTP. Asynchronous here means, the server will send a request and should not wait for a response. The response will be received through same TCP/IP connection.

So the flow I want to achieve is:

  1. Mobile phone sends the HTTP request to server
  2. Server after receiving the HTTP request, does a call to service which is on TCP/IP
  3. Server receives the response from TCP/IP service over the TCP/IP connection
  4. Server responds to the phone with the response.

To represent the above flow I have attached the below image.

enter image description here

In the above image the TCP/IP Server is managed by some other provider.

I wrote the following code in node.js which works perfectly as per our requirement some times, but some times it sends incorrect response to the HTTP request. I did not write any code to handle this issue.

    var net = require('net');

    var client = new net.Socket();
    client.connect(2202, 'example_ip', function () {
    console.log('Connected');
    // client.write('Hello, server! Love, Client.');
    });

    //Lets require/import the HTTP module
    var http = require('http');

    //Lets define a port we want to listen to
    const PORT = 8080;

    //We need a function which handles requests and send response
    function handleRequest(request, response) {
    var body = '';

    request.on('data', function (chunk) {
        body += chunk;
    });

    request.on('end', function () {
        console.log('Received request from JMeter------------>>>');
        // console.log(body);
        client.write(body);

    var count = 0;
    client.on('data', function (data) {
            console.log('<<<------------Received from SSM: ' + data);
            response.end(data);
            // client.destroy(); // kill client after server's response
        });


    });



    client.on('close', function () {
        console.log('Connection closed');
    });

}

//Create a server
var server = http.createServer(handleRequest);

//Lets start our server
server.listen(PORT, function () {
    //Callback triggered when server is successfully listening. Hurray!
    console.log("Server listening on: http://localhost:%s", PORT);
});

Please some one guide me to solve this issue.

Viddesh
  • 441
  • 5
  • 18
  • *Websockets* or *Server-sent events* would be an options for this – Molda May 13 '16 at 05:54
  • I think you didn't get my question properly. I can web sockets on other side but how do i ensure correct response to correct request from mobile phone. Consider mob_1 sends request to server and server gives a call to tcp service. By the time, other client which is mob_2 sends request to server and again server calls the tcp service. By now there is no incoming message from tcp service and both clients are on hold. After sometime I get the incoming message from tcp service and how do i map the response to correct client? How do I continue with the thread to send a response? – Viddesh May 13 '16 at 06:26
  • OK I see. So all you need is to identify the request response from TCP service. Since you haven't shown any code regarding the request to TCP service it's impossible to help you. If the TCP service is managed by you then you could program it so that you can send some ID along with the request and TCP service should return the ID so you can easily identify it. – Molda May 13 '16 at 06:37
  • Well I have not written any code for tcp service. But my issue is not mapping request response of tcp service. My concern is how do i hold request from the phone and send correct response to it? It may happen that i may send wrong response to the mobile phone. Do i need to save request object from the mobile phone request? Whats the best practise for doing this? – Viddesh May 13 '16 at 06:42
  • @Molda- I have added the code and there is a bounty as well. – Viddesh May 27 '16 at 07:07
  • I think the safest way to be sure the phone receive the correct response is for your http server to wait response from tcp server. And then send the response to the phone – Martial May 31 '16 at 13:06

2 Answers2

1

TCP streams don't work like WebSocket streams ( as you expect ). You need to use your own protocol to communicate with a TCP server. Keep in mind that HTTP clients are many and you have only one TCP connection to handle them, so use requestIds like below, code explains itself.

Not tested, but you can get the idea.

shared.js

exports.tcp = {
    host: 'example_ip',
    port: 2202
};

exports.http = {
    host: 'localhost',
    port: 8080
};

/**
 *  TCP "guarantees" that a receiver will receive the reconstituted 
 *   stream of --> BYTES <-- as it was originally sent by the sender.
 *
 *  eg. if written message = 'How are you today?'
 *   the messages can come to us as follows:
 *     
 *     'How ar'
 *     'e you '
 *     'today?'
 *  
 *  so we need to use a simple protocol to handle messages
 */
exports.protocol = protocol;

function protocol(options) {

    if (!options) options = {};

    this.END_OF_MESSAGE = options.endOfMessage || '\0';
    this.END_OF_PART = options.endOfPart || '\1';

    this.dataBuffer = '';
}

protocol.prototype.packMessage = function(id, body) {
    return [id, body].join( this.END_OF_PART ) + this.END_OF_MESSAGE;
};

protocol.prototype.unpackMessage = function(message) {

    var parts = message.toString('utf8').split( this.END_OF_PART );
    return {id: parts.shift(), body: parts.shift()};
};

protocol.prototype.extractMessages = function(data, callback) {

    this.dataBuffer += data.toString('utf8');

    if (this.dataBuffer.indexOf(this.END_OF_MESSAGE) !== -1)
    {
        var messages = this.dataBuffer.split(this.END_OF_MESSAGE);
        var incomplete = this.dataBuffer.slice(-1) === this.END_OF_MESSAGE
            ? '' : messages.pop();

        messages.forEach(function(message)
        {
            if (message !== '') {
                callback( this.unpackMessage(message) );
            }
        });

        this.dataBuffer = incomplete;
        // rest of 'data'
    }

    /**
    if (Buffer.byteLength(this.dataBuffer, 'utf8') > 10240) { // 10KB
        console.log('[!] socket flooded');
        this.dataBuffer = '';
    }
    */
};

protocol.prototype.reset = function() {
    this.dataBuffer = '';
};

httpServer.js

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

var shared = require('./shared.js');
var protocol = new shared.protocol();

var server = http.createServer(handleRequest);
server.listen(shared.http.port, shared.http.host, function() {
    console.log('HTTP server listening: %s:%s', shared.http.host, shared.http.port);
});

function handleRequest(request, response) {

    var body = '';

    var requestId = nextId++;
    var eventName = 'message' + requestId;

    request.on('data', function(chunk) {
        body += chunk.toString('utf8');
    });

    request.on('end', function()
    {
        // ref#2
        client.write( protocol.packMessage(requestId, body) );

        // ref#3
        client.once(eventName, function(data) {

            clearTimeout(timeoutId);
            response.end(data);
        });
    });

    var timeoutId = setTimeout(function() {

        client.removeListener(eventName);
        response.end('timeout');

    }, 10000); // 10 sec.

    /** 
     * [!] Don't do this; you are adding just another 'data' event to
     *  the TCP client for EVERY http request !?
     *  
     *  request: UNIQUE obj. for every http request
     *  client: a PERSISTENT (TCP) stream obj.
     *
    client.on('data', function() { });
    **/
}

var client = new net.Socket();

// ref#1
client.connect(shared.tcp.port, shared.tcp.host, function() {
    console.log('TCP conn. established to: ', shared.tcp.host, shared.tcp.port);
});

var nextId = 0;
// unique per http req.

/**
 * [!] Do this ( once ) ( not for every request )
 */
client.on('data', function(data)
{
    protocol.extractMessages(data, function(message) {

        client.emit('message' + message.id, message.body);
        // ref#3
    });
});

client.on('close', function()
{
    console.log('TCP conn. closed');
    client.removeAllListeners();
})

client.on('error', function()
{
    console.log('TCP conn. error', arguments);
    // client.destroy(); // and reconnect here
});

tcpServer.js

var net = require('net');

var shared = require('./shared.js');
var protocol = new shared.protocol();

var server = net.createServer(handleConnection);
server.listen(shared.tcp, function() {
    console.log('TCP server listening %s:%s', shared.tcp.host, shared.tcp.port);
});

// [!] CONNECTION handler ( ref#1 )
function handleConnection(client)
{
    var this.dataBuffer = '';

    // [!] DATA handler ( ref#2 )
    client.on('data', function(data) {

        protocol.extractMessages(data, function(message)
        {
            var requestId = message.id;
            var body = message.body;

            // Do whatever you want with 'body' here

            /**
             * And return back to 'client' with 'requestId' using same protocol again
             *  so the 'client' ( from httpServer.js ) can handle your response
             */
            client.write( protocol.packMessage(requestId, body) );
        });
    });
}
Abdullah
  • 968
  • 12
  • 17
0

Can you instantiate a new client for each incoming request? This way the TCP connection for each request will be unique.

memimomu
  • 161
  • 4