bson-rpc
Version:
the nodejs version of bson-rpc
246 lines (211 loc) • 6.81 kB
JavaScript
/* Copyright 2017 Evan Liu (hmisty@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var util = require('util');
var bson = require('bson');
var net = require('net');
var isReachable = require('is-reachable');
var common = require('./common');
// the global logger
var logger = common.logger;
var Proxy = function() {
this.connection_mode = common.connection_mode.LOCAL;
this.hosts = [];
this.port = 0;
this.idgen = 1; //for _id gen
var nargs = arguments.length;
var args = arguments;
switch (nargs) {
case 2:
if (args[0] instanceof Array) {
// LOAD BALANCE MODE
// var proxy = new bson_rpc.client(['10.0.0.1', '10.0.0.2', ...], 8181);
this.connection_mode = common.connection_mode.LOAD_BALANCE;
this.hosts = args[0];
this.port = args[1];
} else {
// STAND-ALONE MODE (FOR BACKWARD COMPATIBILITY)
// var proxy = new bson_rpc.client('10.0.0.1', 8181);
this.connection_mode = common.connection_mode.STAND_ALONE;
this.hosts.push(args[0]);
this.port = args[1];
}
break;
case 3:
// STAND-BY MODE, support only 1 master and 1 slave
// var proxy = new bson_rpc.client('10.0.0.1', '10.0.0.2', 8181);
this.connection_mode = common.connection_mode.STAND_BY;
this.hosts.push(args[0]);
this.hosts.push(args[1]);
this.port = args[2];
break;
default:
// var proxy = new bson_rpc.client();
// then we use 127.0.0.1:8181 by default
this.connection_mode = common.connection_mode.LOCAL;
this.hosts.push(common.connection_mode.DEFAULT_HOST);
this.port = common.connection_mode.DEFAULT_PORT;
break;
}
this.socket = new net.Socket();
this.callbacks = {}; //callback func, _id: function
};
/* this.die_on_failure(true/false);
*/
Proxy.prototype.die_on_failure = function(dof) {
common.DIE_ON_FAILURE = dof;
};
Proxy.prototype.connect = function(on_connected_callback) {
//var callbacks = this.callbacks;
var data_handler = common.create_data_handler((doc) => {
if (typeof(this.callbacks[doc._id]) == 'function') {
var callback = this.callbacks[doc._id];
if (doc.error_code == 0) {
callback(null, doc.result);
} else {
var err = JSON.stringify(doc);
callback(err);
}
delete this.callbacks[doc._id];
if (this.callbacks.length == 0) {
this.idgen = 0; //start over
}
} else {
//throw away the doc
logger.warn('no callback for ' + doc._id + '. throw it away.');
}
});
this.socket.on('connect', () => {
logger.info('connected.');
});
this.socket.on('data', data_handler);
this.socket.on('error', (err) => {
common.fail('socket error: ' + err);
});
// connect the socket according to connection_mode
switch (this.connection_mode) {
//local mode
case common.connection_mode.LOCAL:
var sock = this.socket;
var port = this.port;
var next_host = this.hosts.shift(); // take the first host
isReachable(next_host + ':' + port).then(reachable => {
logger.info('trying to connect ' + next_host + ':' + port + ' in LOCAL mode...');
if (reachable) {
sock.connect(port, next_host, on_connected_callback);
} else {
common.fail(next_host + ':' + port + ' is not reachable.');
}
});
break;
//stand alone mode
case common.connection_mode.STAND_ALONE:
var sock = this.socket;
var port = this.port;
var next_host = this.hosts.shift();
isReachable(next_host + ':' + port).then(reachable => {
logger.info('trying to connect ' + next_host + ':' + port + ' in STAND_ALONE mode...');
if (reachable) {
sock.connect(port, next_host, on_connected_callback);
} else {
common.fail(next_host + ':' + port + ' is not reachable.');
}
});
break;
//stand by mode
case common.connection_mode.STAND_BY:
var sock = this.socket;
var port = this.port;
var hosts = this.hosts;
var next_host = hosts.shift();
isReachable(next_host + ':' + port).then(reachable => {
logger.info('trying to connect master ' + next_host + ':' + port + ' in STAND_BY mode...');
if (reachable) {
sock.connect(port, next_host, on_connected_callback);
} else {
logger.warn(next_host + ':' + port + ' is not reachable.');
next_host = hosts.shift();
isReachable(next_host + ':' + port).then(reachable => {
logger.info('trying to connect stand-by ' + next_host + ':' + port + ' ...');
if (reachable) {
sock.connect(port, next_host, on_connected_callback);
} else {
common.fail(next_host + ':' + port + ' is not reachable.');
}
});
}
});
break;
//load balance mode
case common.connection_mode.LOAD_BALANCE:
var sock = this.socket;
var try_next = function (hosts, port) {
if (hosts.length > 0) {
// we have more hosts to try
var which = common.random(0, hosts.length - 1);
var next_host = hosts.splice(which, 1)[0]; // take the `which` host
isReachable(next_host + ':' + port).then(reachable => {
logger.info('trying to connect ' + next_host + ':' + port + ' in LOAD_BALANCE mode...');
if (reachable) {
sock.connect(port, next_host, on_connected_callback);
} else {
logger.warn(next_host + ':' + port + ' is not reachable.');
// try more
try_next(hosts, port);
}
});
} else {
// we have no more host to try, so just die
common.fail('all failed to connect.');
}
};
try_next(this.hosts, this.port);
break;
default:
common.fail('unknown connection mode. exit.');
break;
}
};
Proxy.prototype.use_service = function(names) {
var callbacks = this.callbacks;
var gen_function = function(conn, fn) {
return function() {
var args = Array.prototype.splice.call(arguments, 0, arguments.length);
var doc = {
_id: this.idgen++,
fn: fn,
args: args,
};
var obj = new bson();
var rpc_data = obj.serialize(doc);
conn.write(rpc_data);
return {
then: function(callback) {
callbacks[doc._id] = callback;
}
};
}
};
var conn = this.socket;
names.push('__stats__');
names.forEach(function(name) {
Proxy.prototype[name] = gen_function(conn, name);
});
};
Proxy.prototype.disconnect = function() {
this.socket.end();
common.fail('client-side disconnected explicitly');
};
module.exports = Proxy;