
I am developing the early idea behind a message-driven SaaS application that will require the application UI to update in real time. I was aware of the cometd protocol implementations, but then came across orbited , and TCP sockets in the browser! Even better, I then discovered Apache ActiveMQ JMS broker, which hopefully will handle all the message queuing and subscription logic for my system, rather than having to roll my own. More on this on another blog entry soon.
To test this stack, I decided to adapt the STOMP protocol demo included in the v0.5 orbited distribution to also test the Haxe wrapper of the orbited STOMP protocol js library. Here's my adapted code.
To test this stack, I decided to adapt the STOMP protocol demo included in the v0.5 orbited distribution to also test the Haxe wrapper of the orbited STOMP protocol js library. Here's my adapted code.
- First, I created a Haxe extern wrapper class, STOMPClient.hx, for the orbited STOMP protocol js library:
// Extern class for orbited Stomp.js protocol library object
// Hosting html file must have "Stomp = stomp;" line so that Haxe code can access the js "stomp" object as named "Stomp"
extern class STOMPClient
{
public function new():Void;
// Call back functions
// "msg" parameter is an anonymous object containing:
// type:String
// headers:Hash
// body:String
public function onmessage(msg:Dynamic):Void;
public function onerror(msg:Dynamic):Void;
public function onopen():Void;
public function messageReceived(msg:String):Void;
// Client methods
public function connect(domain:String, port:Int, user:String, ?password:String):Void;
public function disconnect():Void;
public function send(msg:String, destination:String, ?custom_headers:Hash):Void;
public function subscribe(destination:String):Void;
public function unsubscribe(destination:String):Void;
public function begin(id:String):Void;
public function commit(id:String):Void;
public function abort(id:String):Void;
} - I also created a small wrapper for the Shell.js library associated with the orbited demos:
import js.Dom;
/**
* ...
* @author Richard J Smith
*/
// Extern class wraper for orbited client js library Shell.js
extern class Shell
{
public function new(output:HtmlDom):Void;
public function print(data:String):Void;
} - I created a Main.hx Haxe main application file to hold the initialisation logic and create the HTML button handler logic. The first bit sets up imports and the main entry method, which using dojo to provide the
addOnLoadhandler when the page has finished loading into the browser:
**
* ...
* @author Richard J Smith
*/
package test;
import Dojo;
import Shell;
import STOMPClient;
import js.Lib;
import js.Dom;
import test.DomListener;
class Main
{
public static function main():Void {
untyped __js__(" // assign "Dojo" javascript object to actual "dojo" object. Workround because Haxe requires class names to start with uppercase character
Dojo.addOnLoad(Main.initStompPage );
}
Next, the first part of the application method sets up references to Shell and STOMPClients (this is following the original javascript contained in the orbited STOMP demo index.htm file):
public static function initStompPage():Void {
Lib.document.domain = Lib.document.domain;
// Setup shell
var output = Lib.document.getElementById("
var s = new Shell(output);
untyped __js__("ORBITED_PORT = 8000");
var stomp = new STOMPClient();
The method sets up callbacks for the onmessage, onerror and onopen events generated from the STOMPClient object:
stomp.onmessage = function(msg)
{
s.print(msg.body);
};
stomp.onerror = function(msg)
{
Lib.alert(msg.body);
}
stomp.onopen = function() {
var usernameField:Text = cast js.Lib.document.getElementById('username');
s.print("Connected to ActiveMQ as user " + usernameField.value);
} - Finally for the Main.hx file, set up button event listeners (replacing the embedded
onclickhandlers in the original index.html file):
/****** Set up button listeners ***************/
// Connect
var btn1:Button = cast js.Lib.document.getElementById('connectBtn');
DomListener.add(btn1, "
{
var usernameField:Text = cast js.Lib.document.getElementById('username');
stomp.connect('localhost', 61613, usernameField.value);
});
// Subscribe
var btn2:Button = cast js.Lib.document.getElementById('subscribeBtn');
DomListener.add(btn2, "click", function(e)
{
var subChannelField:Text = cast js.Lib.document.getElementById('sub_channel');
stomp.subscribe(subChannelField.value);
});
// Unsubscribe
var btn3:Button = cast js.Lib.document.getElementById('unsubscribeBtn');
DomListener.add(btn3, "click", function(e)
{
var subChannelField:Text = cast js.Lib.document.getElementById('sub_channel');
stomp.unsubscribe(subChannelField.value);
});
// Send
var btn4:Button = cast js.Lib.document.getElementById('sendBtn');
DomListener.add(btn4, "click", function(e)
{
var inputField:Text = cast js.Lib.document.getElementById('input');
var pubChannelField:Text = cast js.Lib.document.getElementById('pub_channel');
stomp.send(inputField.value,pubChannelField.value);
});
// Send With Transaction ID
var btn5:Button = cast js.Lib.document.getElementById('sendWithTransactionIDBtn');
DomListener.add(btn5, "click", function(e)
{
var inputField:Text = cast js.Lib.document.getElementById('input');
var pubChannelField:Text = cast js.Lib.document.getElementById('pub_channel');
var transPubIDField:Text = cast js.Lib.document.getElementById('trans_pub_id');
var headers:Hash= new Hash();
headers.set("transaction", transPubIDField.value);
stomp.send(inputField.value,pubChannelField.value,headers);
});
// Begin Transaction
var btn6:Button = cast js.Lib.document.getElementById('beginTranBtn');
DomListener.add(btn6, "click", function(e)
{
var transIDField:Text = cast js.Lib.document.getElementById('trans_id');
stomp.begin(transIDField.value);
});
// Commit Transaction
var btn7:Button = cast js.Lib.document.getElementById('commitTranBtn');
DomListener.add(btn7, "click", function(e)
{
var transIDField:Text = cast js.Lib.document.getElementById('trans_id');
stomp.commit(transIDField.value);
});
// Commit Transaction
var btn7:Button = cast js.Lib.document.getElementById('commitTranBtn');
DomListener.add(btn7, "click", function(e)
{
var transIDField:Text = cast js.Lib.document.getElementById('trans_id');
stomp.commit(transIDField.value);
});
// Abort Transaction
var btn8:Button = cast js.Lib.document.getElementById('abortTranBtn');
DomListener.add(btn8, "click", function(e)
{
var transIDField:Text = cast js.Lib.document.getElementById('trans_id');
stomp.abort(transIDField.value);
});
}
} - To finish the picture, here's the containing index.html file:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link rel="stylesheet" href="style.css">
<title>JavaScript Stomp Test</title>
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.1.1/dojo/dojo.xd.js"></script>
<script src="/static/BinaryTCPSocket.js"></script>
<script src="/static/shell.js"></script>
<script src="/static/protocols/stomp/stomp.js"></script>
</head>
<body>
<h1>STOMP Test</h1>
<div id="output" style="background-color: #eee; overflow-y: scroll; height : 200px;"></div>
<button id="connectBtn">Connect</button>Name: <input id="username" value="frank">
<br>
<button id="subscribeBtn">Subscribe</button>
<button id="unsubscribeBtn">Unsubscribe</button>
channel:
<input id="sub_channel" value="/topic/home"><br>
<button id="sendBtn">Send</button>
<button id="sendWithTransactionIDBtn">Send with Transaction Id</button>
<input id="input" value="hello">
channel:
<input id="pub_channel" value="/topic/home">
transaction id:
<input id="trans_pub_id" value="trans1">
<br>
<br>
<button id="beginTranBtn">Begin Transaction</button>
<button id="commitTranBtn">Commit</button>
<button id="abortTranBtn">Abort</button>
transaction id:
<input id="trans_id" value="trans1">
<!-- Insert Haxe - generated JS file -->
<script src="/static/main.js"></script>
</body>
</html> - The index.html file is placed in the webroot folder, all the js files included in a "/static" subfolder.
- The Haxe files are compiled using the javascript compiler directive. Here's my ant build script:
<project name="StompTest" basedir="." default="buildAll">
<property name="home.dir" value="F:\orbited\Haxe"/>
<property name="project.dir" value="${home.dir}\StompTest" />
<!--<taskdef name="haxe" classpath="../../antHaxe.jar" classname="com.relivethefuture.ant.Haxe"></taskdef>-->
<taskdef name="haxe" classname="com.relivethefuture.ant.Haxe"></taskdef>
<!-- buildALL -->
<target name="buildAll" depends="buildTests">
</target>
<!-- StompTest-->
<!-- StompTest Tests-->
<target name="buildTests" depends="cleanTests,compileTests">
</target>
<target name="cleanTests" description="Clean the directories for building">
<delete file="${project.dir}\bin\Main.js"/>
</target>
<target name="compileTests">
<haxe classpath="${project.dir}\src;${project.dir}\lib\dojo\src;${project.dir}\lib\orbited\src" javascript="${project.dir}\bin\Main.js"
flags="debug,verbose" main="test.Main" />
</target>
</project> - I launch
nekotools server -p 80from the webroot directory. I launch the orbited server from the same folder (defaults to port 8000) . And launch a standard install of Apache ActiveMQ 5 (port 61616) - Launch a web browser, point it at
http://localhostYou should the browser screen at the top of this post - Click the Connect button, then Subscribe to a topic and send some messages. The messages will get thrown back to the same browser by the ActiveMQ server via the orbited server. Welcome to real-time JMS in the browser using Haxe!
No comments:
Post a Comment