Higher Order Blog

home

Stomple RC1: Combining WebSockets and Reliable Messaging

01 Jun 2010

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: 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) {//called if subscribe succeeds within timeout-period
        //subscribe succeeded... do something
    },
    failure: function(spec) {//called if subscribe fails or times out
        console.log(spec.reason);
    },
    handler: function(msg) {//called when a message arrived ('this' is scope - see below) 
        this.received = msg.body;//"this" is "someObject"
    },
    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,//which is actually the default
    timeout: 3000,
    autoReceipt: true//which is actually the default
    //...
});
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) {//update screen on chat msg receiption
	    var c = document.getElementById('content');
	    c.innerHTML = c.innerHTML + '<br>' + msg.body;
	},
	scope : anObjectIfNeeded,
	success : function() {// did subscription succeed?
        //send a test message
        client.send({
            body: "User krukow joined chatroom #stomple",
            success: function() {
              console.log("sent welcome message...");          
            },
            failure: function() {
              console.log("Failed...");
            }
            
        });
	},
	failure : function() {// did subscription fail?
		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
                //we don't wait for receipts here... Socket provies order..
			});
		}
        this.client.commit({
            failure: function() {
                Y.Assert.fail("Commit transaction failed.");
            },
            success: function(){//...
            }
        });
    }
});