/** ** ============================== ** O O O OOOO ** O O O O O O ** O O O O O O ** OOOO OOOO O OOO OOOO ** O O O O O O O ** O O O O O O O ** OOOO OOOO O O OOOO ** ============================== ** Dr. Stefan Bosse http://www.bsslab.de ** ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED ** BY THE AUTHOR(S). ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, ** MODIFIED, OR OTHERWISE USED IN A CONTEXT ** OUTSIDE OF THE SOFTWARE SYSTEM. ** ** $AUTHORS: Stefan Bosse ** $INITIAL: (C) 2006-2016 bLAB ** $CREATED: 31-10-16 by sbosse. ** $VERSION: 1.1.4 ** ** $INFO: ** ** ============================================== ** DOS: Broker Connection Module ** Server, FS FIFO communication, using child process receiver !!HACK for > 2 channels!! ** Data transfer: JSON ** ============================================== * * * Two unidirectional FIFOs are used for bidirectional client-server communication. * There is one shared public channel request link, and multiple private P2P client-server links (FIFO pair). * Clients writing initially to the request channel to get a free private channel from the server. * Note: All clients simultaneously requesting a channel will receive multiple replies for different clients! * A FIFO channel is shared by all participants! * * * * Notes: * Superfluous? All http function callback computations are wrapped by scheduler callbacks * to avoid main thread preemption! * Is event queuing always guaranteed in JS? * * * ** $ENDOFINFO */ "use strict"; var log = 0; var Io = Require('com/io'); var Net = Require('dos/network'); var Buf = Require('dos/buf'); var Rpc = Require('dos/rpc'); var util = require('util'); var net = require('net'); var Sch = Require('dos/scheduler'); var Comp = Require('com/compat'); var Perv = Comp.pervasives; var String = Comp.string; var Array = Comp.array; var Filename = Comp.filename; var trace = Io.tracing; var div = Perv.div; var Conn = Require('dos/connutils'); var Fs = require('fs'); var Cp = require('child_process'); var isNode = Comp.isNodeJS(); var Mode = Conn.Mode; function receiver (path,i,verbose) { var chI, chO, pathI=path+i+'I', pathO=path+i+'O'; //process.on('SIGINT',function () {}); chO=Fs.createWriteStream(pathO); process.on('message', function(m) { //console.log(m); if (m.event=='end') { console.log('[BFIM] Stopping '+path); process.exit(0); } chO.write(m); }); function listen () { // Wait for a client connecting to the FIFO chI=Fs.createReadStream(pathI); console.log('[BFIM] Listening on '+pathI); chI.on('open',function () { if (verbose>=2) console.log('[BFIM] open '+pathI); process.send({event:'open'}); }); chI.on('close',function () { if (verbose>=2) console.log('[BFIM] close '+pathI); process.send({event:'close'}); listen(); }); chI.on('error',function () { if (verbose>=2) console.log('[BFIM] error '+pathI); process.send({event:'error'}); listen(); }); chI.on('data',function (chunk) { if (verbose>=2) console.log('[BFIM] data '+pathI+': '+chunk.length); process.send({event:'data',data:chunk.toString()}); }); } listen(); } // receiver(process.argv[2],process.argv[3]); /************************** ** FIFO Broker SERVER Interface **************************/ /** * * @param {port} hostport * @param {string} srv_url * @param {string} srv_ipport * @param {rpcrouter} router * @constructor */ // tyepof options = {hostport,path,channels,router,verbose,env,mode} // typeof mode = 'unidir'|'bidir' var Server = function(options) { this.env=options.env||{}; this.hostport=options.hostport; // Public communication Net.Port == Host port this.status = Net.Status.STD_OK; this.router=options.router; this.app=undefined; this.handlers=[]; this.client_queue=[]; this.hosts={}; this.lock=false; this.mode=Mode.TWOCHAN; this.keepalive=options.keepalive||true; // FIFO file system path this.path=options.path||'/tmp/broker'; // Number of channels this.channels=options.channels||10; this.rpccon=Rpc.RpcConn( Net.uniqport(), /* ** Connection Forward and Deliver Operation */ undefined, // Passive client conenctions: Notheing to do here, already handled by the router function(rpcio) { return true; } ) this.verbose=options.verbose||0; this.enabled=false; this.warned=0; this.last=undefined; // Transfer of multi-part messages? this.multipart=true; }; /** Client-App request (collecting pending and queued TRANSREQ/TRANSREP/LOOKUP messages) * Used only in ONECHAN mode!! */ Server.prototype.collect = function (self, msg) { // HTTP Callback handler var res, timeout=(msg.timeout != undefined) ? (Perv.int_of_string(msg.timeout)) : 0; if (timeout) msg.timeout=timeout; this.rpccon.stats.op_forward++; /* ** 1. Forward TRANS message requests if there are pending requests for the host port. ** 2. Forward TRANS message replies if there are pending replies for the host port. ** 3. Forward LOOKUP messages (broadcast) ** If there are actually no messages, add this request to the request queue (if timeout <>0). */ /* ** Forward all pending transaction requests and replies... */ Io.log(((log+self.verbose) < 2) || ('[BFIS] collect for host ' + Net.Print.port(msg.hostport)+ ' #trans='+ Array.length(self.router.trans_queue)+' #lookup='+Array.length(self.router.lookup_queue))); res=this.schedule_client(msg,true); if (res && msg.timeout) self.client_queue.push(msg); }; /** Client-App forwarding (send pending and queued TRANSREQ/TRANSREP/LOOKUP messages) * Used only in TWOCHAN mode!! Called from router. */ Server.prototype.forward = function (msg) { // HTTP Callback handler var res=0, self=this; /* ** 1. Forward TRANS message request if there are pending requests for the host port. ** 2. Forward TRANS message reply if there are pending replies for the host port. ** 3. Forward LOOKUP messages (broadcast) */ /* ** Forward all pending transaction requests and replies... */ Io.log(((log+self.verbose) < 2) || ('[BFIS] forward for host ' + Net.Print.port(msg.hostport)+ ' #trans='+ Array.length(self.router.trans_queue)+' #lookup='+Array.length(self.router.lookup_queue))); if (msg.status==undefined) msg.status='EOK'; // console.log(msg) if (this.hosts[msg.sendport]) { res++; self.send(self.reply(this.hosts[msg.sendport],msg)); } if (res) this.rpccon.stats.op_forward++; return res; }; /** Initialize this server * */ Server.prototype.init = function () { var self = this,pathI,pathO; // Service client requests this.enabled=true; this.handlers=[]; function handler(i) { var pathI=self.path+i+'I', pathO=self.path+i+'O'; self.handlers[i]=Cp.fork('/tmp/receiver.js',[self.path,i,log+self.verbose]); self.handlers[i].on('message', function(m) { // Receive results from child process // console.log(m); switch (m.event) { case 'open': Io.log(((log+self.verbose)<2)||('[BFIS] Connected on ' + pathI)); break; case 'close': Io.log(((log+self.verbose)<2)||('[BFIS] Disconnect on ' + pathI)); break; case 'error': Io.log(((log+self.verbose)<2)||('[BFIS] Disconnect on ' + pathI)); break; case 'data': var req,part,parts,data; data=m.data; Io.log(((log+self.verbose)<2)||('[BFIS] Received: ' + data.length)); Io.log(((log+self.verbose)<2)||('[BFIS] Received: ' + data)); parts=Conn.splitData(data); if (self.last) parts[0]=self.last+parts[0]; if (Array.last(parts) != '') self.last=Array.last(parts); else self.last=undefined; // console.log(parts) for(part in parts) { if (parts[part]=='') continue; req = JSON.parse(parts[part]); if (self.multipart) Conn.decode(req); req.socket=i; self.request(self,req); } break; }; }); }; Fs.writeFileSync('/tmp/receiver.js', 'var Fs = require("fs");'+receiver.toString()+'receiver(process.argv[2],process.argv[3],process.argv[4]);' ); for (var i=0;i0) { self.send(self.reply(client,{status:'EOK',data:data})); return false; } else if (client.timeout<=0) { self.send(self.reply(client,{status:'ENOENTR'})); return false; } else return true; } /* ** Schedule all pending client-side requests if there is matching input. ** Called from router function. Lock required! */ Server.prototype.schedule = function () { var self = this; if (self.lock) return; this.rpccon.stats.op_schedule++; Io.log(((log+self.verbose)<2) || ('[BFIS] server schedule ['+this.client_queue.length+'] .. ')); Io.trace(trace || ('[BFIS] server schedule ['+this.client_queue.length+'] .. ')); self.lock = true; self.client_queue = Array.filter(self.client_queue, function (client) { /* * Forward all matching pending transaction requests and replies... */ Io.log(((log+self.verbose) < 2) || ('[BFIS] schedule for client host ' + Net.Print.port(client.hostport)+ ' #trans='+ Array.length(self.router.trans_queue)+' #lookup='+Array.length(self.router.lookup_queue))); return self.schedule_client(client,true); }); self.lock = false; }; /** Send a message to a destination * */ // function send(repsonse:socket,data:string) Server.prototype.send = function (msg) { var client, self=this, socket, data; //response.writeHead(200, {'Content-Type': 'text/plain; charset=utf8'}); //response.writeHead(200); this.rpccon.stats.op_send++; socket=msg.socket; msg.socket=undefined; Conn.encode(msg); Io.log(((log+this.verbose)<2)||('[BFIS] send ['+util.inspect(msg)+'] -> '+socket)); if (socket!=undefined) { data = JSON.stringify(msg); // console.log(data); if (this.multipart) self.handlers[socket].send(data+Conn.EOM); else self.handlers[socket].send(data); this.warned=0; }; }; /** * * @param {number} [interval] */ Server.prototype.start = function (interval) { var self = this; if (!interval) interval=1000; /* ** Start a client service and garbage collector for blocked client-side transactions (self.client_queue) */ Sch.AddTimer(interval, 'Broker Client Service and Garbage Collector', function (context) { var p; Io.log(((log+self.verbose)<4)||('[BFIS] Client Service and Garbage Collector')); if (!self.lock) { self.lock = true; if (self.mode==Mode.ONECHAN) self.client_queue = Array.filter(self.client_queue, function (client /* msg */) { client.timeout=client.timeout-interval; return self.schedule_client(client,true); }); else { for (p in self.hosts) { // console.log(self.hosts[p]) if (self.hosts[p]) { self.hosts[p].timeout = self.hosts[p].timeout - interval; if (self.hosts[p].timeout <= 0) { Io.log(((log+self.verbose)<1)||('[BFIS] Host cache: Removing host '+ ' hostport='+Net.Print.port(self.hosts[p].hostport))); self.hosts[p]=undefined; } } } } self.lock = false; } else { context.blocked=false; context.timer=Sch.time; Sch.ScheduleNext(); } }); }; Server.prototype.stop = function () { for (var i=0;i