IBM®
Skip to main content
    Country/region [select]      Terms of use
 
 
      
     Home      Products      Services & solutions      Support & downloads      My account     

developerWorks > Web architecture >
developerWorks
Sending rich messages between client and server using asynchronous messaging
e-mail it!
Contents:
Starting from where we left off
Message formats
The client side
Jedi JavaScript
The server side
Burying the complexity -- here's the simplicity
Remaining issues
Conclusion
Resources
About the author
Rate this article
Related content:
Remote scripting using a servlet
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
An easy API framework to apply to your Web applications

Erik Hatcher (erik@hatcher.net)
President, eHatcher Solutions, Inc.
01 May 2001

Erik's previous article, "Remote scripting using a servlet," described an infrastructure for asynchronous remote scripting between a browser and a servlet backend. This article expands on that premise by describing an asynchronous messaging system that allows rich messages to be sent back and forth between client and server. In order for these messages to be compliant with most browsers, they will be sent as JavaScript objects. This article builds on the code presented in the earlier piece, and creates an automated messaging layer so that the browser and server can keep in constant contact with one another.

Starting from where we left off
My previous article (see Resources) laid the foundation for putting a messaging architecture between server and browser using Java servlets and two different client-side techniques to invoke server-side methods. This foundation included an abstract base class for creating methods remotely available from most modern browsers. This was used to build a typical real-world example of a category/subcategory drop-down box scenario where it's necessary or desired to retrieve the values for a dependent drop-down box from the server based on the changing of a "parent" value.

The messaging architecture presented here differs from the previous article in two key ways: First, the client polls the server periodically to retrieve new messages, rather than a user action triggering the method call. Second, the format of the messages coming from the server must be strictly defined and easily parsed, whereas in the original framework the message formatting remained implementation-dependent.

