tedeh / jayson

Jayson is a simple but featureful JSON-RPC 2.0/1.0 client and server for node.js
https://jayson.tedeh.net
MIT License
715 stars 113 forks source link

How to control TCP max connections programmatically #127

Closed franck34 closed 5 years ago

franck34 commented 5 years ago

Hi.

In a TCP context, I want to handle max connections myself, so i've made something like :

function _handleMaxClients(socket) {
    if (tcpServer._connections>10) {
        console.log("max client reached");
        socket.end();
        return;
    }
}

jaysonServer = jayson.server(...);
tcpServer = jaysonServer.tcp();
tcpServer.listen(6525);
tcpServer.on('connection', _handleMaxClients);

I can see "max client reached" log message, but the socket is not really ended, not really closed, not destroyed, and i've got an empty response, crashing the bin command line tool (see https://github.com/tedeh/jayson/pull/126)

Do you have an idea how can i make this code working ?

I don't want to use tcpServer.maxConnections native node attribute because in my code i'd like to have a line in my logs to say max client reached.

Please tell me if you want a test for this.

Btw, thanks for your job, i'm working on a LokiJS-Server. LokiJS don't have TCP/HTTP API, no CLI, so ... LokiJS + Jayson seem's to play very well together ;)

franck34 commented 5 years ago

mmmmhhhh wait

From JSON-RPC spec -32000 to -32099 | Server error | Reserved for implementation-defined server-errors.

Perhaps a better approach should be to handle kind of "maxClient limit reached" error with a specific code on my side

franck34 commented 5 years ago

Got it.

const jayson = require('jayson');

let NET_TCP_HOST = '127.0.0.1';
let NET_TCP_PORT = 6370;
let NET_TCP_MAX_CLIENTS = 2;

let jaysonServer;
let tcpServer;
let sockets = {};
let log = console;

function _onServerListen(err) {
    if (err) {
        throw new Error(err);
        process.exit(1);
    }

    log.info(
        "TCP Server listening at %s:%s (maxClients %s)",
        NET_TCP_HOST,
        NET_TCP_PORT,
        NET_TCP_MAX_CLIENTS
    );
}

function _handleMaxClients(socket) {

    socket.id = `${socket.remoteAddress}:${socket.remotePort}`;

    if (_maxClientsReached()) {
        log.warn(
            '%s: refusing connection, number of connection: %s, allowed: %s',
            socket.id,
            tcpServer._connections-1,
            NET_TCP_MAX_CLIENTS
        );

        // if client is just a tcp connect (prevent kind of slowLoris attack)
        setTimeout(() => {
            socket.end();
        },100);
        return;
    }

    socket.on('end', () => {
        log.info("%s: client disconnected", socket.id);
        delete sockets[socket.id];
    });

    sockets[socket.id] = socket;
    log.info("%s: client connected", socket.id);

}

function _maxClientsReached() {
    return tcpServer._connections>NET_TCP_MAX_CLIENTS;

}

function _maxClientsReachedResponse(params, callback) {
    let error = {
        code: -32000,
        message:"Max Clients Reached"
    }
    callback(error);
}

function start() {

    if (!NET_TCP_PORT) {
        throw new Error('NET_TCP_PORT unavailable');
        process.exit(-1);
    }

    jaysonServer = jayson.server(
        null, // no handlers, because we are using a router (below)
        {
            router: (method, params) => {
                if (_maxClientsReached()) {
                    return _maxClientsReachedResponse;
                }

                // your specific router code here
                log.info('should exec method %s', method);
            }
        }
    );

    tcpServer = jaysonServer.tcp();
    tcpServer.on('connection', _handleMaxClients);
    tcpServer.on('listening', _onServerListen);
    tcpServer.listen(NET_TCP_PORT, NET_TCP_HOST);

}

start();
franck34 commented 5 years ago

Please +1 if you think that it should be interesting to embed this little security/control feature in jayson, i'll make/purpose a PR with tests.

franck34 commented 5 years ago

+1 (lol)