Kaazing WebSocket Gateway

Orange guy with box of questions

Creating an XMPP-Driven Application in Adobe Flex

Technologies used: Adobe Flex, XMPP, and Kaazing WebSocket Gateway

This document contains the following sections:

Introduction

There are several innovations within the HTML 5 specification that will forever change the direction of the Web. Two of these innovations in particular--Web sockets and Server-sent events--revolutionize the way Web applications are going to be architected, developed, and deployed. Until recently, bi‑directional browser communication has been an elusive goal of the Web community. However, with updates to the HTML 5 specification, developers can now use a full-duplex communications channel that operates over a single socket.

HTML 5's WebSocket interface enables communication from the browser to any TCP-based back-end service (for example, XMPP, JMS, JMX, IMAP, and Jabber). For example, it is now possible to avoid convoluted architectures by simply channeling certain protocols to the browser over HTTP and Web applications can now be deployed without the need for a traditional Web application server.

Kaazing WebSocket Gateway delivers an enterprise WebSocket server that enables full-duplex communication from the browser to any TCP-based back-end service. Kaazing WebSocket Gateway is the first enterprise solution that understands the WebSocket protocol and enables direct communication with all major enterprise protocols (for example, Stomp, JMS, IMAP, JDBC, and Jabber).

Kaazing WebSocket Gateway eliminates the need for using convoluted server- and client-side architectures to map server-side protocols to the browser over HTTP. Web developers can use the Flash plugin's support for ActionScript and code directly against the back-end services without the need for custom Servlets or server-side programming.

Overview of XMPP

Extensible Messaging and Presence Protocol (XMPP) is an open XML technology for presence and real-time communication, developed by the Jabber open-source community in 1999. XMPP was formalized by the Internet Engineering Task Force (IETF) between 2002 and 2004 and the protocol continues to be extended through the XMPP Standards Foundation.

XMPP consists of XML streams that enable any two entities on the Internet to exchange messages, presence, and other structured information. Chat and presence are obvious candidates for real-time communication. Examples of chat solutions that use XMPP today are Google Talk and Apple iChat. For more information about XMPP, see http://www.xmpp.org/xsf.

Unlike Stomp, XMPP messages are not sent in frames, but in XML streams that must be parsed carefully. The following list of commands are some of the standard XMPP commands, but there are many other extensions, see http://www.xmpp.org/extensions/.

  • Connect and disconnect
  • Register
  • Check roster
  • Send messages
  • Set status
  • Communicate presence

Example XMPP Communication

The following is an example of XMPP communication from the client to the server. In this case, the server can be a server or another client.

First, a stream is initialized between client and server:

<?xml version='1.0'?>
  <stream:stream
  to='example.com'
  xmlns='jabber:client'
  xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>

The server's response looks something like this:

<?xml version='1.0'?>
  <stream:stream
  from='example.com'
  id='someid'
  xmlns='jabber:client'
  xmlns:stream='http://etherx.jabber.org/streams'
  version='1.0'>

Once the stream is initialized, encryption, authentication, and resource binding takes place so the client send messages to the server. After that, the client can send a chat message like this:

<message from='rocky@example.com'
 to='sean@example.com'
 xml:lang='en'>
 <body>Hi Sean</body>
</message>

The chat message response looks like this:

<message from='sean@example.com'
 to='rocky@example.com'
 xml:lang='en'>
 <body>Hi Rocky!</body>
</message>

WebSocket and XMPP

WebSocket enables direct communication from the browser to an XMPP server. Kaazing Gateway provides the XmppClient client library, which radically simplifies the application design and eliminates the need for using convoluted server- and client-side architectures to map server-side protocols to the browser over HTTP. Web developers can use ActionScript and code directly against the back-end services without the need for custom Servlets or server-side programming.

