index.js | |
|---|---|
stomp-js2 | 3 |Overview4 | 5 |An exercise with node.js to implement the STOMP protocol. 6 | 7 |For documentation see doc/stomp.md and doc/frame.md 8 | 9 |Installation10 | 11 |
Examples16 | 17 |Consumer18 | 19 |See examples/stomp-consumer.js 20 | 21 |Producer22 | 23 |See examples/stomp-producer.js 24 | 25 |Producer with Transaction Support26 | 27 |See examples/stomp-producer-txn.js |
28 |
29 | |
stomp-exceptions.js | |
|---|---|
QueueEmpty = exports.QueueEmpty = function() {
2 | this.name = "QueueEmpty";
3 | this.message = "Queue is Empty";
4 | };
5 |
6 | QueueEmpty.prototype.toString = function() {
7 | return this.message;
8 | };
9 |
10 | |
stomp-utils.js | |
|---|---|
var exceptions = require('./stomp-exceptions');
2 | var sys = require('util');
3 |
4 | StompLogging = exports.StompLogging = function(should_debug) {
5 | this.should_debug = should_debug;
6 | };
7 |
8 | StompLogging.prototype.debug = function(message) {
9 | if (this.should_debug) {
10 | console.log("debug: " + message);
11 | }
12 | };
13 |
14 | StompLogging.prototype.warn = function(message) {
15 | console.log("warn: " + message);
16 | };
17 |
18 | StompLogging.prototype.error = function(message, die) {
19 | console.log("error: " + message);
20 | if (die) {
21 | process.exit(1);
22 | }
23 | };
24 |
25 | StompLogging.prototype.die = function(message) {
26 | this.error(message, true);
27 | };
28 |
29 | StompUtils = exports.StompUtils = function() {
30 | this.available_utils = [];
31 | };
32 |
33 | StompUtils.prototype.really_defined = function(var_to_test) {
34 | return !(var_to_test == null || var_to_test == undefined);
35 | };
36 |
37 | StompUtils.prototype.extend = function(destination, source) {
38 | for (var property in source) {
39 | destination[property] = source[property];
40 | }
41 | return destination;
42 | };
43 |
44 | |
frame.js | |
|---|---|
frame2 | 3 |The frame.Frame6 | 7 |An instance of the
10 |
11 | frame.Frame.build_frame()12 | 13 |Build a frame object from an object of arguments. 14 | 15 | | |
Frame - Object representation of a STOMP frame | function Frame() {
22 | this.command = null;
23 | this.headers = null;
24 | this.body = null;
25 | }; |
Frame.buildframe(args, wantreceipt)26 | 27 |Build frame based on arguments provided 28 | 29 |Takes arguments object needed to build frame (command, headers, body?) 30 | 31 |Takes boolean to indicate that you wish to get a receipt (set receipt header) 32 | 33 |Returns an object representing a frame | Frame.prototype.build_frame = function(args, want_receipt) {
34 | var receipt_stamp = null;
35 |
36 | this.command = args['command'];
37 | this.headers = args['headers'];
38 | this.body = args['body'];
39 |
40 | if (want_receipt) {
41 | var _receipt = '';
42 | receipt_stamp = Math.floor(Math.random()*99999999999).toString();
43 | if (this.headers['session'] != undefined) {
44 | _receipt = receipt_stamp + "-" + this.headers['session'];
45 | }
46 | else {
47 | _receipt = receipt_stamp;
48 | }
49 | this.headers['receipt'] = _receipt;
50 | }
51 | return this;
52 | }; |
Frame.as_string()53 | 54 |String representation of Frame object 55 | 56 |Returns | Frame.prototype.as_string = function() {
57 | var header_strs = [],
58 | frame = "",
59 | command = this.command,
60 | headers = this.headers,
61 | body = this.body;
62 |
63 | for (var header in headers) {
64 | header_strs.push(header + ':' + headers[header]);
65 | }
66 |
67 | frame += command + "\n";
68 | frame += header_strs.join("\n");
69 | frame += "\n\n";
70 |
71 | if(body) {
72 | frame += body;
73 | }
74 |
75 | frame += '\x00';
76 |
77 | return frame;
78 | };
79 |
80 | module.exports.Frame = Frame;
81 |
82 | |
stomp.js | |
|---|---|
stomp2 | 3 |The stomp.Stomp6 | 7 |An instance of the
18 |
19 | If debug is set to true, extra output will be printed to the console. | |
Helpers to handle frames, and do parsing | var net = require('net'),
20 | tls = require('tls'),
21 | sys = require('util'),
22 | frame = require('./frame'),
23 | stomp_utils = require('./stomp-utils'),
24 | exceptions = require('./stomp-exceptions'),
25 | utils = new stomp_utils.StompUtils(),
26 | log = null;
27 |
28 |
29 | function parse_command(data) {
30 | var command,
31 | this_string = data.toString('utf8', 0, data.length);
32 | command = this_string.split('\n');
33 | return command[0];
34 | };
35 |
36 | function parse_headers(headers_str) {
37 | var these_headers = {},
38 | one_header = [],
39 | header_key = null,
40 | header_val = null,
41 | headers_split = headers_str.split('\n');
42 |
43 | for (var i = 0; i < headers_split.length; i++) {
44 | one_header = headers_split[i].split(':');
45 | if (one_header.length > 1) {
46 | header_key = one_header.shift();
47 | header_val = one_header.join(':');
48 | these_headers[header_key] = header_val;
49 | }
50 | else {
51 | these_headers[one_header[0]] = one_header[1];
52 | }
53 | }
54 | return these_headers;
55 | };
56 |
57 | function parse_frame(chunk) {
58 | var args = {},
59 | data = null,
60 | command = null,
61 | headers = null,
62 | body = null,
63 | headers_str = null;
64 |
65 | if (!utils.really_defined(chunk))
66 | return null;
67 |
68 | command = parse_command(chunk);
69 | data = chunk.slice(command.length + 1, chunk.length);
70 | data = data.toString('utf8', 0, data.length);
71 |
72 | var the_rest = data.split('\n\n');
73 | headers = parse_headers(the_rest[0]);
74 | body = the_rest.slice(1, the_rest.length);
75 |
76 | if ('content-length' in headers)
77 | headers['bytes_message'] = true;
78 |
79 | args = {
80 | command: command,
81 | headers: headers,
82 | body: body
83 | }
84 |
85 | var this_frame = new frame.Frame();
86 | var return_frame = this_frame.build_frame(args);
87 |
88 | return return_frame;
89 | };
90 |
91 | function _connect(stomp) {
92 | log = stomp.log;
93 |
94 | if (stomp.ssl) {
95 | log.debug('Connecting to ' + stomp.host + ':' + stomp.port + ' using SSL');
96 | stomp.socket = tls.connect(stomp.port, stomp.host, stomp.ssl_options, function() {
97 | log.debug('SSL connection complete');
98 | if (!stomp.socket.authorized) {
99 | log.error('SSL is not authorized: '+stomp.socket.authorizationError);
100 | if (stomp.ssl_validate) {
101 | _disconnect(stomp);
102 | return;
103 | }
104 | }
105 | _setupListeners(stomp);
106 | });
107 | } else {
108 | log.debug('Connecting to ' + stomp.host + ':' + stomp.port);
109 | stomp.socket = new net.Socket();
110 | stomp.socket.connect(stomp.port, stomp.host);
111 | _setupListeners(stomp);
112 | }
113 | }
114 |
115 | function _setupListeners(stomp) {
116 | function _connected() {
117 | log.debug('Connected to socket');
118 | var headers = {};
119 | if (utils.really_defined(stomp.login) &&
120 | utils.really_defined(stomp.passcode)) {
121 | headers.login = stomp.login;
122 | headers.passcode = stomp.passcode;
123 | }
124 | if (utils.really_defined(stomp["client-id"])) {
125 | headers["client-id"] = stomp["client-id"];
126 | }
127 | stomp_connect(stomp, headers);
128 | }
129 |
130 | var socket = stomp.socket;
131 |
132 | socket.on('drain', function(data) {
133 | log.debug('draining');
134 | });
135 |
136 | var buffer = '';
137 | socket.on('data', function(chunk) {
138 | buffer += chunk;
139 | 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) {
140 | frames = buffer.split('\0');
141 | }
142 |
143 | if (frames.length == 1) return;
144 | buffer = frames.pop();
145 |
146 | var parsed_frame = null;
147 | var _frame = null;
148 | while (_frame = frames.shift()) {
149 | parsed_frame = parse_frame(_frame);
150 | stomp.handle_new_frame(parsed_frame);
151 | }
152 | });
153 |
154 | socket.on('end', function() {
155 | log.debug("end");
156 | });
157 |
158 | socket.on('error', function(error) {
159 | log.error(error.stack + 'error name: ' + error.name);
160 | stomp.emit("error", error);
161 | });
162 |
163 | socket.on('close', function(error) {
164 | log.debug('disconnected');
165 | if (error) {
166 | log.error('Disconnected with error: ' + error);
167 | }
168 | stomp.emit("disconnected", error);
169 | });
170 |
171 | if (stomp.ssl) {
172 | _connected();
173 | } else {
174 | socket.on('connect', _connected);
175 | }
176 | };
177 |
178 | function stomp_connect(stomp, headers) {
179 | var _frame = new frame.Frame(),
180 | args = {},
181 | headers = headers || {};
182 |
183 | args['command'] = 'CONNECT';
184 | args['headers'] = headers;
185 |
186 | var frame_to_send = _frame.build_frame(args);
187 |
188 | send_frame(stomp, frame_to_send);
189 | };
190 |
191 | function _disconnect(stomp) {
192 | var socket = stomp.socket;
193 | socket.end();
194 | if (socket.readyState == 'readOnly')
195 | socket.destroy();
196 | log.debug('disconnect called');
197 | };
198 |
199 | function send_command(stomp, command, headers, body, want_receipt) {
200 | var want_receipt = want_receipt || false;
201 | if (!utils.really_defined(headers))
202 | headers = {};
203 |
204 | var args = {
205 | 'command': command,
206 | 'headers': headers,
207 | 'body': body
208 | };
209 |
210 | var _frame = new frame.Frame();
211 | var this_frame = _frame.build_frame(args, want_receipt);
212 | send_frame(stomp, this_frame);
213 |
214 | return this_frame;
215 | };
216 |
217 | function send_frame(stomp, _frame) {
218 | var socket = stomp.socket;
219 | var frame_str = _frame.as_string();
220 |
221 | if (socket.write(frame_str) === false) {
222 | log.debug('Write buffered');
223 | }
224 |
225 | return true;
226 | }; |
Stomp - Client API227 | 228 |Takes an argument object | function Stomp(args) {
229 | this.port = args['port'] || 61613;
230 | this.host = args['host'] || "127.0.0.1";
231 | this.debug = args['debug'];
232 | this.login = args['login'] || null;
233 | this.passcode = args['passcode'] || null;
234 | this.log = new StompLogging(this.debug);
235 | this._subscribed_to = {};
236 | this.session = null;
237 | this.ssl = args['ssl'] ? true : false;
238 | this.ssl_validate = args['ssl_validate'] ? true : false;
239 | this.ssl_options = args['ssl_options'] || {};
240 | this['client-id'] = args['client-id'] || null;
241 | }; |
Stomp is an EventEmitter | Stomp.prototype = new process.EventEmitter(); |
Stomp.connect()242 | 243 |Begin connection | Stomp.prototype.connect = function() {
244 | _connect(this);
245 | }; |
Stomp.handle_new_frame()246 | 247 |Handle frame based on type. Emit events when needed. 248 | 249 |Takes a | Stomp.prototype.handle_new_frame = function(this_frame) {
250 | switch (this_frame.command) {
251 | case "MESSAGE":
252 | if (utils.really_defined(this_frame.headers['message-id'])) {
253 | if (this_frame.headers !== null && this_frame.headers.destination !== null && this._subscribed_to[this_frame.headers.destination] !== null) {
254 | var subscription = this._subscribed_to[this_frame.headers.destination];
255 | if (subscription.enabled && subscription.callback !== null && typeof(subscription.callback) == 'function') {
256 | subscription.callback(this_frame.body, this_frame.headers);
257 | }
258 | }
259 | this.emit('message', this_frame);
260 | }
261 |
262 | break;
263 | case "CONNECTED":
264 | log.debug('Connected to STOMP');
265 | this.session = this_frame.headers['session'];
266 | this.emit('connected');
267 | break;
268 | case "RECEIPT":
269 | this.emit('receipt', this_frame.headers['receipt-id']);
270 | break;
271 | case "ERROR":
272 | this.emit('error', this_frame);
273 | break;
274 | default:
275 | console.log("Could not parse command: " + this_frame.command);
276 | }
277 | }; |
Stomp.disconnect()278 | 279 |Disconnect from STOMP broker | Stomp.prototype.disconnect = function() {
280 | _disconnect(this);
281 | } |
Stomp.subscribe(headers, callback)282 | 283 |Subscribe to destination (queue or topic) 284 | 285 |Takes a header object 286 | 287 |Takes a callback function | Stomp.prototype.subscribe = function(headers, callback) {
288 | var destination = headers['destination'];
289 | headers['session'] = this.session;
290 | send_command(this, 'SUBSCRIBE', headers);
291 |
292 | /**
293 | / Maybe we could subscribe to mulitple queues?
294 | / if (destination instanceof Array) {
295 | / for (var = i; i < 0; i++) {
296 | / this._subscribed_to[destination[i]] = { enabled: true, callback: callback };
297 | / }
298 | / }
299 | / else {
300 | / this._subscribed_to[destination] = { enabled: true, callback: callback };
301 | / }
302 | /
303 | */
304 |
305 | this._subscribed_to[destination] = { enabled: true, callback: callback };
306 | this.log.debug('subscribed to: ' + destination + ' with headers ' + sys.inspect(headers));
307 | }; |
Stomp.unsubscribe()308 | 309 |Unsubscribe from destination (queue or topic) 310 | 311 |Takes a header object | Stomp.prototype.unsubscribe = function(headers) {
312 | var destination = headers['destination'];
313 | headers['session'] = this.session;
314 | send_command(this, 'UNSUBSCRIBE', headers);
315 | this._subscribed_to[destination].enabled = false;
316 | this.log.debug('no longer subscribed to: ' + destination);
317 | }; |
Stomp.ack()318 | 319 |Acknowledge received message 320 | 321 |Takes a string representing the message id to ack | Stomp.prototype.ack = function(message_id) {
322 | send_command(this, 'ACK', {'message-id': message_id});
323 | this.log.debug('acknowledged message: ' + message_id);
324 | }; |
Stomp.begin()325 | 326 |Begin transaction 327 | 328 |Return a string representing the generated transaction id | Stomp.prototype.begin = function() {
329 | var transaction_id = Math.floor(Math.random()*99999999999).toString();
330 | send_command(this, 'BEGIN', {'transaction': transaction_id});
331 | this.log.debug('begin transaction: ' + transaction_id);
332 | return transaction_id;
333 | }; |
Stomp.commit()334 | 335 |Commit transaction 336 | 337 |Takes a string representing the transaction id generated by stomp.Stomp.begin() | Stomp.prototype.commit = function(transaction_id) {
338 | send_command(this, 'COMMIT', {'transaction': transaction_id});
339 | this.log.debug('commit transaction: ' + transaction_id);
340 | }; |
Stomp.abort()341 | 342 |Abort transaction 343 | 344 |Takes a string representing the transaction id generated by stomp.Stomp.begin() | Stomp.prototype.abort = function(transaction_id) {
345 | send_command(this, 'ABORT', {'transaction': transaction_id});
346 | this.log.debug('abort transaction: ' + transaction_id);
347 | }; |
Stomp.send()348 | 349 |Send MESSAGE to STOMP broker 350 | 351 |Takes a header object (destination is required) 352 | 353 |Takes a boolean requesting recipt of the sent message 354 | 355 |Returns a | Stomp.prototype.send = function(headers, want_receipt) {
356 | var destination = headers['destination'],
357 | body = headers['body'] || null;
358 | delete headers['body'];
359 | headers['session'] = this.session;
360 | return send_command(this, 'SEND', headers, body, want_receipt)
361 | };
362 |
363 | module.exports.Stomp = Stomp;
364 |
365 | |