I've finished
Release Candidate 1 for Stomple, my JavaScript
Stomp library which uses WebSockets to access compatible Stomp Message Brokers (which includes JMS providers via
StompConnect). Stomple is heavily inspired by Jeff Mesnil's great
stomp-websocket, but aims to be feature complete and to provide a high-level API which is more customizable, robust and easy to use.
What? From the
Stomp website:
Stomp provides an interoperable wire format so that any of the available Stomp Clients can communicate with any Stomp Message Broker to provide easy and widespread messaging interop among languages, platforms and brokers.
As mentioned, this includes JMS. Stomple is a JavaScript library which enables using browsers that support WebSockets as messaging clients, e.g., subscribing to an JMS topic in a JavaScript program running inside Chrome.
Websocket communication has less overhead than HTTP when HTTP semantics aren't needed. More importantly, WebSockets allow the
server to push updates to the browser (as opposed to the usual HTTP request-response paradigm). For a simple example, see the
screen cast of the "transactional chat example" where chat messages are pushed from the server to participating clients.
Why release Stomple when there is already stomp-websocket? First of all, Jeff did an excellent job with stomp-websocket. However, I wanted additional features and more production maturity (e.g., support for timeouts, various low- and high-level callbacks and automatic receipts).
A few notable features:
- Customizability and Callbacks. A key value of Stomple is that it shouldn't cut off access to any low-level functionality. Hence everything is customizable and accessible to the user code. All core functions support success and failure callbacks. Further, there are low-level callbacks: socketOpen, socketMessage, socketClose, socketError corresponding to the low-level websocket events. These can be used to customize behaviour for special use-cases. At a higher-level, there are callbacks onConnect, connectFailed, onError, onReceipt correspoding to the Stomp events.
- Automatic receipts. If client.autoReceipt is true (which it is by default), then all Stomp messages sent to the server include an automatically generated 'receipt' header. In the Stomp protocol, this causes the server to send a receipt for each message it receives, hence reassuring that the message has been received. If not enabled, the client can't be sure the message is actually received.
- Timeouts. By default all messages sent to the server are associated with a (configurable) 8 second timeout. So for example, suppose you subscribe to a destination, but do not receive a server receipt within 8 seconds: then your failure callback is called with reason 'timeout' and information about the message that failed. This way you are guaranteed that either your success or failure callback will always be called (but not both :)). These time-outs are pervasive and configurable per API call.
- Transaction management. In Stomp, transactions are started using the 'BEGIN' frame, corresponding to a begin function in Stomple. When you call begin, you don't have to specify a transaction-id: if you don't, one will be generated automatically for you. Further, any API calls made between a begin and a commit/abort automatically use this transaction identifier, hence grouping the calls in the same transaction (unless, of course, you explicitly provide a different or no transaction id). Further, this nests arbitrarily, and works in a stack-like manner (e.g., begin, begin, send, associates send with inner-most begin).
- Feature complete. Stomple aims to be feature complete. For example, it supports the recommended Stomp 'content-length' header, where the current stomp-websocket version does not.
- Correctness. Stomple is tested manually and has a growing automatic test-suite written in YUI3-test. Stomple passes JSLint.
I decided not to build on Jeff's library because when APIs become more complex, I prefer using configuration-objects since it makes code more readable and easy to maintain (most Stomple functions take a single object literal parameter which names the function's "many arguments").
For example to create a Stomple client and to configure it to use destination 'jms.topic.chat' as default use:
var client = Stomple.create_client({
url : "ws://localhost:61614/stomp",
destination : "jms.topic.chat",
login : "guest",
passcode : "guest"
});
A different example of using single-argument configuration objects would be subscribe:
client.subscribe({
success: function(frame) {
},
failure: function(spec) {
console.log(spec.reason);
},
handler: function(msg) {
this.received = msg.body;
},
scope: someObject
});
Note by the way that if
client.autoConnect is true (which it is by default), then you don't have to explicitly connect. Stomple will automatically connect upon your first action. The subscribe function supports an optional destination parameter in-case a default wasn't supplied when the client object was created (or in case you want to subscribe to a non-default destination).
How?
For each Stomp client frame (command): SEND, SUBSCRIBE, UNSUBSCRIBE, BEGIN, COMMIT, ABORT, ACK, DISCONNECT there are corresponding lower-case functions on the client object, e.g., send, subscribe... Defaults are specified when creating the client object. For example.
var client = Stomple.create_client({
url : "ws://localhost:61614/stomp",
destination : "jms.topic.chat",
login : "guest",
passcode : "guest",
autoConnect: true,
timeout: 3000,
autoReceipt: true
});
See Stomple.ClientPrototype for all overrides.
Once a client object is created, the user can issue commands. For example, in the chat example a typical sequence would be.
client.subscribe({
handler : function(msg) {
var c = document.getElementById('content');
c.innerHTML = c.innerHTML + '<br>' + msg.body;
},
scope : anObjectIfNeeded,
success : function() {
client.send({
body: "User krukow joined chatroom #stomple",
success: function() {
console.log("sent welcome message...");
},
failure: function() {
console.log("Failed...");
}
});
},
failure : function() {
console.log("sub fail");
}
});
Things are pretty straightforward from here (if you get Stomp :)). For more advanced examples see the automated test-suite. Here is an example of a transaction.
this.client.begin({
failure: function() {
Y.Assert.fail("Begin transaction failed.");
},
success: function(){
for (i=0;i<N;i+=1) {
this.client.send({
body: ""+msgBodies[i],
failure: sendFailed
});
}
this.client.commit({
failure: function() {
Y.Assert.fail("Commit transaction failed.");
},
success: function(){
}
});
}
});