Using the XmppClient ActionScript client library, you can take advantage of many XMPP features, making Adobe Flex a first-class citizen in XMPP messaging systems. The XmppClient ActionScript library uses Kaazing's WebSocket client library, because XMPP is a text-based protocol. Since the implementation is layered on top of WebSocket--and thus the entire stack of Kaazing HTML 5 Communications client libraries--applications developed using the XmppClient ActionScript library are provided with guaranteed persistence, reliability, and message-receipt acknowledgment all the way to the browser.

Overview of Openfire

Openfire is a real-time XMPP Instant Messaging and Groupchat server licensed under the Open Source GPL. A preconfigured Openfire server is part of the Kaazing WebSocket Gateway demo bundle and you can configure a desktop client such as Spark to communicate with the Adobe Flex-based client that you are going to build in this tutorial. For more information about Openfire, see http://www.igniterealtime.org/projects/openfire/index.jsp.

Creating an XMPP-Driven Application in Adobe Flex

Before you start, take a look at what you are trying to build. The following figure shows the completed XMPP-driven Web application (in the browser) communicating with a desktop chat client (Spark):

Flex XMPP client demo

Setting Up the Development Environment

Before you start building the XMPP-driven application, you must download, install, and run the Kaazing WebSocket Gateway demo bundle and some additional software.

Kaazing WebSocket Gateway

Download, install, and run Kaazing WebSocket Gateway by performing the following steps. Refer to Getting Started for more information.

Openfire XMPP Server

Start the preconfigured Openfire XMPP server, which is part of the Kaazing WebSocket Gateway demo bundle, by performing the following steps:

  1. Navigate to OPENFIRE_HOME/bin.
  2. Run openfire. The Openfire console displays as shown in the following image:

Openfire XMPP server started

Spark XMPP Client

Download and install the Spark XMPP desktop client by performing the following steps:

  1. Download the Spark XMPP client from http://www.igniterealtime.org/projects/spark/.
  2. Install Spark by running the spark executable file.
  3. Accept the default settings.
  4. After the installation completes, select Run Spark and click Finish.
  5. Log in to Openfire with one of the preconfigured accounts: Username rocky, Password welcome1, and Server localhost as shown in the following image:xmpp tutorial

  6. The Spark client connects to the Openfire XMPP server that is already running, and displays the other (preconfigured) member (sean) in the friends group as offline as shown in the following image:

Spark client, logged in

Adobe Flex SDK

In this tutorial, you use the Adobe Flex 3 SDK to compile the ActionScript file into a SWF file, which is the target application. You can download the Adobe Flex 3 SDK from the Adobe download site.

Starter Files

Starter files are located in the KAAZING_HOME/web/tutorials/xmpp-actionscript directory of Kaazing Gateway demo bundle. These files provide a starting point to avoid having to build everything from scratch. For your convenience, a completed example file--xmpp-completed.mxml--is also included.

