Erik Hatcher (
[email protected])President, eHatcher Solutions, Inc.01 Feb 2001
The users of Web applications have suffered a dramatic shift in experiences from the world of desktop applications. Many Web applications do not at all mimic the usability, interactivity, and dynamic nature that is available in typical standalone or client-server desktop applications because of the constraints that HTML and HTTP impose. Here, Erik Hatcher explains how remote scripting can be used to enhance the interactivity and dynamic nature of a Web application experience.
One of the major drawbacks to Web applications is that the user experience is typically inferior to that of desktop applications. Most current Web applications lack in interactivity because once the browser receives a response to its URL request it is on its own, failing to communicate back to the server until a hyperlink is clicked or a form is submitted. Techniques such as using JavaScript and DHTML can be used to make the browser feel more like a desktop application; fancier techniques like using Flash, ActiveX, and Java applets can also accomplish this end.
But even with these newer techniques and technologies, the client is still mostly on its own after it receives the content from the Web server. The technique described in this article offers a solution that allows the browser and Web server to communicate behind the scenes. The browser can invoke remote Java servlet methods that enable the user experience to resemble that of a desktop application, such as populating a drop-down box dynamically based on the selection of a related drop-down box (that is, category/subcategory), or polling the server for messages and refreshing the display dynamically with continuously updated content.
Client sideThere are two popular ways of accomplishing remote method invocation from a Web browser: Microsoft remote scripting (MSRS) and Brent Ashley's JavaScript remote scripting (JSRS). The goal of both is the same: to invoke remote methods and return the results to the browser. Both methods were originally designed to communicate with remote methods defined in Microsoft's Active Server Pages. This article provides a way for both of these methods to communicate with a Java servlet on the server side. Let's get into the architecture details of each of these techniques.
Redmond scripting Microsoft's remote scripting is part of the Visual InterDev development environment. It consists of three pieces: an invisible Java applet, client-side JavaScript, and server-side JavaScript running in Active Server Pages. The Java applet handles the communications with the server. The client-side JavaScript communicates with the applet. The server-side JavaScript deals with taking the requested parameters and dispatching them to the specified server-side method. The communications are simply HTTP GET requests and responses, with the details of the method call sent as query parameters to the server-side script. There is much more to be said about Microsoft's remote scripting architecture, but that is beyond the scope of this article (see Resources).
Figure 1 shows the architecture of Microsoft remote scripting using a servlet on the server-side.
Steps involved in invoking a remote method using Microsoft remote scripting:
The browser executes a JavaScript call to RSExecute (this resides in an included JavaScript framework provided by Microsoft Visual InterDev). The remote scripting applet uses HTTP GET to access a special servlet URL on the server, complete with method name and parameters. The servlet returns its XML-like response and the applet receives it. The response is interpreted by the remote scripting JavaScript and returned to the calling code. A call object is returned, and the actual return value is the .return_value property if the method is returned successfully.
Figure 1. Microsoft Remote Scripting Architecture
JSRS ArchitectureBrent Ashley's JavaScript remote scripting accomplishes the same goal using a nifty DHTML trick of injecting a hidden <IFRAME> or <LAYER> (depending on the browser type) for each concurrent remote scripting call made. The hidden piece is navigated to the remote scripting URL using HTTP GET. The result returned from the server is HTML with an onLoad JavaScript call to the main window callback function.
Figure 2 shows the architecture of JavaScript remote scripting using a servlet on the server-side.
Steps involved in invoking a remote method using JavaScript remote scripting:
The browser executes a JavaScript call to jsrsExecute (this resides in the external jsrsClient.js file [see Resources to obtain JSRS]). Code in jsrsClient.js creates an <IFRAME> or <LAYER> (or re-uses an existing one) and navigates it to a URL with the appropriate parameters. The servlet returns its HTML response and the client receives it. The <BODY> onLoad of the returned HTML invokes the specified callback with the returned value.
Note: The example in this article shows Microsoft's remote scripting being used synchronously, but it could also be used asynchronously with a callback like the JSRS example. However, JSRS is not capable of making synchronous method invocations.
Figure 2. Brent Ashley's JavaScript remote scripting architecture
Let's see some codeThe servlet described below has been designed to support both MSRS and JSRS. This flexibility will be demonstrated by allowing the client to toggle between using either method. A single HTML page is created with both the MSRS pieces (JavaScript and applet) and the JSRS (a single external JavaScript) piece. Using the category/subcategory idea, the goal is to have a category selection which then determines which subcategory selections are available. Here is the HTML <BODY>:
Listing 1. Category selection
<BODY onLoad="javascript:categoryChanged()"> <FORM name="form1"> <TABLE> <TR> <TH>Remote Scripting Type:</TH> <TD> <input type="radio" name="clientType" value="MSRS">MSRS<br /> <input type="radio" name="clientType" value="JSRS" CHECKED>JSRS </TD> </TR> <TR> <TH>Category:</TH> <TD> <SELECT name="category" onChange="javascript:categoryChanged()"> <OPTION value="0" SELECTED>Category 0</OPTION> <OPTION value="1">Category 1</OPTION> <OPTION value="2">Category 2</OPTION> <OPTION value="3">Error Test</OPTION> </SELECT> </TD> </TR> <TR> <TH>Subcategory:</TH> <TD> <SELECT name="subcategory"> <!-- Need a placeholder until it can get loaded --> <OPTION value="-1" SELECTED>---------------------</OPTION> </SELECT> </TD> </TR> </TABLE> </FORM> </BODY>
Everything about the HTML is straightforward. Note that categoryChanged is called when the document is loaded and when the category field is changed by the user. The categoryChanged method is defined as:
Listing 2. categoryChanged method
function categoryChanged() { if (document.form1.clientType[0].checked) { // MSRS var co = RSExecute("/servlet/RSExample", "getSubcategories", document.form1.category.options[document.form1.category.selectedIndex].value); if (co.status != 0) { return; } var subcatstr = co.return_value; populateDropDown(subcatstr); } else { // JSRS jsrsExecute("/servlet/RSExample", populateDropDown, "getSubcategories", document.form1.category.options[document.form1.category.selectedIndex].value); } }
Remote scripting now comes into play by calling the remote method getSubcategories. Refer to the Microsoft remote scripting documentation for details of the remote scripting "call object" returned from RSExecute (see Resources). The call to jsrsExecute specifies that populateDropDown will be called when the asynchronous call completes. Refer to Resources for the details of populateDropDown.
On the server side, the servlet is defined as in Listing 3.
RSExample servlet has a static public method named, not coincidentally, getSubcategories. This implementation is, of course, simply a proof-of-concept, but it could easily be extended to do a database lookup of the subcategories relating to a category. Note that the getSubcategories function can only deal with a catid of 0, 1, or 2. The sample code includes a demonstration of invoking this method with an invalid value of 3 ("Error Test" in the category drop-down). Using remote scripting is useful when the category/subcategory combinations are too numerous to send to the browser as JavaScript objects. Any number of methods could be defined in this servlet and called similarly from the client.
The subcategories are returned to the client in the format "index,value;index,value . . . ." Ideally, this kind of information should be passed using XML, but in order to make this scheme be as cross-browser friendly as possible, XML is not the right choice. If the application environment is confined to Internet Explorer 5 browsers, then XML would be a very elegant way to pass information with remote scripting.
In order to make the servlet architecture as extensible as possible, an abstract class that extends HttpServlet was written to dispatch the method invocation generically and package the return value in a way the remote scripting client-side code is built to handle. The HTTP GET made by MSRS and JSRS looks like the code in Listing 4.
Because the URLs are unique enough to allow the servlet to distinguish between the two different client-side techniques, a single abstract servlet class was created that dynamically handles both methods. To show a bit of the details of how the two client-side remote scripting methods work internally; Listing 5 shows the format that the Microsoft client-side remote scripting piece is expecting as the response to that request.
At first glance it would appear that XML is being used behind the scenes, but note that the value of the TYPE attribute of the RETURN_VALUE element is not double-quoted and therefore does not follow the XML specification. We'll just shake our heads at that apparent oversight and move on. The VERSION attribute of the METHOD element is ignored by the client-side code, but it was left in just to keep things consistent with the return values from Microsoft's server-side implementation in ASP. The value of the RETURN_VALUE element is URL-encoded. This value is unescaped in the client-side framework automatically (note spaces replaced by "%20" rather than "+," as that is how the JavaScript unescape function requires). The client-side JavaScript just uses text functions to find the necessary information in the response and builds the "call object" accordingly, which is then returned from RSExecute to the calling code.
The servlet similarly builds the response to JSRS calls in the format that it expects. The return value is shown in Listing 6.
The heart of the method invocation occurs in the doGet method of RemoteScriptingServlet (a subclass of HttpServlet). Here is the relevant part of doGet:
Listing 7. doGet
String method; int pcount = 0; callbackName = request.getParameter("C"); if (callbackName != null) { // client is JSRS - it passes a "C" parameter clientType = JSRS; method = request.getParameter("F"); // JSRS doesn't tell us how many parameters, so count them while (request.getParameter("P" + pcount) != null) pcount++; } else { clientType = MSRS; method = request.getParameter("_method"); pcount = Integer.parseInt(request.getParameter("pcount")); } // ... some code omitted, refer to the full code included // find and invoke the appropriate static method in the concrete class Class c = this.getClass(); Method m = c.getMethod(method, paramSpec); returnValue = (String) m.invoke(null, params);
Refer to the included code for the full RemoteScriptingServlet class (see Listing 8). First the client type is determined, keyed off the "C" parameter that is present for JSRS, but not for MSRS. Then the idea is to get a reference to the Class of the servlet being invoked (RSExample, in this case), get a reference to the Method being invoked (getSubcategories) based on the number of parameters, and then invoke that method with the appropriate parameters. The parameters to the dispatched methods must match in number with the call from the client, and must all be of type String (internally to that method, the Strings can be converted to other types if necessary). Dispatching to static methods was chosen arbitrarily, but it could easily dispatch to instance methods rather than class methods by specifying "this" as the first parameter to invoke. The entire code above is wrapped inside a try/catch block, and any exception thrown will be gracefully sent back to the client. MSRS errors are returned by setting the TYPE attribute of RETURN_VALUE to ERROR, and specifying the escaped error text as the value of RETURN_VALUE. JSRS errors are returned as HTML that executes jsrsError in the onLoad.
Now what?There are all kinds of interesting things that can be accomplished using remote scripting. Taking advantage of the asynchronous feature of remote scripting and JavaScript window timers, a messaging system could be built to allow a browser to poll the server for messages, content, or other types of update information.
IssuesBecause of the technologies used in remote scripting, there are limitations on the number of browsers it will work with. To use MSRS, JavaScript must communicate with the hidden applet to perform the remote call. The Java Virtual Machine (JVM) is only"scriptable" (such that JavaScript and Java applets can communicate) on a limited number of browsers, meaning only Netscape and Internet Explorer on Win32 (and possibly on other platforms) can support MSRS. Because all parameters are encoded in the URL for an HTTP GET request, there is a limitation to the size of the parameters. A modified applet exists that does HTTP POST rather than GET to eliminate this limitation. Brent Ashley's site has the POST version of the applet available for download (see Resources.
Scalability issues arise if remote scripting is used for continuous polling. Depending on an application's needs, the frequency of requests can be reduced or even made dynamic such that it polls less frequently if it receives fewer messages and increases the frequency when larger sets of messages are received. Since HTTP GET is used for the method call, the size of the data sent to the method is restricted. The response to a remote method has no explicit size restrictions on size; however, if large amounts of data per request are being passed to or from the server, this messaging architecture is not appropriate.
The security of the remote scripting methods should be considered. By simply opening the appropriate URL along with its necessary parameters in a Web browser, the remote method is being called and its results are being sent back. It may be necessary to assure that the user is logged into the application before returning any results. There are security restrictions that do not allow a browser to use cross-host scripting, so that the host for the main HTML page must be the same as the host for the remote scripting servlet.
Firewalls are not an issue for communications because HTTP is used, although in some environments Java applets are blocked by firewalls that would prevent Microsoft's remote scripting from working. HTTPS will also work fine with both client-side methods.
ConclusionRemote scripting is a great technique for creating a more desktop-like feel for Web browser-based applications. The RemoteScriptingServlet base class provided with this article opens the door for server-side Java applications to use tricks previously only available to Active Server Page applications.
Resources
Read Microsoft's documentation on remote scripting.Download Brent Ashley's JSRS.Get the details of populateDropDownDownload with this example code (including the RemoteScriptingServlet class).Learn the beauty of Java's reflection API.Apache's Tomcat was used to test this servlet.
About the authorErik is the President of eHatcher Solutions, Inc. He also recently joined PromoFuel as their High Octane Site Architect. Being actively involved in software developer organizations in Tucson, AZ, he has presented on Windows NT security analysis at the Back Office Administrators Conference and on XML for the Tucson Developer Series. You can reach him at
[email protected].