stomp-client
Version:
A STOMP protocol implementation in node.js
240 lines (211 loc) • 6.45 kB
JavaScript
var net = require('net');
var fs = require('fs');
var util = require('util');
var crypto = require('crypto');
var StompFrame = require('./frame').StompFrame;
var StompFrameEmitter = require('./parser').StompFrameEmitter;
//var privateKey = fs.readFileSync('CA/newkeyopen.pem', 'ascii');
//var certificate = fs.readFileSync('CA/newcert.pem', 'ascii');
//var certificateAuthority = fs.readFileSync('CA/demoCA/private/cakey.pem', 'ascii');
/*
var credentials = crypto.createCredentials({
key: privateKey,
cert: certificate,
ca: certificateAuthority,
});
*/
var StompClientCommands = ['CONNECT', 'SEND', 'SUBSCRIBE', 'UNSUBSCRIBE', 'BEGIN', 'COMMIT', 'ACK', 'ABORT', 'DISCONNECT'];
function StompSubscription(stream, session, ack) {
this.ack = ack;
this.session = session;
this.stream = stream;
}
StompSubscription.prototype.send = function(stompFrame) {
stompFrame.send(this.stream);
};
function StompQueueManager() {
this.queues = {};
this.msgId = 0;
this.sessionId = 0;
}
StompQueueManager.prototype.generateMessageId = function() {
return this.msgId++;
};
StompQueueManager.prototype.generateSessionId = function() {
return this.sessionId++;
};
StompQueueManager.prototype.subscribe = function(queue, stream, session, ack) {
if (!(queue in this.queues)) {
this.queues[queue] = [];
}
this.queues[queue].push(new StompSubscription(stream, session, ack));
};
StompQueueManager.prototype.publish = function(queue, message) {
if (!(queue in this.queues)) {
throw new StompFrame({
command: 'ERROR',
headers: {
message: 'Queue does not exist'
},
body: 'Queue "' + frame.headers.destination + '" does not exist'
});
}
var message = new StompFrame({
command: 'MESSAGE',
headers: {
'destination': queue,
'message-id': this.generateMessageId(),
},
body: message,
});
this.queues[queue].map(function(subscription) {
subscription.send(message);
});
};
StompQueueManager.prototype.unsubscribe = function(queue, session) {
if (!(queue in this.queues)) {
throw new StompFrame({
command: 'ERROR',
headers: {
message: 'Queue does not exist'
},
body: 'Queue "' + frame.headers.destination + '" does not exist'
});
}
// TODO: Profile this
this.queues[queue] = this.queues[queue].filter(function(subscription) {
return (subscription.session != session);
});
};
function StompStreamHandler(stream, queueManager) {
var frameEmitter = new StompFrameEmitter(StompClientCommands);
var authenticated = false;
var sessionId = -1;
var subscriptions = [];
var transactions = {};
stream.on('data', function(data) {
frameEmitter.handleData(data);
});
stream.on('end', function() {
subscriptions.map(function(queue) {
queueManager.unsubscribe(queue, sessionId);
});
stream.end();
});
frameEmitter.on('frame', function(frame) {
if (!authenticated && frame.command != 'CONNECT') {
new StompFrame({
command: 'ERROR',
headers: {
message: 'Not connected'
},
body: 'You must first issue a CONNECT command'
}).send(stream);
return;
}
if (frame.command != 'CONNECT' && 'receipt' in frame.headers) {
new StompFrame({
command: 'RECEIPT',
headers: {
'receipt-id': frame.headers.receipt
}
}).send(stream);
}
try {
switch (frame.command) {
case 'CONNECT':
// TODO: Actual authentication
authenticated = true;
sessionId = queueManager.generateSessionId();
new StompFrame({
command: 'CONNECTED',
headers: {
session: sessionId
}
}).send(stream);
break;
case 'SUBSCRIBE':
queueManager.subscribe(frame.headers.destination, stream, sessionId, frame.headers.ack || "auto");
subscriptions.push(frame.headers.destination);
break;
case 'UNSUBSCRIBE':
queueManager.unsubscribe(frame.headers.destination, sessionId);
break;
case 'SEND':
queueManager.publish(frame.headers.destination, frame.body);
break;
case 'BEGIN':
if (frame.headers.transaction in transactions) {
throw new StompFrame({
command: 'ERROR',
headers: {
message: 'Transaction already exists'
},
body: 'Transaction "' + frame.headers.transaction + '" already exists'
});
}
transactions[frame.headers.transaction] = [];
break;
case 'COMMIT':
// TODO: Actually apply the transaction, this is just an abort
delete transactions[frame.headers.transaction];
break;
case 'ABORT':
delete transactions[frame.headers.transaction];
break;
case 'DISCONECT':
subscriptions.map(function(queue) {
queueManager.unsubscribe(queue, sessionId);
});
stream.end();
break;
}
} catch (e) {
e.send(stream);
}
});
frameEmitter.on('error', function(err) {
var response = new StompFrame();
response.setCommand('ERROR');
response.setHeader('message', err['message']);
if ('details' in err) {
response.appendToBody(err['details']);
}
response.send(stream);
});
}
function StompServer(port) {
this.port = port;
var queueManager = new StompQueueManager();
this.server = net.createServer(function(stream) {
stream.on('connect', function() {
console.log('Received Unsecured Connection');
new StompStreamHandler(stream, queueManager);
});
});
}
function SecureStompServer(port, credentials) {
StompServer.call(this);
var queueManager = new StompQueueManager();
this.port = port;
this.server = net.createServer(function(stream) {
stream.on('connect', function() {
console.log('Received Connection, securing');
stream.setSecure(credentials);
});
stream.on('secure', function() {
new StompStreamHandler(stream, queueManager);
});
});
}
util.inherits(SecureStompServer, StompServer);
StompServer.prototype.listen = function() {
this.server.listen(this.port, 'localhost');
};
StompServer.prototype.stop = function(port) {
this.server.close();
};
//new SecureStompServer(8124, credentials).listen();
exports.createStompServer = function(port) {
return new StompServer(port);
};