Skip to content

Commit 3c1b46f

Browse files
committed
Major refactor.
At a high level... Fix a major bug is how I was handling data (it's a buffer, chunked). Cleaned up the client interface to make more sense. Only expose what is important to the user. Emit events for certain things; messages, connected, error, receipt. Clean up Frame to merely be an object representation of a STOMP frame. Add documentation. Add transaction support and example of use. Add package metadata for npm.
1 parent 4bcf113 commit 3c1b46f

File tree

9 files changed

+608
-281
lines changed

9 files changed

+608
-281
lines changed

doc/frame.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
## frame
2+
3+
The `Frame` module provides an object representation of a `Stomp` frame.
4+
5+
### frame.Frame
6+
7+
An instance of the `Frame` object.
8+
9+
var frame = new frame.Frame();
10+
11+
### frame.Frame.build_frame()
12+
13+
Build a frame object from an object of arguments.
14+
15+
var args = {
16+
command: '',
17+
headers: {},
18+
body: ''
19+
};
20+
21+
this_frame = frame.build_frame(args);
22+
23+
### frame.Frame.as_string()
24+
25+
A string representation of the Frame object.
26+
Useful for sending over a raw socket.
27+
28+
var frame_str = this_frame.as_string();

doc/stomp.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
## stomp
2+
3+
The `Stomp` module provides you with a client interface for interacting with STOMP messaging brokers
4+
5+
### stomp.Stomp
6+
7+
An instance of the `Stomp` object. Initialized like so:
8+
9+
var stomp_args = {
10+
port: 61613,
11+
host: 'localhost',
12+
debug: false,
13+
login: 'guest',
14+
passcode: 'guest',
15+
};
16+
17+
var client = new stomp.Stomp(stomp_args);
18+
19+
If debug is set to true, extra output will be printed to the console.
20+
21+
#### stomp.Stomp.connect()
22+
23+
Initializes the connection to the STOMP broker.
24+
25+
#### stomp.Stomp.disconnect()
26+
27+
Disconnect from the STOMP broker
28+
29+
#### stomp.Stomp.subscribe(headers)
30+
31+
Subscribe to destination (queue or topic)
32+
33+
Headers typically looks like so:
34+
35+
var headers = {
36+
destination: '/queue/queue_name',
37+
ack: 'client',
38+
};
39+
40+
#### stomp.Stomp.unsubscribe(headers)
41+
42+
Unsubscribe from destination (queue or topic)
43+
44+
Headers typically look like so:
45+
46+
var headers = {
47+
destination: '/queue/queue_name',
48+
};
49+
50+
#### stomp.Stomp.ack(message_id)
51+
52+
Acknowledge received message.
53+
Only needed if 'ack': 'client' set in `stomp.Stomp.subscribe()` headers.
54+
55+
message_id is the message number you wish to acknowledge.
56+
57+
#### stomp.Stomp.begin()
58+
59+
Begin transaction
60+
61+
returns 'transaction id'
62+
63+
#### stomp.Stomp.commit(transaction_id)
64+
65+
Commit transaction.
66+
67+
'transaction_id' will be a transaction provided by `stomp.Stomp.begin()`.
68+
69+
#### stomp.Stomp.abort(transaction_id)
70+
71+
Abort transaction.
72+
73+
'transaction_id' will be a transaction provided by `stomp.Stomp.begin()`.
74+
75+
### stomp.Stomp.send(headers, [want_receipt])
76+
77+
Send a message to the stomp broker.
78+
79+
client.send({
80+
'destination': subscribed_queue_or_topic,
81+
'body': 'Message Body",
82+
'persistent': 'true'
83+
}, false);
84+
85+
If 'want_receipt' is true, it will add a 'receipt' header to the `Frame` object.
86+
If there is a receipt, it will be emitted when received.
87+
`stomp.Stomp.send()` will return the `Frame` object sent to the broker. This is useful for debugging.
88+
89+
---
90+
91+
`stomp.Stomp` is an `EventEmitter` and will emit the following events:
92+
93+
#### Event: 'connected'
94+
95+
This event is triggered once the socket is connected and the "CONNECT" command has been sent to the STOMP broker
96+
97+
`function () { }`
98+
99+
client.on('connected', function() {
100+
// This would be a good time to subscribe to a queue or topic
101+
});
102+
103+
#### Event: 'message'
104+
105+
This event is triggered when a message is received by `stomp.Stomp` and is ready for consumption.
106+
107+
The message will be passed as an argument.
108+
109+
`function (message) { }`
110+
111+
client.on('message', function(message) {
112+
// Handle message with ack?
113+
});
114+
115+
#### Event: 'receipt'
116+
117+
This event is triggered when a receipt is received.
118+
119+
`function (receipt_id) { }`
120+
121+
client.on('receipt', function (receipt_id) {
122+
// Validation code
123+
});
124+
125+
#### Event: 'error'
126+
127+
This event is triggered when an error is received from the broker.
128+
129+
client.on('error', function(error_frame) {
130+
// Handle accordingly
131+
// console.log(error_frame.body);
132+
});

lib/frame.js

Lines changed: 45 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,66 @@
1-
var sys = require('sys'),
2-
utils = require('./stomp-utils'),
3-
exceptions = require('./stomp-exceptions');
4-
5-
function Frame(logger) {
6-
this.sock = null;
1+
/*
2+
frame.js - Frame object
3+
4+
Copyright (c) 2010, Benjamin W. Smith
5+
All rights reserved.
6+
7+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
8+
9+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
10+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or
11+
other materials provided with the distribution.
12+
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software
13+
without specific prior written permission.
14+
15+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
17+
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21+
*/
22+
23+
/**
24+
* Frame - Object representation of a STOMP frame
25+
*/
26+
function Frame() {
727
this.command = null;
828
this.headers = null;
929
this.body = null;
10-
this.reply = null;
11-
this.session = null;
12-
this.stomp_log = logger;
13-
this.rqueue = new StompQueue();
14-
this.iqueue = new IntermediateQueue();
15-
this.utils = new StompUtils();
16-
};
17-
18-
Frame.prototype.stomp_connect = function(client) {
19-
var self = this,
20-
args = {},
21-
headers = {}
22-
next_frame = null;
23-
24-
this.sock = client;
25-
args['command'] = 'CONNECT';
26-
args['headers'] = headers;
27-
frame_to_send = this.build_frame(args, true);
28-
parsed_frame = this.send_frame(frame_to_send);
29-
this.stomp_log.debug('Connected to STOMP');
30-
if ('session' in this.headers)
31-
this.session = this.headers['session'];
32-
33-
return this;
3430
};
3531

32+
/**
33+
* Build frame based on arguments provided
34+
* @param {Object} arguments needed to build frame (command, headers, body?)
35+
* @param {Bool} Indicate that you wish to get a receipt (set receipt header)
36+
* @return {Object} Frame object
37+
*/
3638
Frame.prototype.build_frame = function(args, want_receipt) {
3739
var self = this,
3840
receipt_stamp = null;
3941

40-
this.command = args['command'];
41-
this.headers = args['headers'];
42-
this.body = args['body'];
42+
self.command = args['command'];
43+
self.headers = args['headers'];
44+
self.body = args['body'];
4345

4446
if (want_receipt) {
47+
var _receipt = '';
4548
receipt_stamp = Math.floor(Math.random()*99999999999).toString();
46-
if (this.session != null) {
47-
this.headers['receipt'] = receipt_stamp + "-" + this.session;
49+
if (self.headers['session'] != undefined) {
50+
_receipt = receipt_stamp + "-" + self.headers['session'];
4851
}
4952
else {
50-
this.headers['receipt'] = receipt_stamp;
53+
_receipt = receipt_stamp;
5154
}
55+
self.headers['receipt'] = _receipt;
5256
}
53-
return this;
57+
return self;
5458
};
5559

60+
/**
61+
* String representation of Frame object
62+
* @return {String} - Frame as string
63+
*/
5664
Frame.prototype.as_string = function() {
5765
var self = this,
5866
header_strs = [],
@@ -69,113 +77,4 @@ Frame.prototype.as_string = function() {
6977
return frame;
7078
};
7179

72-
Frame.prototype.send_frame = function(frame) {
73-
self = this;
74-
75-
if (this.sock.readyState != 'open')
76-
return
77-
78-
this.sock.write(frame.as_string());
79-
80-
if ('receipt' in frame.headers) {
81-
console.log('receipt in headers');
82-
return this.get_reply();
83-
}
84-
85-
};
86-
87-
Frame.prototype.parse_frame = function(data) {
88-
89-
var self = this,
90-
args = {},
91-
headers_str = null;
92-
93-
if (!this.utils.really_defined(data))
94-
return null;
95-
96-
this.command = this.parse_command(data);
97-
var _data = data.slice(this.command.length + 1, data.length);
98-
_data = _data.toString('utf8', start=0, end=_data.length);
99-
100-
the_rest = _data.split('\n\n');
101-
this.headers = this.parse_headers(the_rest[0]);
102-
this.body = the_rest[1];
103-
104-
if ('content-length' in this.headers)
105-
this.headers['bytes_message'] = true;
106-
107-
args['command'] = this.command;
108-
args['headers'] = this.headers;
109-
args['body'] = this.body;
110-
111-
var this_frame = new Frame(this.sock);
112-
var return_frame = this_frame.build_frame(args);
113-
114-
return return_frame;
115-
};
116-
117-
Frame.prototype.parse_headers = function(headers_str) {
118-
var these_headers = [],
119-
one_header = [],
120-
header_key = null,
121-
header_val = null,
122-
headers_split = headers_str.split('\n');
123-
124-
125-
for (var i = 0; i < headers_split.length; i++) {
126-
one_header = headers_split[i].split(':');
127-
if (one_header.length > 1) {
128-
header_key = one_header.shift();
129-
header_val = one_header.join(':');
130-
these_headers[header_key] = header_val;
131-
}
132-
else {
133-
these_headers[one_header[0]] = one_header[1];
134-
}
135-
}
136-
return these_headers;
137-
};
138-
139-
Frame.prototype.parse_command = function(data) {
140-
var command,
141-
this_string = data.toString('utf8', start=0, end=data.length);
142-
command = this_string.split('\n');
143-
return command[0];
144-
};
145-
146-
Frame.prototype.get_reply = function() {
147-
var self = this;
148-
149-
while (true) {
150-
try {
151-
return self.rqueue.get()
152-
}
153-
catch (error) {
154-
if (error.name == "QueueEmpty") {
155-
this_frame = self.reply;
156-
if (!self.utils.really_defined(this_frame))
157-
return null;
158-
if (this_frame.command == "MESSAGE")
159-
return self.iqueue.put(this_frame);
160-
else
161-
return self.rqueue.put(this_frame);
162-
}
163-
}
164-
}
165-
};
166-
167-
Frame.prototype.get_message = function() {
168-
var self = this;
169-
170-
while (true) {
171-
this_frame = this.rqueue.get()
172-
if (!this.utils.really_defined(this_frame))
173-
return null;
174-
if (this_frame.command == "MESSAGE")
175-
return this_frame;
176-
else
177-
this.rqueue.put(this_frame);
178-
}
179-
};
180-
18180
module.exports.Frame = Frame;

0 commit comments

Comments
 (0)