Creating the XMPP-Driven Application

  1. Navigate to KAAZING_HOME/web/tutorials/xmpp-actionscript.
  2. Open the xmpp.mxml file in your preferred editor. xmpp.mxml is an ActionScript file with a user interface (UI) template for the demo that contains preconfigured code for logging messages. It contains a few TODO sections and that is where you add ActionScript code to build the client using the Kaazing XMPP protocol library for Adobe Flex.
  3. Once you have familiarized yourself with the xmpp.mxml file, you can start adding the missing pieces. First, import the required libraries, including the Kaazing client libraries XmppClient, and XmppRoom that are part of the Adobe Flex client library (the SWC file located in the tutorial directory, also available in KAAZING_HOME/lib/client/flex). These Kaazing client libraries allow you to open a socket and fully leverage bi-directional binary communication for all browsers. They also auto-detect native browser support for WebSocket and Server-sent events (SSE). If the flash plug-in does not support WebSocket and SSE, the library automatically falls back to Kaazing's client-side emulation of the standard.

    Replace:

    //TODO1: Import libraries

    With:

    import mx.collections.ArrayCollection;
    import com.kaazing.gateway.client.protocol.XmppClient;
    import com.kaazing.gateway.client.protocol.XmppRoom;

  4. Next, create variables that contain the list of chat contacts at runtime, the presence options (available, dnd, and away), and the list of available chat rooms.
    Replace:

    //TODO2: Define variables

    With:

    // List of chat contacts
    [Bindable]
    private var contactList:ArrayCollection = new ArrayCollection();

    // Presence options displayed in the GUI
    [Bindable]
    private var statusOptionList:ArrayCollection = new ArrayCollection(["available", "dnd", "away"]);

    // List of chat rooms the client is currently connected to
    [Bindable]
    private var roomList:ArrayCollection = new ArrayCollection();

  5. Next, in the XmppClient control section, define variables for the server name on which the XMPP server is running, the actual XMPP client you are creating, and a dictionary object that maps the XMPP room names to XMPP room objects.
  6. Replace:

    //TODO3: XmppClient variables

  7. With:

    private var serverName:String = "localhost";
    private var client:XmppClient;
    private var rooms:Dictionary = new Dictionary();

  8. Next, add the guiConnect and the guiDisconnect functions. The guiConnect function creates the XmppClient object and connects callback handler functions to it.
    Replace:

    //TODO4: Add the guiConnect and guiDisconnect functions

  9. With:

    private function guiConnect():void {
      try {
        client = new XmppClient();

          // Attach callbacks to the chat client
          client.onopen = openHandler;
          client.onmessage = messageHandler;
          client.onpresence = presenceHandler;
          client.onclose = closedHandler;
          client.onauthenticated = authenticatedHandler;

          client.connect(serviceUri.text, serverName);
        } catch (e:Error) {
          log(e.toString());
        }
    }

    private function guiDisconnect():void {
      client.disconnect();
    }

  10. Next, add the handler functions: openHandler, messageHandler, presenceHandler, closedHandler, and authenticatedHandler. These handler callback functions were attached to the new XmppClient object in the guiConnect function and handle the interactions between the client and the XMPP server. For example, when one of the user's chat contacts changes his status to "out to lunch," the presenceHandler function updates the log window with the new information.
    Replace:

    //TODO5: Add the handler functions

    With:

    private function openHandler():void {
      var u:String = username_input.text;
      var p:String = password_input.text;
      var credentials:Object = {"username" : u, "password": p };
      client.authenticate(credentials);
    }

    private function messageHandler(message:Object):void {
      log(message.from + ":" + message.body);
    }

    private function presenceHandler(presence:Object):void {
      log("presence: ", presence.from, presence.show, presence.status);
    }

    private function closedHandler():void {
      log("DISCONNECTED");
      contactList.removeAll();
    }

    private function authenticatedHandler():void {
      log("AUTHENTICATED");
      client.bind("Home");
      getRoster();
      guiUpdateStatus();
    }

  11. Next, add the roster-related functions that populate the list of contacts. The getRoster function uses an asynchronous callback to avoid blocking the chat application while the list of contacts is fetched.
    Replace:

    //TODO6: Add roster-related functions

    With:

    private function getRoster():void {
      client.getRoster(rosterCallback);
    }

    private function rosterCallback(roster:Array):void {
      var names:Array = [];
      for (var i:int; i<roster.length; i++) {
        names.push(roster[i].jid);
      }

      contactList = new ArrayCollection(names);
    }

  12. Next, add the guiSend and guiUpdateStatus functions. The guiSend function sends chat messages to a jabber id (jid) that is selected from the contact list. The jid has the format rocky@localhost. The guiUpdateStatus function changes the values of the status and show attributes. The status attribute is a user-defined text string (for example "out to lunch") and the show attribute is a predefined value from the statusOptionList array collection ("available," "dnd," and "away" in this tutorial).
    Replace:

    //TODO7: Add the guiSend and guiUpdateStatus functions

    With:

    private function guiSend():void {
      var jid:String = ""
      var body:String = inputText.text;

      try {
        if (ContactListDisplay.selectedItem) {
          jid = ContactListDisplay.selectedItem as String;
          client.sendMessage(jid, body);
          log(username_input.text, ":", body);
        } else {
          log("Error: Could not send. Select a username from the contact list");
        }
      } catch (e:Error) {

      }

    }

    private function guiUpdateStatus():void {
      var show:String = showComboBox.selectedItem.toString();
      var status:String = statusTextInput.text;
      client.setStatus(status, show);
    }

  13. Next, add the group chat functions, starting with the guiJoinRoom function. This function allows users to join a chat room. Each room is represented as an XmppRoom object and stored in the rooms dictionary object.
    Replace:

    //TODO8: Add group chat guiJoinRoom function

    With:

    private function guiJoinRoom():void {
      var name:String = chatInputName.text;
      try {
        var room:XmppRoom = new XmppRoom(client, name, username_input.text);
        room.onerror = function(e:Error):void { groupLog(e.toString()) };
        room.onpresence = groupPresenceHandler;
        room.onmessage = groupMessageHandler;

        roomList.addItem(name);
        rooms[name] = room;
      } catch (e:Error) {
        groupLog("Cannot join a room while logged out.");
      }
    }

  14. Next, add the group chat handler functions. The handler callback functions were attached to the new XmppRoom object in the guiJoinRoom function and handle the interactions between the group chat client and the XMPP server.
    Replace:

    //TODO9: Add the group chat handler functions

    With:

    private function groupMessageHandler(message:Object):void {
      groupLog(message.from + ":" + message.body);
    }

    private function groupPresenceHandler(presence:Object):void {
      groupLog("presence: ", presence.from, presence.show, presence.status);
    }

  15. Finally, add the guiLeaveRoom, guiGroupSend, and guiSetGroupName functions. These functions handle various group chat-related events. For example, the guiGroupSend function sends messages to all the people in the chat room.
    Replace:

    //TODO10: Add the guiLeaveRoom, guiGroupSend, and guiSetGroupName functions

    With:

    private function guiLeaveRoom():void {
      var name:String = chatInputName.text;
      var room:XmppRoom = rooms[name];
      room.leave();

      // find room in roomList and remove it
      for (var i:int=0; i<roomList.length; i++) {
        if (roomList[i] === name) {
          roomList.removeItemAt(i);
          return;
        }
      }
    }

    private function guiGroupSend():void {
      var name:String = chatInputName.text
      var room:XmppRoom = rooms[name];
      if (room) {
        room.sendMessage(groupChatInput.text);
      }
    }

    private function guiSetGroupName():void {
      chatInputName.text = GroupListDisplay.selectedItem as String;
    }