Message formats
The basic idea is to gather a collection of messages from the server to the client, with each message encapsulating a name (that is, essentially, the message type), a text value, and, optionally, a collection of attributes (name/value pairs). XML is the ideal choice for such rich messaging, but unfortunately it's not possible to rely on browsers supporting XML for anything but the most constrained environments. Using IE5's XML data island feature, XML can be used as the messaging format. (Perhaps this would also work in Netscape 6 or other XML capable browsers? I'll leave that as an exercise for the reader.) But JavaScript also provides the capability to send equally rich messages, so this architecture allows for either format to be used interchangeably with no change to the application-specific code (except by specifying the desired format with rsmsg_useJavaScriptFormat).

The server-side code includes an RSMessage class encapsulating the desired name, text value, and optional attributes, as seen in Listing 1.

Listing 1: RSMessage (server-side Java class)

package com.ehatchersolutions.rs;
import java.util.Properties;
public class RSMessage {
 private String name = "";
 private String text = "";
 private Properties attributes = new Properties();
 public RSMessage(String n)
 {
   name = n;
 }
 public String getName ()
 {
   return name;
 }
 public void setText (String t)
 {
   text = t;
 }
 public String getText()
 {
   return text;
 }
 public void addAttribute (String name, String value)
 {
   attributes.setProperty(name, value);
 }
 public Properties getAttributes()
 {
   return attributes;
 }
}

The application-specific implementation of a remote scripting messaging method would return an array of RSMessage objects. This ordered collection of messages is then converted into a JavaScript or XML representation depending on the client desired format. Messages in XML look like this:


<Messages>
<Message1 >message 1 data</Message1>
<Message2 prop1="value" >msg2 data</Message2>
</Messages>

The same collection of messages in JavaScript looks like this:



var ma=new Array();var a=new Array();var m=new Message
('Message1','message 1 data',a);ma[ma.length]=m;var a=new
 Array();a['prop1']='value';var m=new Message('Message2','msg2
 data',a);ma[ma.length]=m;var msgs=new Messages(ma);msgs

The client side
Periodically polling the server asynchronously, so as to not disturb a user's Web browser experience, is implemented using JavaScript. Let's go under the hood of rsmsg.js and take a look at the method invoked every time the JavaScript timer fires. _getMessages method is implemented as shown in Listing 2. (Note: methods and variables prefixed with an underscore are internal to rsmsg.js and should not be accessed directly.)

Listing 2: getMessages (client-side JavaScript defined in rsmsg.js)

/**
 * Invokes the remote scripting method.
 */
function _getMessages(){ 
 var params = rsmsg_getParams();
 var method = _rsmsg_method + ((_rsmsg_usingJavaScript) ? "_JavaScript" : "_XML");
 if (_rsmsg_usingJSRS) {
   // JSRS
   jsrsExecute(_rsmsg_url, _processMessages, method, params);
 } else {
   // MSRS
   var e = "RSExecute(_rsmsg_url, method";
   if (params != null) {
     for (var i=0; i < params.length; i++) {
       e += ", '" + params[i] + "'";
     }
   }
   e += ", _processMessagesMSRS);";
   eval(e);
 }
 //reset timer
 _setTimer();
}

The _setTimer method (see rsmsg.js in the included code) sets a timer which triggers the execution of _getMessages at a developer-specified interval. The timer is reset at the end of _getMessages, triggering it to call _getMessages again.

As in the first article, the client-side implementation is designed to take advantage of either Microsoft's Remote Scripting (MSRS) capability or Brent Ashley's JavaScript remote scripting (JSRS) piece (see Resources for links to information on both). Also, rather than soley using JavaScript as the messaging format, it is implemented flexibly so that the client determines whether to use JavaScript or XML as the message format. The code in Listing 2 looks trickier than it really is (regarding the eval in the MSRS section), with the extra complexity allowing parameter handling to be dealt with generically and defined at an application-specific level; what it's really doing is simply invoking RSExecute (an MSRS defined method).

Most of the work of the client-side scripting is implemented in rsmsg.js. rsmsg.js exposes several "public" methods that control the messaging behavior. Only three of these methods are required to be called: rsmsg_setURL, rsmsg_setMethod, and rsmsg_start. The other parameters have default values and are not required to be called explicitly. Table 1 describes each method and provides an example of its use.

Table 1: Tasks and definitions of methods
Task / DefinitionExample
rsmsg_setURL [required]

Defines the URL to the servlet containing the remote method being invoked.

rsmsg_setURL("/servlets/com.ehatchersolutions.examples.rs.RSMessagingExample");
rsmsg_setMethod [required]

Defines the remote method to invoke.

rsmsg_setMethod("getMessages");
rsmsg_start [required]

Starts the message polling timer.

rsmsg_start();
rsmsg_stop

Stops the message polling timer.

rsmsg_stop();
rsmsg_useJavaScriptFormat (boolean)

Specifies that messages will be retrieved in JavaScript format if true. If false, XML format will be used (this requires that the rsxml XML data island exist and that the browser support XML in this fashion). Defaults to true.

rsmsg_useJavaScriptFormat(document.form1.format[0].checked);

Note: if using XML formatted messages, there must be an XML data island declared in the HTML <BODY> identified by 'rsxml' in this manner:
<xml id=rsxml></xml>

rsmsg_useJSRS (boolean)

Specifies that JSRS be used as the remote scripting method if true. If false, MSRS will be used. Defaults to true.

rsmsg_useJSRS(document.form1.clientType[1].checked);
rsmsg_setInterval (integer)

Specifies the interval (in seconds) between the remote method invocation to retrieve messages. Defaults to 10 seconds.

rsmsg_setInterval(parseInt(document.form1.interval.value));

Besides the required parameter settings shown above, two methods must be defined: rsmsg_processMessage and rsmsg_getParams.

Handling of each message is application-dependent and therefore requires customization. The framework developed in rsmsg.js allows messages to be gathered in a method- and format-independent manner and then handed off individually to rsmsg_processMessage when received. An example of the implementation of rsmsg_processMessage can be seen in Listing 3:

Listing 3: rsmsg_processMessage (example implementation)

function rsmsg_processMessage(msg) {
 switch (msg.nodeName) {
   case "System" :
     eval(msg.text);
     break;
   case "Message1":
     break;
   case "Message2":
     break;
   default : alert("Unknown Message: 
" + msg.xml);
 }
 log(msg);
}

Server-side methods to retrieve messages may or may not require parameters to be passed to them. In order for each remote invocation to get the dynamic parameters, the developer implementing this framework must also define rsmsg_getParams. rsmsg_getParams must return a JavaScript array of strings that will be arguments to the remote method (or null if the remote method takes no arguments). A nice side effect of this is that rsmsg_getParams is called prior to any other parameters being accessed for each remote method invocation, so parameters such as the polling interval can be modified. Here is an example of an implementation of rsmsg_getParams (see Listing 4):

Listing 4: rsmsg_getParams (example implementation)

function rsmsg_getParams()
{
 // This method is called prior to any other parameters being accessed, so all 
 // messaging parameters can be modified dynamically here to affect the next 
 // remote method invocation
 rsmsg_useJavaScriptFormat(document.form1.format[0].checked);
 rsmsg_useJSRS(document.form1.clientType[1].checked);
 rsmsg_setInterval(parseInt(document.form1.interval.value));
 // just for grins, lets send the server something different every time
 lastmsgid++;
 return new Array(lastmsgid.toString());
}

In the example above, both the message format and remote scripting method are modified. This would not be a typical situation as neither of these two parameters would ever change. Even changing the interval would not be a typical application of this architecture, but could be used to dynamically control how often messaging occurs, if necessary. Controlling the message format, remote scripting method, and interval merely showcase all the capabilities available and allow these parameters to be controlled dynamically from the example HTML page.

Jedi JavaScript
rsmsg.js contains some pretty nifty JavaScript tricks to handle JavaScript and XML messages transparently between one another. IE5's XML data island capability allows an XML string to simply be loaded into the browsers DOM. Listing 5 shows a _processMessages implementation:

Listing 5: _processMessages (client-side JavaScript, defined in rsmsg.js)

function _processMessages(str)
{
 var msgs = null;
 if (_rsmsg_usingJavaScript) {
   msgs = eval(str);
 } else {
   if (!rsxml.XMLDocument.loadXML(str)) {
     alert ("Data load failed");
     return;
   }
   msgs = rsxml.XMLDocument.documentElement.childNodes;
 }
 if (msgs == null) return;
 for (var msg=msgs.nextNode(); msg; msg=msgs.nextNode()) {
   rsmsg_processMessage(msg);
 }
}

If the messaging format is JavaScript, it's simply a matter of evaling the result to get a Messages object. If the message format is XML, the result is loaded into the rsxml data island (which parses the XML document into a document object model). The for loop at the end of _processMessages loops over the msgs collection, but this collection is either a set of XML element nodes, or a Messages object. JavaScript handles this beautifully because the Messages object (defined in rsmsg.js) implements the nextNode method (this would be similar to implementing the same interface on two different base-classed objects in Java). rsmsg_processMessage also gracefully handles this situation by utilizing properties available on both the XML element object and the Message object, both of which implement nodeName, text, getAttribute, attributes, and XML (yes, even when using the JavaScript format, the result is available for each message in XML format, if desired).

The server side
In my previous article, I developed the RemoteScriptingServlet abstract base class that handles using Java reflection to find the appropriate method, invoke it, and bundle up its return value in the proper fashion depending on the remote scripting method (JSRS or MSRS) being used. Here, that class is refactored slightly and re-used as a base class to a new abstract RSMessagingServlet class. The key modifications move the code to find and invoke the proper method into an overridable method called invoke. RSMessagingServlet overrides invoke to handle things a bit differently. Creating an application specific concrete class using RSMessagingServlet as the base class is simply a matter of subclassing and creating a public static method that returns an array of RSMessages and takes only String arguments as parameters. Any number of parameters can be specified for the method, but each is restricted to being a String object.

I faced an unclear path when it came to telling the client how to tell the server how it would like the data returned. I could have used a new GET parameter on the HTTP request made, but that would have required tweaking how MSRS and JSRS worked. I also could have made it a hidden "method parameter" that gets stripped prior to invoking the method. Instead, I opted for appending a suffix to the method name sent by the client. If the client does not specify a suffix, JavaScript is the default messaging format. Appending "_JavaScript" or "_XML" to the actual method name invoked tells the servlet the desired format. Appending of the suffix is handled behind the scenes in rsmsg.js. All a developer needs to do is set the actual method name using rsmsg_setMethod, and specify the desired format with rsmsg_useJavaScriptFormat. The overridden invoke method, after it has invoked the proper method, calls the RSMessagingServlet provided methods toXML or toJavaScript supplying the array of RSMessages to it. invoke is implemented as shown in Listing 6.

Burying the complexity -- here's the simplicity
Considering all of the above framework, it is now trivial to create a servlet that sends rich messages asynchronously from the server to the browser. While this example is prototypical, it could be extended to package messages received from queuing mechanisms or to report constantly changing information. Parameters to such a method would typically include things like the client ID (perhaps specified dynamically from session ID when the browser page is initially loaded), last message ID, or values from the state of the browser (form field values and the like). Listing 7 provides an example:

Listing 7: RSMessaging example (server-side example of using RSMessagingServlet)

public class RSMessagingExample extends RSMessagingServlet {
 public static RSMessage[] getMessages (String lastidstr) throws Exception
 {
   RSMessage[] msgArray = new RSMessage[2];
   RSMessage msg = new RSMessage("Message1");
   msg.setText("message 1 data");
   msg.addAttribute("lastid", lastidstr);
   msgArray[0] = msg;
   msg = new RSMessage("Message2");
   msg.setText("msg2 data");
   msg.addAttribute("prop1", "value");
   msgArray[1] = msg;
   return msgArray;
 }
}
Development Details
Resources

Remaining issues
Special characters in message name, attribute names, values, or message text will likely cause problems. There is a bit of meta-character escaping going on to take care of attribute value and message text character escaping, but currently no checks or escaping occur on message names or attribute names. Also, the issues presented in my previous article, particularly security and scalability, still apply to the architecture presented here. With every client polling the server frequently, the scalability issue is a big concern and must be addressed for production applications. Browser compatibility with the two remote scripting methods was also discussed in my previous article.

I debated whether adding a "last ID" parameter to the architecture was appropriate or not, and decided against building it in specifically. My past implementations of this remote scripting scheme had the client send the server the last message ID that it had received, and the server only returned messages after that message ID. I left this feature to be application dependent since IDing messages is going to be implementation specific. Adding message IDing is simply a matter of:

  • determining the scheme needed to tag messages uniquely,
  • adding an attribute with that value to each RSMessage returned,
  • defining the server-side method to accept the last ID as one of its parameters,
  • modifying the client to send its last received ID for each call to the method,
  • and having the client-side rsmsg_processMessage handling store the last ID for each message processed.

Alternatively, you could end the array of messages with a special "end of messages" message that specifies what the client should update its last value to.

Another feature that would be a nice addition is passing the HttpServletRequest object to the server-side method being invoked so that it could factor session or request information into which messages are returned.

Conclusion
Using the framework described in this article, enabling "static" Web pages to communicate with the server becomes very straightforward. The messaging layer is completely abstracted and buried behind a simple API. The uses of this mechanism could include stock tickers, continuously updated sports scores, or even highly dynamic server-controlled Web pages.

Resources

About the author
Erik Hatcher has been dot.bombed twice this year, and each time he has written technical articles to hone his skills. He currently seeks his next professional adventure, entertaining himself in the meantime with eHatcher Solutions, Inc. consulting gigs. Erik is Brainbench certified in both XML and JavaScript, and most recently consulted with Brainbench on the revamping of their XML exam. He can be reached at erik@hatcher.net.


e-mail it!
Rate this article

This content was helpful to me:

Strongly disagree (1)Disagree (2)Neutral (3)Agree (4)Strongly agree (5)

Comments?



developerWorks > Web architecture >
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact