|
@ -0,0 +1,580 @@
|
|
|
function hashSize(obj) {
|
|
|
var size = 0, key;
|
|
|
for (key in obj) {
|
|
|
if (obj.hasOwnProperty(key)) size++;
|
|
|
}
|
|
|
return size;
|
|
|
}
|
|
|
|
|
|
function uuid() {
|
|
|
//http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
|
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
|
|
return v.toString(16);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function string2Buffer(str) {
|
|
|
var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
|
|
|
var bufView = new Int16Array(buf);
|
|
|
for (var i = 0, strLen = str.length; i < strLen; i++) {
|
|
|
bufView[i] = str.charCodeAt(i);
|
|
|
}
|
|
|
return bufView;
|
|
|
}
|
|
|
|
|
|
function buffer2String(buf) {
|
|
|
return String.fromCharCode.apply(null, buf);
|
|
|
}
|
|
|
|
|
|
//First, checks if it isn't implemented yet.
|
|
|
if (!String.prototype.format) {
|
|
|
String.prototype.format = function () {
|
|
|
var args = arguments;
|
|
|
return this.replace(/{(\d+)}/g, function (match, number) {
|
|
|
return typeof args[number] != 'undefined'
|
|
|
? args[number]
|
|
|
: match
|
|
|
;
|
|
|
});
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function inherits(ctor, superCtor) {
|
|
|
ctor.super_ = superCtor;
|
|
|
ctor.prototype = Object.create(superCtor.prototype, {
|
|
|
constructor: {
|
|
|
value: ctor,
|
|
|
enumerable: false,
|
|
|
writable: true,
|
|
|
configurable: true
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
|
|
|
// /////////////////////////////////////////////////////////////////
|
|
|
|
|
|
function Meta(meta) {
|
|
|
this.status = null;
|
|
|
|
|
|
this.method = "GET";
|
|
|
this.url = "/";
|
|
|
this.path = null;
|
|
|
this.params = null;
|
|
|
|
|
|
if (!meta || meta == "") return;
|
|
|
|
|
|
var blocks = meta.split(" ");
|
|
|
var method = blocks[0];
|
|
|
if (Meta.HttpMethod.indexOf(method) == -1) {
|
|
|
this.status = blocks[1];
|
|
|
return;
|
|
|
}
|
|
|
this.url = blocks[1];
|
|
|
this.decodeUrl(this.url);
|
|
|
}
|
|
|
Meta.HttpMethod = ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"];
|
|
|
Meta.HttpStatus = {
|
|
|
"200": "OK",
|
|
|
"201": "Created",
|
|
|
"202": "Accepted",
|
|
|
"204": "No Content",
|
|
|
"206": "Partial Content",
|
|
|
"301": "Moved Permanently",
|
|
|
"304": "Not Modified",
|
|
|
"400": "Bad Request",
|
|
|
"401": "Unauthorized",
|
|
|
"403": "Forbidden",
|
|
|
"404": "Not Found",
|
|
|
"405": "Method Not Allowed",
|
|
|
"416": "Requested Range Not Satisfiable",
|
|
|
"500": "Internal Server Error"
|
|
|
};
|
|
|
|
|
|
Meta.prototype.toString = function () {
|
|
|
if (this.status) {
|
|
|
var desc = Meta.HttpStatus[this.status];
|
|
|
if (!desc) desc = "Unknown Status";
|
|
|
return "HTTP/1.1 {0} {1}".format(this.status, desc);
|
|
|
}
|
|
|
return "{0} {1} HTTP/1.1".format(this.method, this.url);
|
|
|
};
|
|
|
|
|
|
Meta.prototype.getParam = function (key) {
|
|
|
if (!this.params) {
|
|
|
return undefined;
|
|
|
}
|
|
|
return this.params[key];
|
|
|
};
|
|
|
Meta.prototype.setParam = function (key, val) {
|
|
|
if (!this.params) {
|
|
|
this.params = {};
|
|
|
}
|
|
|
this.params[key] = val;
|
|
|
};
|
|
|
|
|
|
Meta.prototype.setUrl = function (url) {
|
|
|
this.url = url;
|
|
|
this.decodeUrl(url);
|
|
|
}
|
|
|
|
|
|
Meta.prototype.decodeUrl = function (cmdStr) {
|
|
|
var idx = cmdStr.indexOf("?");
|
|
|
if (idx < 0) {
|
|
|
this.path = cmdStr;
|
|
|
} else {
|
|
|
this.path = cmdStr.substring(0, idx);
|
|
|
}
|
|
|
if (this.path.charAt(0) == '/') {
|
|
|
this.path = this.path.substring(1);
|
|
|
}
|
|
|
if (idx < 0) return;
|
|
|
|
|
|
var paramStr = cmdStr.substring(idx + 1);
|
|
|
this.params = {};
|
|
|
var kvs = paramStr.split("&");
|
|
|
for (var i in kvs) {
|
|
|
var kv = kvs[i];
|
|
|
idx = kv.indexOf("=");
|
|
|
if (idx < 0) {
|
|
|
util.debug("omit: " + kv);
|
|
|
continue;
|
|
|
}
|
|
|
var key = kv.substring(0, idx);
|
|
|
var val = kv.substring(idx + 1);
|
|
|
this.params[key] = val;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
|
|
|
//HTTP Message
|
|
|
|
|
|
function Message(body) {
|
|
|
this.meta = new Meta();
|
|
|
this.head = {};
|
|
|
this.setBody(body);
|
|
|
}
|
|
|
|
|
|
Message.HEARTBEAT = "heartbeat";
|
|
|
Message.REMOTE_ADDR = "remote-addr";
|
|
|
Message.ENCODING = "encoding";
|
|
|
|
|
|
Message.CMD = "cmd";
|
|
|
Message.BROKER = "broker";
|
|
|
Message.TOPIC = "topic";
|
|
|
Message.MQ = "mq";
|
|
|
Message.ID = "id";
|
|
|
Message.ACK = "ack";
|
|
|
Message.SENDER = "sender";
|
|
|
Message.RECVER = "recver";
|
|
|
Message.ORIGIN_URL = "origin_url";
|
|
|
Message.ORIGIN_ID = "rawid";
|
|
|
Message.ORIGIN_STATUS = "reply_code"
|
|
|
|
|
|
|
|
|
Message.prototype.getHead = function (key) {
|
|
|
return this.head[key];
|
|
|
};
|
|
|
|
|
|
Message.prototype.setHead = function (key, val) {
|
|
|
this.head[key] = val;
|
|
|
};
|
|
|
|
|
|
Message.prototype.removeHead = function (key) {
|
|
|
delete this.head[key];
|
|
|
};
|
|
|
|
|
|
|
|
|
Message.prototype.getMq = function () {
|
|
|
return this.getHead(Message.MQ);
|
|
|
};
|
|
|
|
|
|
Message.prototype.setMq = function (val) {
|
|
|
this.setHead(Message.MQ, val);
|
|
|
};
|
|
|
|
|
|
Message.prototype.getId = function () {
|
|
|
return this.getHead(Message.ID);
|
|
|
};
|
|
|
|
|
|
Message.prototype.setId = function (val) {
|
|
|
this.setHead(Message.ID, val);
|
|
|
};
|
|
|
|
|
|
Message.prototype.getTopic = function () {
|
|
|
return this.getHead(Message.TOPIC);
|
|
|
};
|
|
|
Message.prototype.setTopic = function (val) {
|
|
|
this.setHead(Message.TOPIC, val);
|
|
|
};
|
|
|
Message.prototype.getEncoding = function () {
|
|
|
return this.getHead(Message.ENCODING);
|
|
|
};
|
|
|
Message.prototype.setEncoding = function (val) {
|
|
|
this.setHead(Message.ENCODING, val);
|
|
|
};
|
|
|
Message.prototype.isAck = function () {
|
|
|
var ack = this.getHead(Message.ACK);
|
|
|
if (!ack) return true;//default to true
|
|
|
|
|
|
return ack == '1';
|
|
|
};
|
|
|
Message.prototype.setAck = function (val) {
|
|
|
this.setHead(Message.ACK, val);
|
|
|
};
|
|
|
|
|
|
Message.prototype.getCmd = function () {
|
|
|
return this.getHead(Message.CMD);
|
|
|
};
|
|
|
Message.prototype.setCmd = function (val) {
|
|
|
this.setHead(Message.CMD, val);
|
|
|
};
|
|
|
|
|
|
Message.prototype.getSender = function () {
|
|
|
return this.getHead(Message.SENDER);
|
|
|
};
|
|
|
Message.prototype.setSender = function (val) {
|
|
|
this.setHead(Message.SENDER, val);
|
|
|
};
|
|
|
|
|
|
Message.prototype.getRecver = function () {
|
|
|
return this.getHead(Message.RECVER);
|
|
|
};
|
|
|
Message.prototype.setRecver = function (val) {
|
|
|
this.setHead(Message.RECVER, val);
|
|
|
};
|
|
|
|
|
|
Message.prototype.getOriginUrl = function () {
|
|
|
return this.getHead(Message.ORIGIN_URL);
|
|
|
};
|
|
|
Message.prototype.setOriginUrl = function (val) {
|
|
|
this.setHead(Message.ORIGIN_URL, val);
|
|
|
};
|
|
|
|
|
|
Message.prototype.getOriginStatus = function () {
|
|
|
return this.getHead(Message.ORIGIN_STATUS);
|
|
|
};
|
|
|
Message.prototype.setOriginStatus = function (val) {
|
|
|
this.setHead(Message.ORIGIN_STATUS, val);
|
|
|
};
|
|
|
|
|
|
Message.prototype.getOriginId = function () {
|
|
|
return this.getHead(Message.ORIGIN_ID);
|
|
|
};
|
|
|
Message.prototype.setOriginId = function (val) {
|
|
|
this.setHead(Message.ORIGIN_ID, val);
|
|
|
};
|
|
|
|
|
|
|
|
|
Message.prototype.getPath = function () {
|
|
|
return this.meta.path;
|
|
|
};
|
|
|
Message.prototype.getUrl = function () {
|
|
|
return this.meta.url;
|
|
|
};
|
|
|
Message.prototype.setUrl = function (url) {
|
|
|
this.meta.status = null;
|
|
|
return this.meta.setUrl(url);
|
|
|
};
|
|
|
Message.prototype.getStatus = function () {
|
|
|
return this.meta.status;
|
|
|
};
|
|
|
Message.prototype.setStatus = function (val) {
|
|
|
this.meta.status = val;
|
|
|
};
|
|
|
Message.prototype.getBodyString = function () {
|
|
|
if (!this.body) return null;
|
|
|
return buffer2String(this.body);
|
|
|
};
|
|
|
|
|
|
Message.prototype.getBody = function () {
|
|
|
if (!this.body) return null;
|
|
|
return this.body;
|
|
|
};
|
|
|
|
|
|
Message.prototype.setBody = function (val) {
|
|
|
if (val === undefined) return;
|
|
|
if (val instanceof Int16Array) {
|
|
|
this.body = val;
|
|
|
} else {
|
|
|
this.body = string2Buffer(val);
|
|
|
}
|
|
|
this.setHead('content-length', this.body.length);
|
|
|
};
|
|
|
Message.prototype.setJsonBody = function (json) {
|
|
|
this.setBody(json);
|
|
|
this.setHead('content-type', 'application/json');
|
|
|
}
|
|
|
Message.prototype.setBodyFormat = function (format) {
|
|
|
var args = Array.prototype.slice.call(arguments, 1);
|
|
|
var body = format.replace(/{(\d+)}/g, function (match, number) {
|
|
|
return typeof args[number] != 'undefined'
|
|
|
? args[number]
|
|
|
: match
|
|
|
;
|
|
|
});
|
|
|
this.setBody(body);
|
|
|
};
|
|
|
|
|
|
Message.prototype.isStatus200 = function () {
|
|
|
return "200" == this.getStatus();
|
|
|
};
|
|
|
Message.prototype.isStatus404 = function () {
|
|
|
return "404" == this.getStatus();
|
|
|
};
|
|
|
Message.prototype.isStatus500 = function () {
|
|
|
return "500" == this.getStatus();
|
|
|
};
|
|
|
|
|
|
|
|
|
Message.prototype.toString = function () {
|
|
|
var lines = new Array();
|
|
|
lines.push("{0}".format(this.meta.toString()));
|
|
|
|
|
|
for (var key in this.head) {
|
|
|
lines.push("{0}: {1}".format(key, this.head[key]));
|
|
|
}
|
|
|
var bodyLen = 0;
|
|
|
if (this.body) {
|
|
|
bodyLen = this.body.length;
|
|
|
}
|
|
|
var lenKey = "content-length";
|
|
|
if (!(lenKey in this.head)) {
|
|
|
lines.push("{0}: {1}".format(lenKey, bodyLen));
|
|
|
}
|
|
|
var bodyString = "\r\n";
|
|
|
if (bodyLen > 0) {
|
|
|
bodyString += buffer2String(this.body);
|
|
|
}
|
|
|
lines.push(bodyString);
|
|
|
|
|
|
return lines.join("\r\n");
|
|
|
};
|
|
|
|
|
|
Message.parse = function (str) {
|
|
|
var blocks = str.split("\r\n");
|
|
|
var lines = [];
|
|
|
for (var i in blocks) {
|
|
|
var line = blocks[i];
|
|
|
if (line == '') continue;
|
|
|
lines.push(line);
|
|
|
}
|
|
|
var lenKey = "content-length";
|
|
|
var lenVal = 0;
|
|
|
var msg = new Message();
|
|
|
msg.meta = new Meta(lines[0]);
|
|
|
for (var i = 1; i < lines.length; i++) {
|
|
|
var line = lines[i];
|
|
|
if (i == lines.length - 1) {
|
|
|
if (lenVal > 0) {
|
|
|
msg.setBody(line);
|
|
|
continue;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var p = line.indexOf(":");
|
|
|
if (p == -1) continue;
|
|
|
var key = line.substring(0, p).trim().toLowerCase();
|
|
|
var val = line.substring(p + 1).trim();
|
|
|
if (key == lenKey) {
|
|
|
lenVal = val;
|
|
|
}
|
|
|
|
|
|
msg.setHead(key, val);
|
|
|
}
|
|
|
|
|
|
return msg;
|
|
|
};
|
|
|
|
|
|
function Ticket(reqMsg, callback) {
|
|
|
this.id = uuid();
|
|
|
this.request = reqMsg;
|
|
|
this.response = null;
|
|
|
this.callback = callback;
|
|
|
reqMsg.setId(this.id);
|
|
|
}
|
|
|
|
|
|
var WebSocket = window.WebSocket;
|
|
|
if (!WebSocket) {
|
|
|
WebSocket = window.MozWebSocket;
|
|
|
}
|
|
|
function MessageClient(address) {
|
|
|
this.address = address;
|
|
|
this.autoReconnect = true;
|
|
|
this.reconnectInterval = 3000;
|
|
|
this.ticketTable = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
MessageClient.prototype.connect = function (connectedHandler) {
|
|
|
console.log("Trying to connect to " + this.address);
|
|
|
|
|
|
this.socket = new WebSocket(this.address);
|
|
|
var client = this;
|
|
|
this.socket.onopen = function (event) {
|
|
|
console.log("Connected to " + client.address);
|
|
|
if (connectedHandler) {
|
|
|
connectedHandler(event);
|
|
|
}
|
|
|
client.heartbeatInterval = setInterval(function () {
|
|
|
var msg = new Message();
|
|
|
msg.setCmd(Message.HEARTBEAT);
|
|
|
client.invokeAsync(msg);
|
|
|
}, 300 * 1000);
|
|
|
};
|
|
|
|
|
|
this.socket.onclose = function (event) {
|
|
|
clearInterval(client.heartbeatInterval);
|
|
|
setTimeout(function () {
|
|
|
try {
|
|
|
client.connect(connectedHandler);
|
|
|
} catch (e) {
|
|
|
}//ignore
|
|
|
}, client.reconnectInterval);
|
|
|
};
|
|
|
|
|
|
this.socket.onmessage = function (event) {
|
|
|
var msg = Message.parse(event.data);
|
|
|
var msgid = msg.getId();
|
|
|
var ticket = client.ticketTable[msgid];
|
|
|
if (ticket) {
|
|
|
ticket.response = msg;
|
|
|
if (ticket.callback) {
|
|
|
ticket.callback(msg);
|
|
|
}
|
|
|
delete client.ticketTable[msgid];
|
|
|
} else {
|
|
|
console.log("Warn: drop message\n" + msg.toString());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
this.socket.onerror = function (data) {
|
|
|
console.log("Error: " + data);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
MessageClient.prototype.invokeAsync = function (msg, callback) {
|
|
|
if (this.socket.readyState != WebSocket.OPEN) {
|
|
|
console.log("socket is not open, invalid");
|
|
|
return;
|
|
|
}
|
|
|
if (callback) {
|
|
|
var ticket = new Ticket(msg, callback);
|
|
|
this.ticketTable[ticket.id] = ticket;
|
|
|
}
|
|
|
this.socket.send(msg);
|
|
|
};
|
|
|
|
|
|
|
|
|
function Proto() {
|
|
|
}
|
|
|
Proto.Produce = "produce";
|
|
|
Proto.Consume = "consume";
|
|
|
Proto.Route = "route";
|
|
|
Proto.Heartbeat = "heartbeat";
|
|
|
Proto.Admin = "admin";
|
|
|
Proto.CreateMQ = "create_mq";
|
|
|
|
|
|
function MqMode() {
|
|
|
}
|
|
|
MqMode.MQ = 1 << 0;
|
|
|
MqMode.PubSub = 1 << 1;
|
|
|
MqMode.Memory = 1 << 2;
|
|
|
|
|
|
var Broker = MessageClient;
|
|
|
//define more brokers
|
|
|
|
|
|
function MqAdmin(broker, mq) {
|
|
|
this.broker = broker;
|
|
|
this.mq = mq;
|
|
|
this.mode = 0;
|
|
|
var args = Array.prototype.slice.call(arguments, 2);
|
|
|
for (var i in args) {
|
|
|
this.mode |= args[i];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
MqAdmin.prototype.createMq = function (callback) {
|
|
|
var params = {};
|
|
|
params["mq_name"] = this.mq;
|
|
|
params["mq_mode"] = "" + this.mode;
|
|
|
|
|
|
var msg = new Message();
|
|
|
msg.setCmd(Proto.CreateMQ);
|
|
|
msg.setHead("mq_name", this.mq);
|
|
|
msg.setHead("mq_mode", "" + this.mode);
|
|
|
|
|
|
this.broker.invokeAsync(msg, callback);
|
|
|
};
|
|
|
|
|
|
|
|
|
function Producer(broker, mq) {
|
|
|
MqAdmin.call(this, broker, mq);
|
|
|
}
|
|
|
inherits(Producer, MqAdmin)
|
|
|
|
|
|
Producer.prototype.sendAsync = function (msg, callback) {
|
|
|
msg.setCmd(Proto.Produce);
|
|
|
msg.setMq(this.mq);
|
|
|
this.broker.invokeAsync(msg, callback);
|
|
|
};
|
|
|
|
|
|
function Consumer(broker, mq) {
|
|
|
MqAdmin.call(this, broker, mq);
|
|
|
}
|
|
|
|
|
|
inherits(Consumer, MqAdmin);
|
|
|
|
|
|
Consumer.prototype.take = function (callback) {
|
|
|
var msg = new Message();
|
|
|
msg.setCmd(Proto.Consume);
|
|
|
msg.setMq(this.mq);
|
|
|
if (this.topic) msg.setTopic(this.topic);
|
|
|
|
|
|
var consumer = this;
|
|
|
this.broker.invokeAsync(msg, function (res) {
|
|
|
if (res.isStatus404()) {
|
|
|
consumer.createMq(function (res) {
|
|
|
if (res.isStatus200()) {
|
|
|
console.log(consumer.mq + " created");
|
|
|
}
|
|
|
consumer.take(callback);
|
|
|
});
|
|
|
return;
|
|
|
}
|
|
|
if (res.isStatus200()) {
|
|
|
var originUrl = res.getOriginUrl();
|
|
|
var id = res.getOriginId();
|
|
|
res.removeHead(Message.ORIGIN_ID);
|
|
|
if (originUrl == null) {
|
|
|
originUrl = "/";
|
|
|
} else {
|
|
|
res.removeHead(Message.ORIGIN_URL);
|
|
|
}
|
|
|
|
|
|
res.setId(id);
|
|
|
res.setUrl(originUrl);
|
|
|
try {
|
|
|
callback(res);
|
|
|
} catch (error) {
|
|
|
console.log(error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return consumer.take(callback);
|
|
|
});
|
|
|
};
|
|
|
|
|
|
Consumer.prototype.route = function (msg) {
|
|
|
msg.setCmd(Proto.Route);
|
|
|
msg.setAck(false);
|
|
|
this.broker.invokeAsync(msg);
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|