This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
|
Remote Scripting
cripts let you direct a user's interaction with an HTML page by listening for the events raised by the page elements and handling them as needed. ASP notwithstanding, scripts always are executed on the client side, but real-world projects often require data from a server. This can create some user interface awkwardness.
In a typical scenario, a customer requests information by entering a user name and password, then presses a Submit button and waits for the server to analyze the request and respond. The server responds by sending back a page with the requested informationfor example, all the orders that the customer made in the past month. The server doesn't update the user's current page; it sends an entirely new one.
If this were a desktop application, the process would be simpler and smoother. The server would receive the request and then update the current window with the requested data. Until now, this couldn't be done on the Web. Hidden HTML fields, cookies, and other tricks let you keep the next page as similar as possible to the previous one, but you still had to load a new page.
A new technology called Remote Scripting (RS) changes this by letting you issue remote calls to a server object as if it were a standard client-side scripting object. The current page remains in place even when the connection has finished. The requested information is available as the return value of the method you've used. Thus you can update the page via the usual Dynamic HTML (DHTML) model. This schema is outlined in Figure 1.
|
Figure 1: Remote Scripting Schema |
In this month's column, I'll focus on the various aspects of RS and the Microsoft Scripting Library (MSL). This library is made up of the script functions that comprise the RS technology, along with some other interesting features I'll discuss later on.
At the time of this writing, both MSL and RS are in beta testing and some of the details discussed here might change slightly in the future. A beta version of the library and some sample code are available at http://www.microsoft.com/scripting. The library consists of three files: rs.asp (the server-side implementation), rs.htm (the client-side implementation), and rsproxy.class (the scripting proxy).
The Microsoft Scripting Library
RS is an important part of the larger MSL project. This library of scripting functions is meant to enhance your power when developing Web applications. Like any other library, it provides useful functions that are built on top of existing technologies and languages. It only requires a browser that supports Java applets and JavaScript. The ultimate goal of both RS and MSL is to improve Web applications so that they work the same way as Microsoft® Visual Basic® or Visual C++®-based client-server applications.
To meet this goal, developers must be able to issue remote calls directly from the client. This is what RS provides. Another interesting feature provided by the library is unevaluation functionality, which takes an object and flattens it out into a string that can be transmitted across the Internet. Eventually, MSL will provide other features such as closures and ADO recordset-to-array conversion functions.
Remote Scripting Architecture
Now let's explore the ideas behind RS, and what you can expect to do with it once it's released. There are some similarities between RS and Remote Procedure Calls (RPC) and even greater similarities between RS and Java Remote Method Invocation (RMI), which is a kind of Java-to-Java object communication. In both cases, developers can issue calls to a remote server, have it execute some code, and pass back the results.
Whether the server is local or remote should be transparent to the client objects that issue the call. For RS this is not completely true right now, but it should work transparently when the final version of the library is released. Currently, when scripting you need to follow different coding conventions if you call a remote object, and you must use specific functions, too. Apart from this, the RS architecture is similar to others you may know, and it's based on a proxy module that sets up the conversation between the Microsoft Internet Explorer 4.0 client page and the server. Figure 2 shows how the MSL components fit into this architecture.
|
Figure 2: MSL Architecture |
Let's use a sample program to see how the components interact. Suppose you have a local HTML page called mind.htm with code like that shown in Figure 3. Suppose for now that you also know how to link to MSL and include its functionality in your own pages.
You want a remote method to execute when the user clicks on the button. First, you specify the URL. It may be an ASP document that defines a JavaScript object. Through an RS function called RSExecute (more on this later), you start the process that will result in some processing on the server:
function DoSomething() {
var serverURL = "http://dino/rsmind/mind.asp";
var co = RSExecute( serverURL, "Method1" );
}
RSExecute is not directly part of your page, but it is defined in the rs.htm file (the client-side portion of MSL). The function allows you to run the method called Method1 on an object defined at the specified URL. RSExecute is one of the public functions exposed by rs.htm. That is, it's just a wrapper around an internal object that does the job. This engine interacts with an invisible Java applet that is silently inserted into your page after loading. The code for the applet must reside in a specific directory on the server. However, this is a detail that may change in the final release.
The applet works as a broker, receiving all the requests and starting a new thread of execution for each one. The thread connects to the URL, gets the data, and returns it. The URL used by the applet is a bit different from the one you passed to RSExecute. Added to it are the name of the method and the argument list, if any. Using the previous example, the URL becomes:
http://dino/rsmind/mind.asp?method=Method1¶meter_count=0
At runtime, the server page that contains the object you want to access (in this case, mind.asp) includes the code
for the server-side portion of the library (which is contained in the file rs.asp). The particular structure you must give to the server page causes the execution of a dispatch function defined in rs.asp. This function reads the method's name through the ASP Request object and then invokes it. Finally, the result is marshaled back to the client through Response.write.
Enabling MSL in Your Pages
MSL is currently available as source code that you must include somewhere in your client and server pages. In addition to this, you must explicitly enable the RS engine through one of the library functions. Thus, any HTML page that needs to issue remote calls through RS must have a mandatory header. As shown in Figure 3, you import the file rs.htm (typically from a local path), and call the function RSEnableRemoteScripting to initialize the engine.
<body>
<script language=Javascript src="rs.htm"></script>
<script language=Javascript>
RSEnableRemoteScripting();
</script>
Note the special use of the src attribute, which allows you to take script code from a file name. Note also that despite the .htm extension, rs.htm (or any file you want to import this way) isn't a real HTML file. It should consist of source code only without any tags, as if you were inserting script code directly. In fact, the entire content of rs.htm goes between the preexisting <SCRIPT> tag block.
Once you import the client-side module of the RS engine, you can start calling its functions. The first one you need is the root function, since it lets all the others work properly.
As mentioned earlier, a hidden Java applet plays a central role in the RS architecture. You don't need to insert it manually since this is the kind of thing that RSEnableRemoteScripting (which I'll discuss later) can do for you. If you study the source code of this function (which is found in rs.htm), you'll note that it prevents adding similar text to the current document:
<APPLET name=RSAspProxyApplet
codebase="http://dino/RSMind"
code=RSProxy.class height=1 width=1>
</APPLET>
|
Figure 4 shows the actual body of the
page specified in Figure 3 once RS has been
initialized.
|
|
Figure 4: Remote Scripting Page
|
Synchronous and Asynchronous Calls
Basically, there are three functions for dealing with remote scripts: RSEnableRemoteScripting, RSExecute, and RSGetASPObject.RSEnableRemoteScripting handles synchronous and asynchronous execution of a method on a remote object, and RSGetASPObject creates a local copy of the server object you want to access. I'll have more to say on this later.
First, I'll discuss the behavior and prototype of RSExecute. Based on the previous example, RSExecute would seem to have a simple syntax. Actually, the declaration of RSExecute is as follows:
|
function RSExecute( url, method, p1, ..., pn, cb, ecb, context )
|
The url and method parameters are exactly what you saw in the examplethat is, the remote page to access and the method to call. The parameters p1 through pn are the method's required list of arguments. The parameters cb, ecb, and context are optional and denote two callback functions and a context string, respectively. The callback functions are called at the end of processing in case of errors.
Let's look at an example:
|
var serverURL = "http://dino/rsmind/mind.asp";
var r = RSExecute( serverURL, "Method1" );
processResult( r );
|
Here I'm asking the engine to issue a call synchronously without arguments. RSExecute returns only when the server is finished. In the meantime, client-side execution stops. Compare this second example:
|
var serverURL = "http://dino/rsmind/mind.asp";
var r = RSExecute( serverURL, "Method1", processResult );
alert( "Execution started!" );
|
If I pass a function name on the stack, then the RS engine interprets it as a request for asynchronous execution. Consequently, the message box shows up immediately and the rest of the page's code executes. When the server finishes processing the request, the callback is invoked automatically.
I haven't said anything yet about the return value of this function. RSExecute always returns something called a Call Object with the properties listed in Figure 5. Status indicates how the whole operation ended, while message is a descriptive string that depends upon the results.
Data and return_value are what the server actually returns. These can come back in two forms: the method invoked can return raw data such as strings or numbers, or the method can create objects or arrays. In the first case, there's no need to package anything. Data is sent across the net "as is" and made available to the client page through the data property. If the method needs to return a more complex object, it can be serialized as a string and then sent. In this case, it is available in return_value and must be rebuilt to become useful. The rebuild process is called "evaluation," and takes place through JavaScript's eval function.
Given this, you can't directly use what RSExecute returns, but instead must resort to code like this:
|
var r = RSExecute( serverURL, "Method1" );
if( r.status != -1 ) {
d = eval(r.return_value);
alert( d.getYear() );
}
|
You first check the status of the operation against errors, then handle the returned data. As is explained in Figure 5, if you know that the method returns an object, say a date, then you need to evaluate the returned string as an object before calling its own methods. This is not necessary when handling raw data.
For security reasons, the preliminary version of MSL doesn't let you send objects to the server through RSExecute. Instead, the opposite can occur seamlessly. At the time of this writing, Microsoft plans to remove this limitation when the final version of MSL is available.
A Few Words About Callbacks
All the callback functions you might use for processing the return data or errors have the same prototype:
|
The only argument is the Call Object I described in Figure 5. The RSExecute function has a variable-length command line that can even accommodate the callback functions. Only two of the parameters are necessary: the URL and the method name. All the rest, whether they're method parameters, callbacks, or simple context strings, are packed into an array and passed to the private functions defined in rs.htm as shown in Figure 2.
As you can see from the following source code, RSExecute packs all the optional arguments in a flexible structure and calls an internal object named MSRS. (I'll clarify the role of this object later.)
|
function RSExecute(url, method) {
var cb, ecb, context;
var params = new Array;
var pn = 0;
var len = RSExecute.arguments.length;
for (var i=2; i < len; i++)
params[pn++] = RSExecute.arguments[i];
return MSRS.processRequest(url,method,params);
}
|
When invoking RSExecute with callbacks, it's important that you follow the correct order for optional arguments: method parameters, callbacks, error callbacks, and the context string.
To force an asynchronous call, it's sufficient to specify a callback function. The MSRS object works as a kind of proxy between the client page and the RS proxy applet. It stores all the information contained in the RSExecute call, and recognizes the arguments passed by their type by using the built-in JavaScript function typeof. MSRS then verifies that the RS proxy applet is up and running and starts a request. The communication is always synchronous between MSRS and the applet. Finally, when the server marshals back the data, MSRS passes the returned Call Object to the specified callback, if any, or directly to the caller page.
Remote Object Instantiation
There's another RS function we need to consider: RSGetASPObject. This function is useful when you need to repeatedly access the same remote object because it lets you get a local proxy for the object. Instead of continously accessing it through RSExecute, you can access it through a more convenient object-oriented syntax. RSGetASPObject gives you a new object that has the same public interface as the remote server object, and works by passing to the remote object all the calls you issue. The calls are still sent across the Internet, but the code is easier to write. Here's an example of how you'd use it:
|
var o = RSGetASPObject( serverURL );
var r = o.Method1();
if( r.status != -1 ) {
d = eval(r.return_value);
alert( d.getYear() );
}
|
Figure 6 shows a portion of the source code for RSGetASPObject. Let's look at the differences between its implementation and that of RSExecute. First, the function calls a GetServerProxy function through MSRS.startRequest, an RS client internal method that is called inside MSRS.processRequest. You could say it's a more direct way of invoking a remote method.
GetServerProxy is defined in rs.aspthat is, in the server-side RS engine. Ignoring for the moment how it happens (I'll discuss this in the upcoming section), GetServerProxy receives input in the public vtable of the server object to which you want a proxy. (Recall that you referenced this object via the URL.) Then GetServerProxy creates a new object and adds to it as many members as are in the server object. This number is returned to RSGetASPObject as the variable request.return_value. The object exposes all the public members of the base server plus two attributes such as location and exec. (Location is a string, while exec is a function.) All the inherited methods are implemented as:
|
return exec( location, methodname, params );
|
Figure 6 shows that exec and location actually map to MSRS.processRequest and the URL passed to RSGetASPObject. Thus the o.Method1 shown previously ends up executing:
|
MSRS.processRequest( serverURL, "Method1", params )
|
This is the same code you run through RSExecute. Compare this with the source of RSExecute shown earlier.
Coding Conventions
So far I've mainly discussed what happens on the client side. There are a number of issues that need to be described on the server side. For example, what should be the structure of the server object you refer to from the client page? This page must have a predefined layout. You must save it as an ASP page and place at the top a call to a dispatcher function RSDispatch, defined in rs.asp. Figure 7 shows a typical RS server. This file is the server-side counterpart of the mind.htm script I detailed in Figure 3. Note the invocation of RSDispatch at the beginning of the file and the embedding of rs.asp.
RSDispatch plays a fundamental role in this archictecture as a sort of server-side stub that receives calls and arguments, and dispatches them to the right server method. In light of this, it's now time to update the diagram shown in Figure 2 so it more precisely illustrates how the various pieces of the library work together (see Figure 8).
|
|
Figure 8: RSDispatch in MSL Architecture
|
While the RS proxy applet that governs the transaction always references the URL specified in your client call (in the example discussed previously it is http://dino/rsmind/mind.asp), any call to it actually lands in the RSDispatch function in rs.asp. This function dispatches the method and arguments to the proper server function, and takes care of marshaling the return value back to the caller. Its code is split into two parts, one of which executes if the method to be called is GetServerProxy (discussed earlier). You can obtain the name of the method or the arguments needed through the ASP Request object.
|
methodname = Request.QueryString("method");
param_count = Request.QueryString("parameter_count");
|
Figure 9 shows a demo page that simulates the behavior of RSDispatch. Once it knows which method to call, it formats a string and executes it through JavaScript's built-in function eval.
The actual server objects must look like client-side scriptlets. However, they should not be confused with server scriptlets, a new technology I'll cover here in the near future. If you want to know more about scriptlets, refer to the Cutting Edge column in the January 1998 issue of MIND. For even more in-depth coverage, see my book titled Instant Scriptlets Programmer's Reference (WROX Press, 1998).
RS requires you to have an object like a scriptlet on the server. This page exposes methods through the public_
description object, just like a client-side scriptlet. This means that all the functionality available on the server must be expressed in terms of an object and a set of methods:
|
function ServerSideScriptlet()
{
this.Method1 = DoMethod1;
this.Method2 = DoMethod2;
}
public_description = new ServerSideScriptlet();
|
In this example, the server is coded in the mind.asp file and exports two methods called Method1 and Method2.
In summary, to take advantage of RS at the present time you need to follow different coding conventions, depending on whether the object is local or remote. You have to either call RSExecute to ask the server to execute a method or get a local proxy for the server object, which lets you use a different and simpler syntax. The value the function returns is not the real return value of the method. Instead, it's an intermediate object that encompasses the whole transaction, including the return value. Furthermore, the source code for the client page must include and initialize the MSL library. On the server side, you must expose the services in an ASP page using the scriptlet interface through a public_description object. Also, this page must include the server-side portion of MSL, and must invoke it.
Object Unevaluation
RS is the key technology in MSL. However, MSL provides at least one other important feature: the ability to serialize or "unevaluate" the objects. Both JavaScript and the DHTML object model let you run code dynamically. That is, you can call a function and pass it a string denoting an executable expression. In JavaScript you can do this via the eval function.
MSL introduces a function called uneval that does the exact opposite. This function takes an object as input and flattens it out to a string that can be transmitted across
the Internet easily. When you receive such a string, you can recreate the object by simply calling eval with the string.
|
|
Figure 10: Uneval and Eval Page
|
Figure 10 shows a page that demonstrates the combined use of uneval and eval. The page, which imports a stripped-down version of MSL that includes only the uneval source code, creates a public_description object and then unevaluates it to the string that Figure 10 displays in the foreground. The same string is then used to clone a new identical object via eval. This is the same kind of machinery that lies behind RS. The source code for the page is shown in Figure 11.
Summary
Remote Scripting and the Microsoft Scripting Library represent important steps toward more powerful and flexible Web applications. Up until now, Web apps have been fun to develop, but frustrating for users with slow connections. First Dynamic HTML, then scriptlets, and now MSL and RS promise to blur the distinction between Web and desktop client-server applications. Release versions of this technology will be available soon, so don't forget to check the scripting area on the Microsoft Web site for the latest news.
|
From the April 1998 issue of Microsoft Interactive Developer.