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.
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> |
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 |
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 / Definition | Example |
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_stopStops 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 |
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.
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).
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
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
RSMessagereturned, - 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_processMessagehandling 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.
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.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code for this article | wa-rich-rsmsg.zip | 119 KB | HTTP |
Information about download methods
-
This article builds on the architectural foundation of my previous article, Remote
scripting using a servlet.
-
JSRS is available from Brent Ashley's
Remote Scripting page.
-
The complete code used in this article is available in this zip file.
-
The example remote scripting application is demonstrated online here.
-
The wonderful software available from Apache
made this article a joy to write (Apache web server, Tomcat, and Ant
specifically).
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.
Comments (Undergoing maintenance)





