stomp
Version:
Implementation of the STOMP protocol in node.js
460 lines (399 loc) • 12.3 kB
JavaScript
// ## stomp
//
// The `Stomp` module provides you with a client interface for interacting with STOMP messaging brokers
//
// ### stomp.Stomp
//
// An instance of the `Stomp` object. Initialized like so:
//
// var stomp_args = {
// port: 61613,
// host: 'localhost',
// debug: false,
// login: 'guest',
// passcode: 'guest',
// };
//
// var client = new stomp.Stomp(stomp_args);
//
// If debug is set to true, extra output will be printed to the console.
// ## Helpers to handle frames, and do parsing
var events = require('events'),
net = require('net'),
tls = require('tls'),
sys = require('util'),
util = require('util'),
frame = require('./frame'),
stomp_utils = require('./stomp-utils'),
exceptions = require('./stomp-exceptions'),
utils = new stomp_utils.StompUtils(),
log = null;
function parse_command(data) {
var command,
this_string = data.toString('utf8', 0, data.length);
command = this_string.split('\n');
return command[0];
};
function parse_headers(raw_headers) {
var headers = {},
headers_split = raw_headers.split('\n');
for (var i = 0; i < headers_split.length; i++) {
var header = headers_split[i].split(':');
if (header.length > 1) {
var header_key = header.shift().trim();
var header_val = header.join(':').trim();
headers[header_key] = header_val;
continue;
}
headers[header[0].trim()] = header[1].trim();
}
return headers;
};
function parse_frame(chunk) {
var args = {},
data = null,
command = null,
headers = null,
body = null,
headers_str = null;
if (!utils.really_defined(chunk)) {
return null;
}
command = parse_command(chunk);
data = chunk.slice(command.length + 1, chunk.length);
data = data.toString('utf8', 0, data.length);
var the_rest = data.split('\n\n');
headers = parse_headers(the_rest[0]);
body = the_rest.slice(1, the_rest.length);
if ('content-length' in headers) {
headers['bytes_message'] = true;
}
args = {
command: command,
headers: headers,
body: body
}
var this_frame = new frame.Frame();
var return_frame = this_frame.build_frame(args);
return return_frame;
};
function _connect(stomp) {
log = stomp.log;
if (stomp.ssl) {
log.debug('Connecting to ' + stomp.host + ':' + stomp.port + ' using SSL');
stomp.socket = tls.connect(stomp.port, stomp.host, stomp.ssl_options, function() {
log.debug('SSL connection complete');
if (!stomp.socket.authorized) {
log.error('SSL is not authorized: '+stomp.socket.authorizationError);
if (stomp.ssl_validate) {
_disconnect(stomp);
return;
}
}
_setupListeners(stomp);
});
}
else {
log.debug('Connecting to ' + stomp.host + ':' + stomp.port);
stomp.socket = new net.Socket();
stomp.socket.connect(stomp.port, stomp.host);
_setupListeners(stomp);
}
}
function _setupListeners(stomp) {
function _connected() {
log.debug('Connected to socket');
var headers = {};
if (utils.really_defined(stomp.login) &&
utils.really_defined(stomp.passcode)) {
headers.login = stomp.login;
headers.passcode = stomp.passcode;
}
if (utils.really_defined(stomp["client-id"])) {
headers["client-id"] = stomp["client-id"];
}
if (utils.really_defined(stomp["vhost"])) {
headers["host"] = stomp["vhost"];
}
stomp_connect(stomp, headers);
}
var socket = stomp.socket;
socket.on('drain', function(data) {
log.debug('draining');
});
var buffer = '';
socket.on('data', function(chunk) {
buffer += chunk;
var frames = buffer.split('\0\n');
// Temporary fix : NULL,LF is not a guranteed standard, the LF is optional, so lets deal with it. (Rauls)
if (frames.length == 1) {
frames = buffer.split('\0');
}
if (frames.length == 1) return;
buffer = frames.pop();
var parsed_frame = null;
var _frame = null;
while (_frame = frames.shift()) {
parsed_frame = parse_frame(_frame);
stomp.handle_new_frame(parsed_frame);
}
});
socket.on('end', function() {
log.debug("end");
});
socket.on('error', function(error) {
log.error(error.stack + 'error name: ' + error.name);
stomp.emit("error", error);
});
socket.on('close', function(error) {
log.debug('disconnected');
if (error) {
log.error('Disconnected with error: ' + error);
}
stomp.emit("disconnected", error);
});
if (stomp.ssl) {
_connected();
} else {
socket.on('connect', _connected);
}
};
function stomp_connect(stomp, headers) {
var _frame = new frame.Frame(),
args = {},
headers = headers || {};
args['command'] = 'CONNECT';
args['headers'] = headers;
var frame_to_send = _frame.build_frame(args);
send_frame(stomp, frame_to_send);
};
function _disconnect(stomp) {
var socket = stomp.socket;
socket.end();
if (socket.readyState == 'readOnly') {
socket.destroy();
}
log.debug('disconnect called');
};
function send_command(stomp, command, headers, body, want_receipt) {
var want_receipt = want_receipt || false;
if (!utils.really_defined(headers)) {
headers = {};
}
var args = {
'command': command,
'headers': headers,
'body': body
};
var _frame = new frame.Frame();
var this_frame = _frame.build_frame(args, want_receipt);
send_frame(stomp, this_frame);
return this_frame;
};
function send_frame(stomp, _frame) {
var socket = stomp.socket;
var frame_str = _frame.as_string();
if (socket.write(frame_str) === false) {
log.debug('Write buffered');
}
return true;
};
//
// ## Stomp - Client API
//
// Takes an argument object
//
function Stomp(args) {
events.EventEmitter.call(this);
this.port = args['port'] || 61613;
this.host = args['host'] || '127.0.0.1';
this.debug = args['debug'];
this.login = args['login'] || null;
this.passcode = args['passcode'] || null;
this.log = new stomp_utils.StompLogging(this.debug);
this._subscribed_to = {};
this.session = null;
this.ssl = args['ssl'] ? true : false;
this.ssl_validate = args['ssl_validate'] ? true : false;
this.ssl_options = args['ssl_options'] || {};
this['client-id'] = args['client-id'] || null;
if(typeof args.vhost !== 'undefined'){
this.vhost = args['vhost'] ;
}
};
// ## Stomp is an EventEmitter
util.inherits(Stomp, events.EventEmitter);
// ## Stomp.connect()
//
// **Begin connection**
//
Stomp.prototype.connect = function() {
_connect(this);
};
// ## Stomp.is_a_message(frame)
//
// **Test that `Frame` is a message**
//
// Takes a `Frame` object
//
Stomp.prototype.is_a_message = function(this_frame) {
return (this_frame.headers !== null && utils.really_defined(this_frame.headers['message-id']))
}
// ## Stomp.should_run_message_callback
//
// **Handle any registered message callbacks**
//
// Takes a `Frame` object
//
Stomp.prototype.should_run_message_callback = function(this_frame) {
var subscription = this._subscribed_to[this_frame.headers.destination];
if (this_frame.headers.destination !== null && subscription !== null) {
if (subscription.enabled && subscription.callback !== null && typeof(subscription.callback) == 'function') {
subscription.callback(this_frame.body, this_frame.headers);
}
}
}
// ## Stomp.handle\_new_frame(frame)
//
// **Handle frame based on type. Emit events when needed.**
//
// Takes a `Frame` object
//
Stomp.prototype.handle_new_frame = function(this_frame) {
switch (this_frame.command) {
case "MESSAGE":
if (this.is_a_message(this_frame)) {
this.should_run_message_callback(this_frame);
this.emit('message', this_frame);
}
break;
case "CONNECTED":
log.debug('Connected to STOMP');
this.session = this_frame.headers['session'];
this.emit('connected');
break;
case "RECEIPT":
this.emit('receipt', this_frame.headers['receipt-id']);
break;
case "ERROR":
this.emit('error', this_frame);
break;
default:
console.log("Could not parse command: " + this_frame.command);
}
};
//
// ## Stomp.disconnect()
//
// **Disconnect from STOMP broker**
//
Stomp.prototype.disconnect = function() {
_disconnect(this);
}
//
// ## Stomp.subscribe(headers, callback)
//
// **Subscribe to destination (queue or topic)**
//
// Takes a header object
//
// Takes a callback function
//
Stomp.prototype.subscribe = function(headers, callback) {
var destination = headers['destination'];
headers['session'] = this.session;
send_command(this, 'SUBSCRIBE', headers);
/**
/ Maybe we could subscribe to mulitple queues?
/ if (destination instanceof Array) {
/ for (var = i; i < 0; i++) {
/ this._subscribed_to[destination[i]] = { enabled: true, callback: callback };
/ }
/ }
/ else {
/ this._subscribed_to[destination] = { enabled: true, callback: callback };
/ }
/
*/
this._subscribed_to[destination] = { enabled: true, callback: callback };
this.log.debug('subscribed to: ' + destination + ' with headers ' + sys.inspect(headers));
};
//
// ## Stomp.unsubscribe(headers)
//
// **Unsubscribe from destination (queue or topic)**
//
// Takes a header object
//
Stomp.prototype.unsubscribe = function(headers) {
var destination = headers['destination'];
headers['session'] = this.session;
send_command(this, 'UNSUBSCRIBE', headers);
this._subscribed_to[destination].enabled = false;
this.log.debug('no longer subscribed to: ' + destination);
};
//
// ## Stomp.ack(message_id)
//
// **Acknowledge received message**
//
// Takes a string representing the message id to ack
//
Stomp.prototype.ack = function(message_id) {
send_command(this, 'ACK', {'message-id': message_id});
this.log.debug('acknowledged message: ' + message_id);
};
//
// ## Stomp.begin()
//
// **Begin transaction**
//
// Return a string representing the generated transaction id
//
Stomp.prototype.begin = function() {
var transaction_id = Math.floor(Math.random()*99999999999).toString();
send_command(this, 'BEGIN', {'transaction': transaction_id});
this.log.debug('begin transaction: ' + transaction_id);
return transaction_id;
};
//
// ## Stomp.commit(transaction_id)
//
// **Commit transaction**
//
// Takes a string representing the transaction id generated by stomp.Stomp.begin()
//
Stomp.prototype.commit = function(transaction_id) {
send_command(this, 'COMMIT', {'transaction': transaction_id});
this.log.debug('commit transaction: ' + transaction_id);
};
//
// ## Stomp.abort(transaction_id)
//
// **Abort transaction**
//
// Takes a string representing the transaction id generated by stomp.Stomp.begin()
//
Stomp.prototype.abort = function(transaction_id) {
send_command(this, 'ABORT', {'transaction': transaction_id});
this.log.debug('abort transaction: ' + transaction_id);
};
//
// ## Stomp.send(headers, want_receipt)
//
// **Send MESSAGE to STOMP broker**
//
// Takes a header object (destination is required)
//
// Takes a boolean requesting receipt of the sent message
//
// Returns a `Frame` object representing the message sent
//
Stomp.prototype.send = function(headers, want_receipt) {
var destination = headers['destination'],
body = headers['body'] || null;
delete headers['body'];
headers['session'] = this.session;
return send_command(this, 'SEND', headers, body, want_receipt)
};
module.exports.Stomp = Stomp;