Congratulations! You just finished coding your ActionScript XMPP application using Adobe Flex. Now let us run it!

Note: The file xmpp-completed.mxml, located in KAAZING_HOME/web/tutorials/xmpp-actionscript contains a complete version of the file for your convenience. Rename the file to xmpp.mxml before you compile it.

Compiling and Running the XMPP-Based Adobe Flex Application

Use the Adobe Flex 3 SDK from Adobe to compile the Adobe Flex client. The xmpp.mxml file that you have been changing is located in KAAZING_HOME/web/tutorials/xmpp-actionscript directory. In a command prompt, navigate to that directory and execute the following command to generate the xmpp.swf file.

ADOBE_FLEX_SDK_HOME/bin/mxmlc xmpp.mxml -library-path+=KAAZING_HOME/lib/client/flex

The library-path argument should point to the directory that contains the Kaazing Flex client library file kaazing-enterprise-gateway-client-release-version.swc. This SWC file can be found in the Kaazing Gateway demo bundle in the directory KAAZING_HOME/lib/client/flex.

You can now execute this SWF file from the flash player or use a Web page to invoke it. To test your new XMPP application, point your browser to http://[localhost:port]/tutorials/xmpp-actionscript/xmpp-flash.html, which embeds the generated xmpp.swf for testing purposes. For example, http://localhost:8000/tutorials/xmpp-actionscript/xmpp-flash.html. Your application should look like the finished application shown earlier.

Note: If you want to see two or more browsers communicate and you are running Kaazing Gateway on your local system, you can launch two or more browser instances. For example, Internet Explorer and Firefox.