768 lines
24 KiB
JavaScript
768 lines
24 KiB
JavaScript
/**
|
|
** ==============================
|
|
** 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-2017 bLAB
|
|
** $CREATED: 28-3-15 by sbosse.
|
|
** $VERSION: 1.3.4
|
|
**
|
|
** $INFO:
|
|
**
|
|
* ===========================================
|
|
* DOS: Broker communication module
|
|
* Client side, Synchronous HTTP connection (blocking)
|
|
* Data transfer: XML + EABC (compacted ASCII)
|
|
* ===========================================
|
|
*
|
|
* Notes:
|
|
* Superfluous? All http function callback computations are wrapped by scheduler callbacks to avoid main thread preemption!
|
|
* Is event queuing always guaranteed in JS?
|
|
*
|
|
* HTTP PATH/BODY <-> JSON Message Formats
|
|
*
|
|
* status:'EOK'|'ENOENTR'|'EWOULDBLOCK'
|
|
* EABC: ASCII Hexadecimal Code
|
|
* XX: Hexadecimal Number Code
|
|
* SS: String Code
|
|
*
|
|
* TRANSREQ (send RPC message to broker)
|
|
* --------
|
|
*
|
|
* { rpc:'trans',hostport:XX,tid:NN,
|
|
* data: {
|
|
* header:EABC
|
|
* data:EABC
|
|
* }
|
|
* }
|
|
*
|
|
* =>
|
|
*
|
|
* /?rpc=trans&hostport=XX:XX:XX:XX:XX:XX&tid=NNN
|
|
* <xml>
|
|
* <header>EABC</header>
|
|
* <data>EABC</data>
|
|
* </xml>
|
|
*
|
|
*
|
|
* REPLY:
|
|
*
|
|
* STATUS |
|
|
*
|
|
* <xml>
|
|
* <header>EABC</header>
|
|
* <data>EABC</data>
|
|
* </xml>
|
|
*
|
|
* {status} |
|
|
* { status,
|
|
* data: { header:EABC, data:EABC}
|
|
* }
|
|
*
|
|
* REQUEST (get RPC messages from broker)
|
|
* -------
|
|
*
|
|
* {rpc:'request',hostport:'..'}
|
|
*
|
|
* =>
|
|
*
|
|
* /?rpc=request&hostport=XX:XX:XX:XX:XX:XX
|
|
*
|
|
* REPLY:
|
|
*
|
|
* <xml>
|
|
* <rpc hostport, sendport, operation, tid>
|
|
* <header>EABC</header>
|
|
* <data>EABC</data>
|
|
* </rpc>
|
|
* <rpc>
|
|
* ..
|
|
* </rpc>
|
|
* ..
|
|
* </xml>
|
|
*
|
|
* =>
|
|
*
|
|
* { status,
|
|
* data: [
|
|
* { hostport: XX,
|
|
* sendport: XX,
|
|
* operation : Rpc.Operation,
|
|
* tid: NN,
|
|
* header:EABC, data:EABC}
|
|
* ..
|
|
* ]}
|
|
*
|
|
*
|
|
* REPLY
|
|
* -----
|
|
* {rpc:'reply',hostport:XX,sendport:XX,
|
|
* tid:NN,
|
|
* data:{header:EABC,data:EABC}
|
|
* }
|
|
*
|
|
* =>
|
|
* /?rpc=reply&hostport=XX:XX:XX:XX:XX:XX&sendport=XX:XX:XX:XX:XX:XX&tid=NN
|
|
* <xml>
|
|
* <header>EABC</header>
|
|
* <data>EABC</data>
|
|
* </xml>
|
|
*
|
|
*
|
|
*
|
|
* IAMHERE
|
|
* -------
|
|
* {type:'iamhere',hostport:XX,srvport:XX}
|
|
* =>
|
|
* /?rpc=iamhere&host=XX:XX:XX:XX:XX:XX&port==XX:XX:XX:XX:XX:XX
|
|
*
|
|
* ALIVE
|
|
* -----
|
|
* =>
|
|
* /?alive&host=XX:XX:XX:XX:XX:XX&url=SS&port=XX:XX:XX:XX:XX:XX
|
|
*
|
|
*
|
|
* ASK
|
|
* ---
|
|
* {type:'ask',hostport:XX,xname:SS}
|
|
* =>
|
|
* /&ask&host=XX:XX:XX:XX:XX:XX&xname=SS
|
|
*
|
|
* REPLY:
|
|
*
|
|
*
|
|
* NOTIFY
|
|
* ---
|
|
* {type:'notify',hostport:XX,xname:SS,xvalue:SS}
|
|
* =>
|
|
* /¬ify&host=XX:XX:XX:XX:XX:XX&xname=SS&xval=SS
|
|
*
|
|
* REPLY:
|
|
*
|
|
** $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 Conn = Require('dos/connutils');
|
|
var util = Require('util');
|
|
var http = Require('http');
|
|
var xmldoc = Require('dos/ext/xmldoc');
|
|
var Sch = Require('dos/scheduler');
|
|
var Comp = Require('com/compat');
|
|
var Perv = Comp.pervasives;
|
|
var Hashtbl = Comp.hashtbl;
|
|
var String = Comp.string;
|
|
var Rand = Comp.random;
|
|
var Array = Comp.array;
|
|
var Obj = Comp.obj;
|
|
var trace = Io.tracing;
|
|
var div = Perv.div;
|
|
var Status = Net.Status;
|
|
|
|
/** Client-side Appl. only.
|
|
** Unidirectional HTTP client-only (Browser app.) Interface with pseudo-bidirectional communication to a broker server.
|
|
*
|
|
*
|
|
* @param hostport
|
|
* @param srv_url
|
|
* @param srv_ipport
|
|
* @param [my_url]
|
|
* @param [my_ipport]
|
|
* @constructor
|
|
*/
|
|
|
|
// typeof options : {hostport,srv_ip,srv_ipport,my_ip?,my_ipport?,router,verbose?}
|
|
var httpConnection = function(options) {
|
|
/*
|
|
** Broker
|
|
*/
|
|
var self=this;
|
|
this.srv_ip=options.srv_ip; // URL
|
|
this.srv_ipport=options.srv_ipport; // URL:port
|
|
this.srv_port=undefined; // Broker host server port (== host node port), returned by ALIVE request
|
|
this.hostport=options.hostport; // Public communication Net.Port == Host port
|
|
this._status = Net.Status.STD_UNKNOWN;
|
|
this.my_ip=options.my_ip||(options.srv_ip=='127.0.0.1'?options.srv_ip:'localhost');
|
|
this.my_ipport=options.my_ipport;
|
|
this.conn_port=Net.uniqport();
|
|
this.verbose=options.verbose||0;
|
|
this.keepalive=(options.keepalive==undefined?true:options.keepalive);
|
|
this.interval=options.interval||100; // service loop interval
|
|
/*
|
|
** Pending broker request?
|
|
*/
|
|
this.pending=0;
|
|
this.waiting=false;
|
|
this.rpccon=Rpc.RpcConn(
|
|
self.conn_port,
|
|
/*
|
|
** send: RPCIO message send operation called from router
|
|
*/
|
|
function (rpcio,callback) {
|
|
// Messages are forwarded to broker server
|
|
self.send(self.format(rpcio),function () {
|
|
if (callback) callback(Net.Status.STD_OK,rpcio);
|
|
});
|
|
},
|
|
/*
|
|
** alive: Connection status
|
|
*/
|
|
function() {
|
|
return self._status==Net.Status.STD_OK;
|
|
}
|
|
);
|
|
this.rpccon.multiport=true;
|
|
this.mode=Conn.Mode.ONECHAN; // only client can initiate a message transfer
|
|
this.router=options.router;
|
|
this.stats = this.rpccon.stats;
|
|
};
|
|
|
|
|
|
|
|
|
|
/** Send the broker server an ALIVE message and wait for response
|
|
** to check the connection status.
|
|
*
|
|
* @param callback
|
|
*/
|
|
httpConnection.prototype.alive = function (callback) {
|
|
var self=this,
|
|
msg;
|
|
Io.log(((log+this.verbose)<2)||('[BHPC] ALIVE: current status: '+this._status));
|
|
this.stats.op_alive++;
|
|
msg={type:'alive',hostport:this.hostport,ip:this.my_ip,ipport:this.my_ipport};
|
|
this.send(msg, function(reply) {
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] ALIVE status: ' + reply.status));
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] ALIVE data: ' + reply.data));
|
|
if (reply.status=='EOK') {
|
|
/*
|
|
** Reply must contain the broker host server port.
|
|
*/
|
|
if (String.length(reply.data)==(Net.PORT_SIZE*2)) {
|
|
var buf=Buf.Buffer(reply.data);
|
|
self.srv_port=Buf.buf_get_port(buf);
|
|
if (self.verbose>0 && (self._status!=Net.Status.STD_OK || self.waiting))
|
|
Io.out('[BHPC] ALIVE! ['+
|
|
Net.Print.port(self.hostport)+
|
|
'] is connected to broker '+self.srv_ip+':'+self.srv_ipport+' ['+
|
|
Net.Print.port(self.srv_port)+ ']');
|
|
self._status=Net.Status.STD_OK;
|
|
self.waiting=false;
|
|
} else {
|
|
if (self.verbose>0 && (self._status==Net.Status.STD_OK||self._status==Net.Status.STD_UNKNOWN))
|
|
Io.out('[BHPC] ALIVE! Not connected to broker '+self.srv_ip+':'+self.srv_ipport+' ['+
|
|
Net.Print.port(self.srv_port)+ ']');
|
|
Io.log(((log+self.verbose)<1)||('[BHPC] Error: ALIVE returned invalid data: '+data+', '+self.srv_ip+':'+self.srv_ipport));
|
|
self._status=Net.Status.STD_IOERR;
|
|
}
|
|
} else if (reply.status!='EOK') {
|
|
if (self.verbose>=0 && (self._status==Net.Status.STD_OK||self._status==Net.Status.STD_UNKNOWN))
|
|
Io.out('[BHPC] ALIVE! Not connected to broker '+self.srv_ip+':'+self.srv_ipport+' ['+
|
|
Net.Print.port(self.srv_port)+ ']');
|
|
self._status=Net.Status.STD_IOERR;
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] Error: ALIVE ['+self.srv_ip+':'+self.srv_ipport+path+']: ' + reply.status));
|
|
};
|
|
if (callback) callback(self._status);
|
|
});
|
|
};
|
|
|
|
/** Ask the broker server for a value (e.g., a capability)..
|
|
*
|
|
* @param {string} xname
|
|
* @param {function(string)} callback
|
|
*/
|
|
httpConnection.prototype.ask = function (xname,callback) {
|
|
var self=this,
|
|
msg;
|
|
Io.log(((log+this.verbose)<2)||('[BHPC] ASK: ' + xname));
|
|
this.stats.op_ask++;
|
|
msg={type:'ask',hostport:this.hostport,xname:xname};
|
|
this.send(msg, function(reply) {
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] ASK status: '+path+' STATUS: ' + reply.status));
|
|
|
|
if (reply.status=='EOK') {
|
|
var buf=Buf.Buffer();
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] ASK data: ' + reply.data));
|
|
Buf.buf_of_hex(buf,reply.data);
|
|
if (callback) callback(Buf.buf_get_string(buf));
|
|
} else if (reply.status!='EOK') {
|
|
self._status=Net.Status.STD_IOERR;
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] Error: ASK ['+self.srv_ip+':'+self.srv_ipport+path+']: ' + reply.status));
|
|
}
|
|
});
|
|
};
|
|
|
|
/** Create a request message from rpcio object
|
|
*
|
|
*/
|
|
httpConnection.prototype.format = function (rpcio) {
|
|
var msg,data,buf,
|
|
self=this;
|
|
switch (rpcio.operation) {
|
|
case Rpc.Operation.TRANSREQ:
|
|
data = rpcio.to_json('simple');
|
|
msg={rpc:'trans',hostport:rpcio.hostport,sendport:rpcio.sendport,
|
|
hop:rpcio.hop,tid:rpcio.tid,data:data};
|
|
break;
|
|
case Rpc.Operation.TRANSREP:
|
|
data = rpcio.to_json('simple');
|
|
msg={rpc:'reply',hostport:self.router.hostport,sendport:rpcio.sendport,
|
|
hop:rpcio.hop,tid:rpcio.tid,data:data};
|
|
break;
|
|
case Rpc.Operation.LOOKUP:
|
|
msg={rpc:'lookup',hostport:self.router.hostport,srvport:rpcio.header.h_port,
|
|
hop:rpcio.hop};
|
|
break;
|
|
case Rpc.Operation.IAMHERE:
|
|
msg={rpc:'iamhere',hostport:rpcio.hostport,sendport:rpcio.sendport,
|
|
srvport:rpcio.header.h_port,hop:rpcio.hop};
|
|
break;
|
|
}
|
|
return msg;
|
|
}
|
|
|
|
/** HTTP GET request to send a messageto the server broker returning data on reply.
|
|
*
|
|
* @param path
|
|
* @param callback
|
|
*/
|
|
httpConnection.prototype.get = function (path,callback) {
|
|
var body;
|
|
var self=this;
|
|
Io.log(((log+this.verbose)<2)||('[BHPC] GET: ' + path));
|
|
Io.trace(trace||('[BHPC] GET: ' + path));
|
|
this.stats.op_get++;
|
|
var req;
|
|
if (!http.xhr) {
|
|
req = http.request({
|
|
host: self.srv_ip,
|
|
port: self.srv_ipport,
|
|
path: path,
|
|
method: 'GET',
|
|
keepAlive: this.keepalive,
|
|
headers: {
|
|
}
|
|
} , function(res) {
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] GET REPLY: '+path+' returned STATUS: ' + res.statusCode));
|
|
Io.log(((log+self.verbose)<10)||('[BHPC] GET HEADERS: ' + JSON.stringify(res.headers)));
|
|
if (res.setEncoding != null) res.setEncoding('utf8');
|
|
body = '';
|
|
res.on('data', function (chunk) {
|
|
body = body + chunk;
|
|
});
|
|
res.once('end', function () {
|
|
self._status=Net.Status.STD_OK;
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] GET REPLY DATA: '+(body.length<100?body:'..')+' [' + body.length+']'));
|
|
Io.log(((log+self.verbose)<10)||('[BHPC] GET REPLY DATA: '+body));
|
|
if (callback) callback(body);
|
|
});
|
|
});
|
|
req.once('error', function(e) {
|
|
self._status=Net.Status.STD_IOERR;
|
|
self.stats.op_error++;
|
|
Io.log(((log+self.verbose)<1)||('[BHPC] Error: GET ['+self.srv_ip+':'+self.srv_ipport+path+']: ' + e.message));
|
|
});
|
|
req.end();
|
|
} else {
|
|
// XHR Browser
|
|
http.request({
|
|
port: self.srv_ipport,
|
|
host: self.srv_ip,
|
|
path:path,
|
|
proto:'http',
|
|
method: 'GET',
|
|
keepAlive: this.keepalive,
|
|
headers: {
|
|
}
|
|
} , function(err,xhr,body) {
|
|
if (err) {
|
|
self._status=Net.Status.STD_IOERR;
|
|
self.stats.op_error++;
|
|
Io.log(((log+self.verbose)<1)||('[BHPC] Error: GET ['+self.srv_ip+':'+self.srv_ipport+path+']: ' + err));
|
|
return;
|
|
}
|
|
self._status=Net.Status.STD_OK;
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] GET REPLY DATA: '+(body.length<100?body:'..')+' [' + body.length+']'));
|
|
Io.log(((log+self.verbose)<10)||('[BHPC] GET REPLY DATA: '+body));
|
|
if (callback) callback(body);
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
/** Initialize communication module
|
|
*
|
|
*/
|
|
httpConnection.prototype.init = function (callback) {
|
|
var self=this;
|
|
if (!this.interval) this.interval=100;
|
|
this.waiting=true;
|
|
if (callback) callback();
|
|
};
|
|
|
|
httpConnection.prototype.debug = function (v) {log=v};
|
|
|
|
|
|
/** Notify the broker server about a value (e.g., a capability)..
|
|
*
|
|
* @param {string} xname
|
|
* @param {string} xval
|
|
* @param {function(string)} callback
|
|
*/
|
|
httpConnection.prototype.notify = function (xname,xval,callback) {
|
|
var self=this,
|
|
msg;
|
|
this.stats.op_notify++;
|
|
Io.log(((log+this.verbose)<2)||('[BHPC] NOTIFY: ' + xname+'='+xval));
|
|
msg={type:'notify',hostport:this.hostport,xname:xname,xvalue:xval};
|
|
this.send(msg, function(reply) {
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] NOTIFY status: '+path+' STATUS: ' + reply.status));
|
|
if (reply.status=='EOK' && callback) callback();
|
|
else if (reply.status!='EOK') {
|
|
self._status=Net.Status.STD_IOERR;
|
|
Io.log(((log+self.verbose)<1)||('[BHPC] Error: NOTIFY ['+self.srv_ip+':'+self.srv_ipport+' '+xname+'='+xval+']: ' + reply.status));
|
|
}
|
|
});
|
|
};
|
|
|
|
/** Parse reply message (repsonse to collect request) and convert to rpcio object
|
|
*
|
|
*/
|
|
|
|
httpConnection.prototype.parse = function (reply) {
|
|
var i,rpc,rpcio,res,mysrv,
|
|
routed=0,
|
|
self=this;
|
|
// console.log(reply)
|
|
if (reply.data==undefined) return 0;
|
|
if (!Obj.isArray(reply.data)) reply.data=[reply.data];
|
|
|
|
for (i in reply.data) {
|
|
rpc = reply.data[i];
|
|
if (!rpc.operation) switch (rpc.rpc) {
|
|
case ('lookup') : rpc.operation='LOOKUP'; break;
|
|
}
|
|
|
|
rpcio = self.router.pkt_get();
|
|
res=rpcio.of_json(rpc);
|
|
//console.log(rpc)
|
|
if (res==0) {
|
|
self.router.pkt_discard(rpcio);
|
|
rpcio=undefined;
|
|
}
|
|
else {
|
|
rpcio.connport=self.conn_port;
|
|
if (rpcio.operation == Rpc.Operation.TRANSREQ) {
|
|
if (rpcio.sendport==undefined) rpcio.sendport = self.hostport; // Nothing to-do?
|
|
}
|
|
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] received ' + Rpc.Print.rpcio(rpcio)));
|
|
Io.trace(trace||('[BHPC] received ' + Rpc.Print.rpcio(rpcio)));
|
|
routed++; self.router.route(rpcio);
|
|
}
|
|
}
|
|
return routed;
|
|
}
|
|
|
|
|
|
httpConnection.prototype.parseQueryString = function(url) {
|
|
var queryString = url.substring( url.indexOf('?') + 1 );
|
|
if (queryString == url) return [];
|
|
var params = {}, queries, temp, i, l;
|
|
|
|
// Split into key/value pairs
|
|
queries = queryString.split("&");
|
|
|
|
// Convert the array of strings into an object
|
|
for ( i = 0, l = queries.length; i < l; i++ ) {
|
|
temp = queries[i].split('=');
|
|
if (temp[1]==undefined) temp[1]='true';
|
|
params[temp[0]] = temp[1];
|
|
}
|
|
|
|
return params;
|
|
};
|
|
|
|
/** HTTP PUT request to send a message and data to the broker server.
|
|
*
|
|
* @param path
|
|
* @param data
|
|
* @param callback
|
|
*/
|
|
httpConnection.prototype.put = function (path,data,callback) {
|
|
var self=this,
|
|
req;
|
|
Io.log(((log+this.verbose)<2)||('[BHPC] PUT: ' + path+ ' ['+data.length+']'));
|
|
Io.trace(trace||('[BHPC] PUT: ' + path));
|
|
this.stats.op_put++;
|
|
|
|
if (!http.xhr) {
|
|
req = http.request({
|
|
host: self.srv_ip, // hostname not avail. in http-browserify
|
|
port: self.srv_ipport,
|
|
path: path,
|
|
method: 'POST',
|
|
keepAlive: this.keepalive,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'Content-Length': data.length
|
|
}
|
|
} , function(res) {
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] PUT REPLY: '+path+' returned STATUS: ' + res.statusCode));
|
|
Io.log(((log+self.verbose)<10)||('[BHPC] PUT REPLY HEADERS: ' + JSON.stringify(res.headers)));
|
|
if (res.setEncoding != null) res.setEncoding('utf8');
|
|
// TODO body=+chunk, res.on('end') ..??
|
|
res.once('data', function (chunk) {
|
|
self._status=Net.Status.STD_OK;
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] PUT REPLY DATA: '+(chunk.length<100?chunk:'..')+' [' + chunk.length+']'));
|
|
Io.log(((log+self.verbose)<10)||('[BHPC] PUT REPLY DATA: ' + chunk));
|
|
if (callback) callback(chunk);
|
|
});
|
|
});
|
|
req.once('error', function(e) {
|
|
self._status=Net.Status.STD_IOERR;
|
|
self.rpccon.stats.op_error++;
|
|
Io.log(((log+self.verbose)<1)||('[BHPC] Error: PUT ['+self.srv_ip+':'+self.srv_ipport+path+']: ' + e.message));
|
|
});
|
|
|
|
// write data to request body
|
|
req.write(data);
|
|
req.end();
|
|
} else {
|
|
// XHR Browser
|
|
http.request({
|
|
host: self.srv_ip, // hostname not avail. in http-browserify
|
|
port: self.srv_ipport,
|
|
path: path,
|
|
method: 'POST',
|
|
body:data,
|
|
keepAlive: this.keepalive,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'Content-Length': data.length
|
|
}
|
|
} , function(err,xhr,body) {
|
|
if (err) {
|
|
self._status=Net.Status.STD_IOERR;
|
|
self.rpccon.stats.op_error++;
|
|
Io.log(((log+self.verbose)<1)||('[BHPC] Error: PUT ['+self.srv_ip+':'+self.srv_ipport+path+']: '+err));
|
|
return;
|
|
}
|
|
self._status=Net.Status.STD_OK;
|
|
Io.log(((log+self.verbose)<2)||('[BHPC] PUT REPLY DATA: '+(body.length<100?chunk:'..')+' [' + body.length+']'));
|
|
Io.log(((log+self.verbose)<10)||('[BHPC] PUT REPLY DATA: ' + body));
|
|
if (callback) callback(body);
|
|
})
|
|
}
|
|
};
|
|
|
|
/** Main entry for broker requests with JSON interface. Multiplexer for HTTP GET/PUT.
|
|
* Called by router.
|
|
*
|
|
* msg: JSON
|
|
* callback : function (reply:JSON)
|
|
*/
|
|
httpConnection.prototype.send = function (msg,callback) {
|
|
var path='/?',
|
|
body;
|
|
if (msg.rpc) path += 'rpc='+msg.rpc;
|
|
else if (msg.type) path += msg.type;
|
|
if (msg.hostport) path += '&hostport='+Net.port_to_str(msg.hostport);
|
|
if (msg.srvport) path += '&srvport='+Net.port_to_str(msg.srvport);
|
|
if (msg.sendport) path += '&sendport='+Net.port_to_str(msg.sendport);
|
|
if (msg.tid!=undefined) path += '&tid='+msg.tid;
|
|
if (msg.timeout!=undefined) path += '&timeout='+msg.timeout;
|
|
if (msg.xname!=undefined) path += '&xname='+msg.xname;
|
|
if (msg.xvalue!=undefined) path += '&xvalue='+msg.xvalue;
|
|
if (msg.ip!=undefined) path += '&ip='+msg.ip;
|
|
|
|
if (msg.data!=undefined) {
|
|
body='<xml>';
|
|
if (msg.data.header) body += '<header>' + msg.data.header + '</header>';
|
|
if (msg.data.data) body += '<data>' + msg.data.data + '</data>';
|
|
body += '</xml>';
|
|
this.put(path,body,function (body) {
|
|
if (callback) {
|
|
if (Conn.is_error(body) || Conn.is_status(body)) callback({status:body});
|
|
else callback({status:'EINVALID'});
|
|
// TODO: reply? Currently not accepted by router
|
|
}
|
|
});
|
|
}
|
|
else
|
|
this.get(path,function (body) {
|
|
var xml,rpcs,rpc,i,
|
|
reply={},
|
|
elem={};
|
|
if (Conn.is_error(body) || Conn.is_status(body)) {
|
|
if (callback) callback({status:body});
|
|
}
|
|
else if (msg.type=='alive' || msg.type=='ask') callback({status:'EOK',data:body});
|
|
else if (msg.rpc != undefined) {
|
|
/*
|
|
** We can get more than one message contained in the reply: <xml><rpc>..</rpc><rpc>..</rpc>..</xml>
|
|
** including LOOKUP/WHEREIS messages
|
|
*/
|
|
// console.log(body)
|
|
xml = new xmldoc.XmlDocument(body);
|
|
if (xml.name == undefined) {
|
|
// Not a XML reply, communication error or wrong server.
|
|
if (callback) callback({status:'EINVALID'});
|
|
return;
|
|
};
|
|
reply.status='EOK';
|
|
rpcs = xml.childrenNamed('rpc');
|
|
if (!rpcs) {
|
|
/*
|
|
** Plain RPCIO <xml><header/><data/>
|
|
*/
|
|
elem.header = Conn.getData(xml.childNamed('header'));
|
|
elem.data = Conn.getData(xml.childNamed('data'));
|
|
reply.data=[elem];
|
|
} else {
|
|
reply.data=[];
|
|
for (i in rpcs) {
|
|
rpc = rpcs[i];
|
|
elem={};
|
|
if (String.equal(rpc.name,'rpc')) {
|
|
/*
|
|
** Wrapped RPCIO <rpc><header/><data/>
|
|
*/
|
|
elem.tid = rpc.attr.tid;
|
|
elem.hostport = Net.port_of_param(rpc.attr.hostport);
|
|
elem.sendport = Net.port_of_param(rpc.attr.sendport);
|
|
elem.operation = rpc.attr.operation;
|
|
elem.header = Conn.getData(rpc.childNamed('header'));
|
|
|
|
if (elem.operation=='LOOKUP' || elem.operation=='IAMHERE')
|
|
elem.data = Conn.getData(rpc);
|
|
else
|
|
elem.data = Conn.getData(rpc.childNamed('data'));
|
|
}
|
|
reply.data.push(elem);
|
|
}
|
|
}
|
|
if (callback) callback(reply);
|
|
}
|
|
});
|
|
}
|
|
|
|
/** Service handler loop collecting messages from broker.
|
|
*
|
|
*/
|
|
// function (options:{hostport,timeout},callback:function(reply))
|
|
httpConnection.prototype.service = function (interval) {
|
|
var reply,msg,
|
|
self=this,
|
|
timeout=interval*10,
|
|
step=interval/20;
|
|
|
|
Sch.AddTimer(interval, 'BHPC Service', function (context) {
|
|
Io.log(((log+self.verbose) < 4)||('[BHPC] Service run '+Status.print(self._status)+ ' '+ self.pending));
|
|
/*
|
|
** Check for available TRANS messages for THIS application identified by the app. port (name..) ...
|
|
** The broker request is blocked until RPC transactions are available or a timeout occurred.
|
|
*/
|
|
if (self._status==Net.Status.STD_OK && self.pending<=0) {
|
|
self.pending=timeout;
|
|
msg = {rpc:'request',hostport:self.hostport, timeout:timeout};
|
|
Io.log(((log+self.verbose) < 2) || ('[BHPC] Service: '+util.inspect(msg)));
|
|
self.send(msg,function (reply) {
|
|
self.pending=0;
|
|
self.stats.op_brokerrep++;
|
|
if (reply.status=='EINVALID') {
|
|
// Not a valid reply, communication error or wrong server.
|
|
self.stats.op_error++;
|
|
Io.out('[BHPC] Received invalid HTTP message w/o data');
|
|
} else if (reply.status!='ENOENTR') {
|
|
/*
|
|
** We can get more than one message contained in the reply: <xml><rpc>..</rpc><rpc>..</rpc>..</xml>
|
|
** including LOOKUP/WHEREIS messages
|
|
*/
|
|
self.parse(reply);
|
|
// Force new execution of this handler immediately
|
|
Sch.Wakeup(context);
|
|
context.timer=Sch.time;
|
|
Sch.ScheduleNext();
|
|
} else {
|
|
self.stats.op_noentr++;
|
|
// callback('ENOENTR');
|
|
// Force new execution of this handler immediately
|
|
Sch.Wakeup(context);
|
|
context.timer=Sch.time;
|
|
Sch.ScheduleNext();
|
|
}
|
|
});
|
|
} else if (self._status!=Net.Status.STD_OK) self.pending=0;
|
|
else if (self.pending>0) self.pending=self.pending-step;
|
|
});
|
|
}
|
|
|
|
|
|
|
|
httpConnection.prototype.start = function (callback) {
|
|
// Watchdog and service collector loop
|
|
this.watchdog(1000);
|
|
this.service(this.interval);
|
|
if (callback) callback();
|
|
}
|
|
|
|
httpConnection.prototype.status = function () {
|
|
return this._status;
|
|
}
|
|
|
|
httpConnection.prototype.stop = function (callback) {
|
|
// Stop watchdog & service loop
|
|
Sch.RemoveTimer('BHPC Watchdog');
|
|
Sch.RemoveTimer('BHPC Service');
|
|
if (callback) callback();
|
|
};
|
|
|
|
httpConnection.prototype.watchdog = function (interval) {
|
|
var self=this;
|
|
// Watchdog
|
|
Sch.AddTimer(interval,'BHPC Watchdog',function () {
|
|
self.alive();
|
|
});
|
|
};
|
|
|
|
module.exports = {
|
|
/**
|
|
*
|
|
* @param hostport
|
|
* @param srv_ip
|
|
* @param srv_ipport
|
|
* @param [my_ip]
|
|
* @param [my_ipport]
|
|
* @returns {httpConnection}
|
|
* @constructor
|
|
*/
|
|
/**
|
|
* type options = {hostport,srv_ip,srv_ipport,my_ip?,my_ipport?,verbose?}
|
|
*/
|
|
Connection: function(options) {
|
|
var obj = new httpConnection(options);
|
|
Object.preventExtensions(obj);
|
|
return obj;
|
|
}
|
|
};
|