thintalk
Version:
Very thin RPC layer using TPC and IPC
180 lines (141 loc) • 5.7 kB
JavaScript
/**
* Copyright (c) 2012 Andreas Madsen
* MIT License
*/
(function () {
"use strict";
var util = require('util'),
net = require('net'),
core = require('../core/core.js');
/**
* Helpers
*/
// Since data from a socket comes in buffer streams in an unknown length
// this helper store the data until a complete json object has been received
// when a json object is received it will emit the message event on target.
// JSON strings will be isolated by a newline sign.
function jsonStream(target, socket) {
// first set the channel encodeing to utf8
socket.setEncoding('utf8');
// data chunks may not contain a complete json string, thats why we store the data
var jsonBuffer = '';
// emits when new data from TCP connection is received
socket.on('data', function (chunk) {
// first add the chunk to the json buffer
jsonBuffer += chunk;
var start = 0, i;
// next find the next line end char
while ((i = jsonBuffer.indexOf('\n', start)) >= 0) {
// if one was found copy it out of the jsonBuffer string
var json = jsonBuffer.slice(start, i);
// now that we have a complete json string
// emit message with a parsed json object
target.emit('message', JSON.parse(json));
// set the starting point of next line end search
start = i + 1;
}
// when no more complete json objects exist in the jsonBuffer,
// we will slice the jsonBuffer so it only contains the uncomplete json string
jsonBuffer = jsonBuffer.slice(start);
});
}
/**
* Lisenter
*/
// this will be called from core, when the lisenter.listen is executed by user
function Listener() {
// setup abstract layer
core.ListenerAbstract.apply(this, arguments);
// parse the argument
if (!(this.object instanceof net.Server)) {
// create a new TCP server
if (typeof this.object === 'number' || typeof this.object === 'string') {
this.object = net.createServer().listen(this.object);
} else {
this.object = net.createServer().listen(this.object.port, this.object.host);
}
this.object.on('listening', this.emit.bind(this, 'listening'));
} else {
this.emit('listening');
}
// relay errors
this.object.on('error', this.emit.bind(this, 'error'));
// relay the close event
this.object.on('close', this.emit.bind(this, 'close'));
var self = this;
// when a remote has connected to this server, this TCP event will emit
this.object.on('connection', function (socket) {
// we will emit the connection event to create a new communication object
// the socket argument set here will be this.object in the new communication object
self.emit('connection', socket);
});
}
util.inherits(Listener, core.ListenerAbstract);
exports.Listener = Listener;
// this will be called from core, when lisenter.close is excuted by user
Listener.prototype.close = function () {
// TCP function there will refuse any new connections
this.object.close();
// next we end all connections
// all online communications objects are stored in this.connections
// in this case a TCP socket is stored in .object so we can just close on that one
for (var assignID in this.connections) {
this.connections[assignID].object.end();
}
};
// this will be called from core when layer-lisenter object when it emit connection
function Communication() {
// setup abstract layer
core.CommunicationAbstract.apply(this, arguments);
// when socket is closed we will emit the close event
this.object.on('close', this.emit.bind(this, 'close'));
// setup a json stream handler there rescive data from the TCP socket
// remember that this.object is the TCP socket
jsonStream(this, this.object);
}
util.inherits(Communication, core.CommunicationAbstract);
exports.Communication = Communication;
// this will be called from core, when a message need to be sended
// 1: when the core setup the requester object
// 2: in response to a request from the requester
Communication.prototype.send = function (message) {
// send the json message using JSON.stringify and add a linebreak to indicate json-end
// also encode as utf8
this.object.write(JSON.stringify(message) + '\n', 'utf8');
};
/**
* Requester
*/
// this will be called from core when requester.connect is executed by user
function Requester() {
// setup abstract layer
core.RequesterAbstract.apply(this, arguments);
// parse the argument
if (typeof this.object === 'number' || typeof this.object === 'string') {
this.object = net.createConnection(this.object);
} else if (!(this.object instanceof net.Server)) {
this.object = net.createConnection(this.object.port, this.object.host);
}
// relay errors
this.object.on('error', this.emit.bind(this, 'error'));
// when socket is closed we will emit the close event
this.object.on('close', this.emit.bind(this, 'close'));
// setup a json stream handler there rescive data from the TCP socket
jsonStream(this, this.object);
}
util.inherits(Requester, core.RequesterAbstract);
exports.Requester = Requester;
// this will be called from core, when a message need to be sended
// 1: when a method in the remote object is called
Requester.prototype.send = function (message) {
// send the json message using JSON.stringify and add a linebreak to indicate json-end
// also encode as utf8
this.object.write(JSON.stringify(message) + '\n', 'utf8');
};
// this will be called from core, when requester.close is excuted by user
Requester.prototype.close = function () {
// we will close the socket gracefull by calling this.object.end
// when the socket is closed it will emit 'close'.
this.object.end();
};
})();