Chapter 6

LiveWire and Server-Side JavaScript


CONTENTS


Starting with Netscape Navigator 2.0, Web users have been able to run JavaScript programs on their desktop machines. Sometimes, however, client-side JavaScript is not enough. The Web developer may want to provide services for users of all browsers-not just Navigator. Or, the application may need to access a database or server file, or reach out over the Internet. Any of these requirements may induce a Web developer to look to server-side JavaScript.

Server-side JavaScript is a feature of LiveWire. Using LiveWire, the developer can write a server-side script (a JavaScript program that runs on the server) and have it compiled before starting the application. Then, when the visitor connects to the application pages, the compiled versions of the scripts run and send HTML to the client.

The Dynamics of the HTTP Request

Anyone who has spent more than a few minutes on the Web has wondered about the ubiquitous http that appears at the beginning of each URL. The Hypertext Transfer Protocol (HTTP) is the protocol of the Web. By making it a part of each URL, the designers left room for other protocols to be added in the future.

Standard (non-LiveWire) HTTP is a five-step process:

  1. The client establishes a TCP connection with the server.
  2. The client sends a request to a well-known port on the server (typically port 80).
  3. The server looks up the requested entity on the server (which may include running a CGI script).
  4. The server sends back a header with status codes, followed by the requested entity.
  5. After sending the requested entity, client and server disconnect.

You can experiment with this protocol by using Telnet to substitute for the browser. Establish a Telnet session to port 80 of your favorite server. Type the request line:

GET pathToEntity HTTP/1.0

Now press the ENTER key twice.

Note
The latest version of HTTP is 1.1, and there are some important new features in the new version. For our purposes in this chapter, however, both HTTP/1.0 and HTTP/1.1 work about the same way.

For example, if you want the welcome page from the DSE Web server, you can establish a Telnet session (on a UNIX machine) by typing:

telnet www.dse.com 80

Once connected, you can request the default document with

GET / HTTP/1.0

The server responds with several lines of header, followed by the requested file. After that, the TCP connection is torn down.

The section titled "Interactive Pages in Standard HTML" in Chapter 3, "Standard HTML," describes HTTP in more detail. The remainder of this section describes how this process proceeds when the server is a Netscape server that is running LiveWire.

Receiving the Request

When the Webmaster installs an application by using the Application Manager (described in Chapter 2, "Developing a Netscape ONE Application"), he or she specifies the URL at which the application is served. The Application Manager writes a line into the livewire.conf file similar to this line:

world uri=/world object=D:/Netscape/Server/LiveWire/Samples/world/

hello.web home=hello.html client-mode=client-cookie maxDBConnects=0

The first two fields of this line say that the application named World should be served when a client requests http://www.ourserver.com/world.

Once the server has the request, it passes the request through its usual security checks before checking to see whether the URL may be a pointer to a LiveWire application.

Checking with LiveWire

When a client requests an URL from a Netscape Web server, the server checks the contents of the livewire.conf file to see whether the URL is on the list of LiveWire applications. If it is, control is turned over to LiveWire.

Caution
If you have a directory in your document root directory named nep and you also install a LiveWire application named nep, the application takes precedence, and the directory becomes unreachable.

Starting LiveWire

To see what LiveWire does when it takes control, launch the Application Manager (or, equivalently, connect to your Netscape server at http://www.yourserver.com/appmgr/). Select the sample application named World from the menu in the left pane. Your screen should look like Figure 6.1.

Figure 6.1: Launch the application Manager and select the World application.

Now choose Debug in the left pane. (It's below the row of links that says Start, Stop, and Restart. You may have to scroll down to see Debug.) The left pane holds the debug information-the right pane shows the application's initial page. The debugger says in the left pane that the application is now Waiting for first request..., but the debugger does not yet show the application's activity.

The Netscape Web server, like all of the new servers described in Chapter 17, "SuiteSpot and Orion Technology," has a layer called the Java Virtual Machine (see Figure 6.2), which can understand either Java or JavaScript. On Web servers, that layer supports the LiveWire Object Framework-four prebuilt objects that offer an interface with the server, the application, the client, and the request.

Figure 6.2: Each Netscape server--not just the Web servers--includes Java and JavaScript support.

Runtime Interpreter

The LiveWire compiler converts server-side JavaScript into a file of bytecodes that are read by the server's Java Virtual Machine (JVM) at runtime. To understand the JVM, recall that conventional processors accept binary instructions in a machine language, which is specific to each processor family. That is, the machine language for an Intel 80X86 processor in a PC is different from the machine language for an IBM/Motorola PowerPC processor in a Macintosh. The JVM is a software implementation of a "virtual processor." Java (and, JavaScript) is platform independent because compilers can generate Java "machine language" that runs on the JVM. Netscape has built a version of the JVM for each major platform.

Note
In addition to writing JavaScript that can be compiled for the JVM, Sun provides documentation that enables developers to take further advantage of the JVM built into each server. You can extend the JVM. You can port it to new processors. You can write your own compiler to output bytecodes for the JVM. Anything you write that is targeted for the JVM is highly portable because the JVM is available on so many machines, and can easily be ported to new processors and architectures.

ON THE WEB
http://java.sun.com:80/doc/language_vm_specification.html   Sun's documentation on the JVM is available at this site. The Java Virtual Machine is also described in detail in the book, Java Virtual Machine by Troy Downing and Jon Meyer (O'Reilly, 1996).

Running the Application

The application has already instantiated three of the four default objects: server, application, and client. Enter your name in the field provided and press Enter to see LiveWire in action. LiveWire fills in your response as a property of its request object. The resulting screen should look like Figure 6.3.

Figure 6.3: The debugger frame on the left shows the internal processing of LiveWire.

Scroll through the debugging frame. Here are some of the things you see LiveWire doing:

Note
Although only a part of the HTML file is server-side JavaScript, all of the file is converted into JavaScript bytecodes. All of the HTML file is generated dynamically, not just the portion built from JavaScript.

LiveWire and the .web File

If you use the View, Document Source menu to examine the page of a LiveWire application, the source can appear rather confusing. None of the server-side JavaScript comes down to the client-only its output does. For example, the source of the hangman application shows the same page-hangman.html-being repeatedly loaded. Each time it is loaded, it changes slightly, based on the users answers.

When the page is served, LiveWire runs the bytecodes from the application's Web page on the server. Only the output is sent to the client.

Tip
LiveWire reads the Web file only when the application is started. If you change the HTML file, the application is not updated. If you recompile, the application is not updated. You must use the Application Manager to restart the application in order to force LiveWire to reread the Web file.

Attaching the Built-In Objects

Before it runs the Web page, LiveWire loads the user's environment with the built-in objects such as request, client, and server.

Notice from the debug pane, examined earlier, that these objects are preloaded with information that is based on the HTTP request and the server configuration files.

The LiveWire Interpreter

After the JavaScript context is loaded with the prebuilt objects and the bytecodes from the application's Web file, the <SERVER> code is run by the JavaScript Runtime Interpreter. The application may read or write files, access the database, or make calls around the Internet. Anything that is passed through write() is sent back to the client browser. Note that the output may include client-side JavaScript-the browser does not begin to interpret the JavaScript in the document until the server-side JavaScript has finished running.

HTML with JavaScript

This section shows how to write server-side JavaScript into a page. Note that, unlike client-side JavaScript, server-side JavaScript must be compiled and the application must be installed before it can be used.

<server>-delimited Example

For most large chunks of server-side JavaScript, you want to wrap the code in <SERVER>...</SERVER> tags. This section shows how to set up JavaScript code that uses this notation.

The Syntax  The proper syntax for server-side JavaScript with the <SERVER> tags is

<SERVER>
Java statements, including one or more write statements
</SERVER>

JavaScript Statements  Listing 6.1 shows a simple example of server-side JavaScript:


Listing 6.1  -Simple Example of Server-Side JavaScript

<HTML>
<HEAD>
<TITLE>Simple Example</TITLE>
</HEAD>
<BODY>
<H1>Hello, World!</H1>
<P>
Your IP address is 
<SERVER>
write(request.ip)
</SERVER>
</BODY>
</HTML>

Up until the first <SERVER> tag, this file appears to be a typical HTML static file. Inside the <SERVER> tag pair, however, you see something new-a call to a write method. In this case, the write writes the ip property of the built-in request object.

As you saw in Chapter 5, "Client-Side JavaScript," JavaScript is dynamically typed. The runtime environment easily converts between numbers and text, so the IP address is written in its conventional dotted-decimal form.

Note
Without a call to write() somewhere in the JavaScript, nothing gets written back to the client. Only rarely will you write server-side JavaScript for its side effects alone. If your server-side JavaScript does not include at least one write(), think it through carefully to be sure you haven't missed something.

Tip
Some of the state maintenance mechanisms (for example, client-cookies) have to send information back to the client in the headers. It is good practice, therefore, to set all client properties before doing any calls to write.

The request Object

If you've done any CGI programming, you are quite familiar with the difference between GET and POST methods. Those methods are less visible in server-side JavaScript because all of the form's fields become properties of the request object. Thus, you can write code like that shown in Listings 6.2 and 6.3.


Listing 6.2  -A Request Object Demo

<HTML>
<HEAD>
<TITLE>A Request Object Demo</TITLE>
</HEAD>
<BODY>
<FORM NAME="theForm" ACTION="Request2.html" METHOD="GET">
Enter text:
<INPUT TYPE="Text" NAME="Text">
<BR>
Enter a number:
<INPUT TYPE="Text" NAME="Number">
<INPUT TYPE="Submit" NAME="Submit" VALUE="Enter">
</FORM>
</BODY>
</HTML>


Listing 6.3  -Processing the Form

<HTML>
<HEAD>
<TITLE>A Request Object Demo</TITLE>
</HEAD>
<BODY>
<SERVER>
write("<P>The form's text field contained " + request.text + "</P>");
write ("<P>The form's number field contained " + request.number + "</P>");
</SERVER>
<A HREF="home.html">Home</A>
</BODY>
</HTML>

Of course, more interesting applications may send the form elements to the site owner by e-mail or write them to a file or the database, or even use them to look something up in a file or database. The end result, however, is nearly always a write() of something back to the client.

Locking

The built-in objects, like project, contain a lock() method that operates in much the same way as transactions do in a SQL database. After a lock is set, no other application can change the locked object until the lock is released. This section shows how to set locks and explains why they are needed.

Why Lock?  Suppose you want a simple counter that keeps track of how many times someone visits your site. Hundreds of versions of this program exist in CGI scripts-Listing 6.4 shows one in server-side JavaScript.


Listing 6.4  -An Access Counter in Server-Side JavaScript

<HTML>
<HEAD>
<TITLE>An Access Counter</TITLE>
</HEAD>
<BODY>
<H1>Welcome!</H1>
<SERVER>
project.lock();
if (project.lastCount == NULL)
{
  project.lastCount == 1;
  write ("<P>You are the first person to use this application.</P>");
}
else 
{
  if (!client.logged)
    project.lastCount = 1 + parseInt(project.lastCount, 10);
write("<P>This application has been accessed " + project.lastCount + " times.</P>");
}
project.unlock();
client.logged = true;
</SERVER>
<A HREF="home.html">Home</A>
</BODY>
</HTML>

Once again, the interesting part begins with the first <SERVER> tag. The script locks the project to prevent anyone else from changing the values of the project properties while it is using them. If this is the first time that anyone has accessed this project, the script initializes project.lastCount. If the script finds project.lastCount already initialized, it checks to see whether the current client has been to this application before. If the current client has not, the script increments the project.lastCount value. In either case, the script reports out the value of lastCount.

Finally, the script unlocks the project and (perhaps redundantly) sets a client variable to log the fact that this particular client has been to this application.

Tip
The parseInt construct in Listing 6.4 is used so often that it has become a JavaScript idiom. parseInt is a built-in function in server-side JavaScript. It takes the string in the first parameter and interprets it as a number in the base that is given by the second parameter. If the string contains certain key characters, then the base is overridden. For example, parseInt("0x10", 10) is 16 because 0x denotes a hexadecimal interpretation.

Caution
If parseInt cannot interpret its first parameter as a number, parseInt will return a zero on Windows platforms and NaN (for Not a Number) on other platforms. This function is one of the few times that JavaScript behaves differently depending on the platform. This behavior has been changed in JavaScript 1.1, but only JavaScript 1.0 (not 1.1) is used in LiveWire 1.0.

Locking at the Project Level  At any given time, dozens-or even hundreds-of people may be accessing a given page. When the project is locked, no one else can read or change any project property. Consider what might happen in the previous example if two users accessed a project property at about the same time, without using locking:

  1. User 1 reads project.lastCount and finds that it is 10.
  2. User 2 reads project.lastCount and finds that it is 10.
  3. User 1 increments project.lastCount and sets it to 11.
  4. User 2 increments project.lastCount and sets it to 11.

If project.lastCount is used, for example, as an ID for a shopping cart, two users are now putting items into the same cart. This design is risky-with enough users, over a long enough time, this design sooner or later will cause an error.

With locking, the following sequence occurs:

  1. User 1 locks the project and reads project.lastCount, finding that it is 10.
  2. User 2 attempts to read project.lastCount and finds that the project is locked.
  3. User 1 increments project.lastCount and sets it to 11.
  4. User 1 releases the lock on project.
  5. User 2 now locks the project and reads and increments project.lastCount, setting it from 11 to 12.

Tip
LiveWire implicitly locks the project whenever you read or set a project property. You only need to lock the project explicitly when, having read a project property, you intend to set something in the project based on what you read.

Caution
While you have project locked, no one else can read or set any project property. Keep project locked for as short a period as possible.

An Example From a Shopping Cart System  Many shopping cart scripts want to assign a sequential number to each shopper so that the script can keep track of the shopping baskets and orders. Listing 6.5 shows a simple way to do that by using project locking.


Listing 6.5  -A Script to Assign an ID to a Shopper

<HTML>
<HEAD>
<TITLE>A Shopping Cart ID Generator</TITLE>
</HEAD>
<BODY>
<H1>Welcome!</H1>
<SERVER>
project.lock();
if (project.lastID == NULL)
{
  project.lastID == 1;
}
else 
{
  if (client.ID == NULL)
  {
    project.lastID = 1 + parseInt(project.lastID, 10);
    client.ID = project.lastID;
  }
}
project.unlock();
write("<P>Now proecessing order number " + client.ID + ".</P>");
</SERVER>
<A HREF="home.html">Home</A>
</BODY>
</HTML>

Because the project can be initialized in a starter page, you may prefer the simplified version of this script, shown in Listing 6.6.


Listing 6.6  -A Simplified Version of the Shopper's ID

<HTML>
<HEAD>
<TITLE>A Shopping Cart ID Generator</TITLE>
</HEAD>
<BODY>
<H1>Welcome!</H1>
<SERVER>
project.lock();
if (client.ID == NULL)
{
    project.lastID = 1 + parseInt(project.lastID, 10);
    client.ID = project.lastID;
}
project.unlock();
write("<P>Now proecessing order number " + client.ID + ".</P>");
</SERVER>
<A HREF="home.html">Home</A>
</BODY>
</HTML>

Note that, for this version of the script to work, the developer must initialize the project.lastID. The application's starter page is a reasonable place to do this step-so that each page does not have to check to see whether the application is initialized.

Calling JavaScript from Inside a Tag

For short pieces of JavaScript, the <SERVER>...</SERVER> construct can be cumbersome. It is often preferable to just "drop" a bit of server-side JavaScript into an otherwise conventional HTML tag.

The Syntax  Of course, conventional HTML tags have the form

<TAG ATTRIBUTE=VALUE...>

By surrounding either an attribute or a value (or both) in backquotes, the developer can force a call to server-side JavaScript. The next section shows some examples.

Some Examples  The following line puts up the appropriate image during a game of Hangman:

<IMG SRC='"Graphics/hang"+client.numberOfMisses+".gif">

Here's a line that works with the preceding shopping cart script:

<A HREF='"addToOrder.html?ID="+cursor.productID'>

Here's another line from a shopping cart application:

<A HREF='"product.html?category=" + categoryString'">

Using a .js File

Experienced programmers reading Chapter 5, "Client-Side JavaScript," will have noted that no way was given for building up a library of functions (such as is commonly done in other languages). Because all of the pages are stored on the server, but executed on the client, no easy way existed in the original version of JavaScript to send a library of functions to the client for use by all pages.

Tip
In the latest version of JavaScript you can write
<SCRIPT SRC="http://yourserver/someDirectory/filename.js">
</SCRIPT>
This technique allows you to build a library of JavaScript functions on the server, which are downloaded and run on the client. You cannot include any HTML in a .js file, but you can use this feature to share common functions among several pages. Once the file is in the client's cache, you can avoid the time required to download JavaScript for each page. In order for this feature to work, you must configure your server to map the .js suffix to MIME media type application/x-javascript.

Caution
If you write a <SCRIPT> tag with the SRC attribute, don't forget to include the closing tag, </SCRIPT>. It's easy to forget, but without it Navigator will ignore all the HTML in your file!

With server-side JavaScript, it makes sense to define a file of common functions and compile them only once. The LiveWire compiler recognizes files with a .js extension as containing function declarations only. The developer can put all of the application's common routines in .js files and compile them into the Web file. Then, the only code in the .html files is the local logic and function calls.

Built-In Functions

Although most functions are really methods on one object or another, a few functions, like parseInt, are true functions built into the JavaScript language. This section lists the most common of these functions in server-side JavaScript.

Tip
Your copy of LiveWire comes with plenty of built-in documentation. For the latest information on client-side JavaScript, look in the LiveWire/doc/javascript/ directory of your server for the file toc.html. Open that file with your browser and explore the links to the documentation. For similar information specific to server-side JavaScript, check out LiveWire/doc/index.html.

write

You have seen the write function throughout this chapter. You use it to generate HTML that is sent back to the client.

writeURL

If an application generates URLs dynamically, it should use writeURL instead of trying to build the URL with a write function. writeURL encapsulates any URL encoding that is being used to maintain the client's state.

Tip
Even if you are using a non-URL method of maintaining state (such as client-cookies), it is a good idea to use writeURL to generate any dynamically-generated URLs. This way, you can change your mind later and switch the state-saving mechanism in the Application Manager without having to change the source code.

flush

In general, operating systems do not write data as soon as a write or print statement is executed. Instead, data is buffered into fairly large "chunks." When the buffer is full, the data in the buffer is written all at once. This approach is much more efficient than stopping to do small writes of a few bytes each. LiveWire, for instance, flushes after every 64K of generated content.

Usually this mechanism is transparent to the user, but sometimes the buffering gets in the way. For example, unless the flush is included, the following code gives the user the impression that nothing is happening:

while (!In.eof())
{
  theLine = In.readln();
  if (!In.eof())
    write(LPad(LineCount + ": ", 5), theLine, "\n");
  LineCount++;
  flush();
}

redirect

The redirect function works like a call to print("Location: some new page\n\n"); in a CGI script, as soon as it is executed, LiveWire transfers the client to the indicated URL. None of the statements following the redirect are executed.

debug

The debug function writes its parameter to the Trace facility.

For more information on LiveWire debugging and the Trace facility, see Chapter 16, "Testing and Debugging Server-Side JavaScript," of Special Edition Using Netscape LiveWire (Que, 1996).

registerCFunction

The registerCFunction is part of the mechanism for calling C from JavaScript.

Details of that mechanism are given in Chapter 15, "Calling C and C++ Code," of Special Edition Using Netscape LiveWire (Que, 1996).

Predefined Objects

Recall that the LiveWire application developer has several ways to add dynamic content to the site: plug-ins, Java, client-side JavaScript, server-side JavaScript, and CGI. Of these mechanisms, all except server-side JavaScript and CGI run on the user's machine. As a result, Netscape's designers started their extension to JavaScript by examining how CGI programmers spent their time.

Thousands of CGI applications are on the Web-many of them are very simple programs, some of them are great, complex affairs. An examination of CGI code and a discussion with CGI programmers reveals three facts:

To meet these and other needs on the server, Netscape defined four objects that are available automatically to every server-side JavaScript program:

Collectively, these four objects are known as the LiveWire Object Framework. The LiveWire Object Framework is illustrated in Figure 6.4.

Figure 6.4: The LiveWrie Object Framework consists of four predefined objects, shown here in order from the longest-lived to the shortest-lived.

The Server Object

The server object comes into existence when the server is started and is destroyed when the server is stopped. Each application can read and, to a limited extent, write server data. Every application on a server shares the same server object, but if more than one Web server is running on the same computer (listening to different TCP ports), each has its own server object.

Properties  The server Object supports four properties, as shown in Table 6.1.

Table 6.1  Properties of the server Object

PropertyDescription Example
hostname The full hostname of the server, including the port number www.dse.com:80
host The server name, subdomain, and domain name www.dse.com
protocol The communications protocol of the server http:
port The port on which the server is listening80

Methods  Any application may call the lock() method of the server. Doing so makes it possible to read and update server properties without interference from any other application. While the server is locked, no one else can read or set any server property, nor can anyone else lock the server. Call the unlock() method to release the lock on the server.

Caution
While the server is locked, other applications that try to access to the server's properties are blocked. Do not keep the server locked any longer than necessary.

The Project Object

The project object is associated with the application. Recall that the developer uses the LiveWire application manager to install, start, stop, and remove applications. When the application is started, LiveWire makes a new project object. Everyone accessing that application uses the same project object. On a well-run server, the application and, consequently, the project object, have a lifetime of many weeks.

The application may add properties to the project object as the application runs. Suppose the application wants to count the number of times it is accessed by a unique client.

Each application has both a default page and an initial page. When a client comes to the application for the first time, LiveWire serves the initial page, followed by the default page. Thus, an application developer can put the following server-side JavaScript on the default page:

<SERVER>
project.lock();
if (project.count == NULL)
  project.count = 1;
else
  project.count = 1 + parseInt (project.count, 10);
project.unlock();
</SERVER>

This bit of code locks the project (so you can read and increment project.count without fear of someone else changing it before you are done with it). If you are the first client to access this project since it started, the count is set to one. Otherwise, the count is incremented.

Recall from earlier in this chapter, in the section entitled "HTML with JavaScript," that parseInt() is a built-in top-level function that transforms strings into integers.

Tip
You should use string functions when reading or writing properties of objects in the LiveWire Object Framework. These objects store their properties internally as strings. By acknowledging that fact and dealing with the conversion explicitly, you are less likely to get subtle defects in your code.

Like the server object, the project object supports lock() and unlock() methods. Also like the server object, locking the project blocks other users of the application, so the project should be unlocked as quickly as possible.

The Client Object

The Hypertext Transfer Protocol (HTTP) is called a stateless protocol. This statement means that if a Web server gets two requests in a row, it has no way of knowing whether those requests came from a single user or from two different users. Several mechanisms can be built into a CGI script by which the server can tell one user from another, but these mechanisms require advanced programming and can be tricky to set up.

When a user first requests access to a LiveWire application, LiveWire instantiates a new client object and makes it available to the application. Any information that is associated with that user can be stored in the client object. Upon completion of the request, LiveWire stores the client information. If the same user makes another request before the client object expires, the application restores the client object with the stored information.

The mechanisms LiveWire uses to store client information are fairly complex, but the application programmer doesn't have to be concerned with those mechanisms. The programmer specifies a mechanism in the Application Manager and lets LiveWire do the work.

Tip
Some of the state-preservation mechanisms, like client-cookies, rely on LiveWire to send information back to the client. This information must be sent with the HTTP headers before any content is sent.
The LiveWire buffers can store 64K of data. After you have issued calls to send that much data, the buffers are sent, and your opportunity to put data into the header is lost. Therefore, you should set any client properties before you do many calls to write, making sure that the header can be sent before the content is returned to the client.

Do not rely upon the installer to use one mechanism over another. Strive to make your code independent of the state-preservation mechanism so that Webmasters can set the state-preservation mechanism to whatever is appropriate for their site.

Properties  There are no predefined properties for the client object. Instead, the client object is the place for the application to store any information about this particular user that the application needs. A shopping cart script may store information about the items the visitor has selected. A multipart form may store information about the choices the visitor has already made. A multipage survey may store information about the visitor's choices so that coefficients of correlation can be calculated between questions on one page and another.

Methods  One of the problems with state-preserving systems is that no one knows when the visitor is gone. Suppose a visitor arrives at a shopping site like the one shown in Figure 6.5. Because this visitor arrives with no client object (he or she is not known to the site), the application's initial page issues a shopper ID, which it stores in the client object. The application then redirects the visitor to a welcome page and returns control back to the visitor.

Figure 6.5: A shopping cart script must have some way to keep track of which request goes with which order file.

A few minutes later, the visitor requests a catalog page. Because the visitor is now known to the site, the application does not issue a new ID but serves the requested page directly. The visitor makes a series of requests, each one asking that an item be added to the shopper's order. Each request contains information about the item, such as size, color, and style. The requests arrive two to three minutes apart as the shopper navigates the site.

Then-nothing. Minutes pass, and nothing is heard from the shopper. The application is keeping a record of the shopper's order so far. How long should that record be retained? Five minutes? Ten? An hour? A day? At what point can you confidently say that the visitor is gone, or-if not gone for good-at least has been gone so long that he or she would not expect to return and find the order waiting for them?

The answer, of course, depends on the application. LiveWire enables the application to call the client method expiration() that takes a single parameter-seconds. Using this method, the application can set the time-out period. The default is 10 minutes.

Of course, sometimes the application designer has just the opposite problem. The shopper has completed the order-the visitor has completed the last page of the form or the survey-it's time to destroy the client object and bid farewell to the client. Call client.destroy() and the deed is done.

The Request Object

Recall that the LiveWire Object Framework includes four prebuilt objects: the server, the project, the client, and the request. The request object has more built-in properties and methods than the other three combined. This section describes the capabilities of the request object.

If you've written CGI scripts to process form data, you know that HTTP supports two methods that enable data to go from the client to the server. The first, called GET, appends the data to the URL-it is available as the environment variable QUERY_STRING to the CGI script. If the form uses POST, the data is sent to the TCP port following the request headers. The data is pulled out by the server and delivered to the CGI script on its STDIN.

Either way, the CGI programmer must write code to take the fields out of the query string or STDIN, separate the field names from their values, and decode any characters from hexadecimal back to ASCII. With LiveWire, that coding and decoding is a thing of the past. (This feature alone may be enough to justify some programmers buying the product!) As shown in this section, when the request object is delivered to the application, it comes with all of the form elements attached to it as properties.

The request object is the shortest-lived of the four LiveWire Object Framework objects. It is brought into existence when the client sends a request, and destroyed after the response is sent. A new request is generated whenever the following happens:

Built-In Properties

The four built-in properties of the request object are shown in Table 6.2.

Table 6.2  The Built-In Properties of the request Object

PropertyDescription Example
agent Name and version of the client softwareMozilla/2.02Gold (WinNT; I)
ip The IP address of the client207.2.80.1
method The HTTP method associated with the request GET, POST, or HEAD
protocol The HTTP protocol level supported by the browser HTTP/1.0

As mentioned in Chapter 4, "Netscape Enhancements to HTML," Netscape browsers understand extensions that are not part of standard HTML (not the least of which is client-side JavaScript). The agent property can be used to serve one version of a page to Netscape browsers (identified by the distinctive name, "Mozilla"), and a different version to browsers that do not understand Netscape's extensions.

Properties from Form Elements

In addition to the four predefined properties, every element of every form on a page becomes a property of the request object. Consider the tax calculator in Listing 6.7.


Listing 6.7 -A Real Estate Tax Calculator

<HTML>
<HEAD>
<TITLE>Tax Calculator</TITLE>
</HEAD>
<BODY>
<FORM NAME="taxcalc" METHOD=GET ACTION="taxcalc.html">
What is the assessed value of the property?
<INPUT TYPE=text NAME=assessed>
<BR>
In which city is the property located?
<SELECT NAME=city>
<OPTION Chesapeake>Chesapeake
<OPTION Norfolk>Norfolk
<OPTION Portsmouth>Portsmouth
<OPTION VirginiaBeach>Virginia Beach
</SELECT>
<INPUT TYPE=submit NAME=submit VALUE=Enter>
<INPUT TYPE=reset NAME=reset VALUE=Clear>
</FORM>
<P>
The tax rate for <SERVER>request.city</SERVER> is 
<SERVER>taxrate(request.city)</SERVER>. At that rate your
monthly taxes will be $
<SERVER>
  write(computeTax(request.city, request.assessed));
</SERVER>
<A HREF="home.htm">Home</A>
</BODY>
</HTML>

In this case, the programmer can get access to the form elements just by asking for them by name from the request object.

Note
The code in Listing 6.7 could have been run as client-side JavaScript. By implementing it as server-side JavaScript, however, the application developer can offer the application to non-Netscape browsers without having to maintain two versions of the page.

Note
Note that, unlike client-side JavaScript, Listing 6.7 does not have to include all of the functions in the HTML file. Server-side JavaScript functions can be declared in a separate JavaScript file (with a .js extension) and compiled into the application.

Chapter 14, "Building LiveWire Applications," of Special Edition Using Netscape LiveWire (Que, 1996) describes JavaScript files in more detail.

Properties from URLs

The request object also picks up values passed in the URL. The general syntax is

URL?name=value[&name=value...]

For example, when the link

<A HREF="remove.html?ID=112">Remove Item 112</A>

is selected, it requests that the server load the page named remove.html and pass the value 112 in a variable named ID. Inside remove.html, the programmer can refer to request.ID and get back the value 112.

Tip
An advanced technique is to call some server-side JavaScript to build a part of a page from a database, and then call server-side JavaScript to handle the request. For example,
<SERVER>
cursor = new database.cursor(SELECT name, id FROM publishers);
while (cursor.next())
  write ("<A HREF=&quot;remove.html?id=" + cursor.id + "&quot;>" + cursor.name + "</A><BR>\n");
</SERVER>
uses the database extension (part of LiveWire Pro) to read a list of publishers from the database.
The resulting HTML that is sent to the client may resemble this code:
<A HREF="remove.html?id=201">Addison-Wesley</A><BR>
<A HREF="remove.html?id=471">Wiley and Sons</A><BR>
<A HREF="remove.html?id=55615">Microsoft Press</A><BR>
<A HREF="remove.html?id=937175">O'Reilly & Associates</A><BR>
When a user selects one of these links, the browser requests the remove.html page. The browser passes the ID that is to be removed to the server-side JavaScript on that page. Remove.html deletes the entry from the database.

The File Object

Although the file object is not part of the LiveWire Object Framework, it is one of the most useful objects a programmer can instantiate. A file can be used to store data when the full power of a relational database is not needed. You can also write an application that writes HTML files so that an application can build its own pages.

Here's an example of how to use a file object:

// file names are platform dependent; this example favors UNIX
var theFile = new File("/tmp/aFile.dat")

// check to see if the file exists and is readable
var fileIsOpen = theFile.open("r")
if (fileIsOpen)
{
  write("File name is "+theFile+"<BR>")
  while (!theFile.eof())
  {

    //keep reading the file and writing out lines
    line = theFile.readln()

    // as long as there is data to be read
    if (!theFile.eof())
      write(line+"<BR>")
  }
  if (theFile.error() != 0)
    write("Error reading file " + theFile + ".<BR>")
  theFile.close()
}

Caution
Most Web servers are run as an unprivileged user. For example, UNIX servers often run as user nobody. Nevertheless, be careful about where your applications write. Your application can write (and overwrite!) anywhere on the server machine that the unprivileged user can write to.

Note that the file constructor makes a new file object, not a new file on the hard drive. The path specified with the constructor is relative to the application directory. Specify the path in the manner that is consistent with the platform. (Thus, Windows NT users should use backslashes to separate levels of directories, and UNIX users should use forward slashes.)

Note, too, that the name of the file object is synonymous with the name of the file. In the example, write(theFile) causes /tmp/aFile.dat to be written to the output stream.

Static Methods

Recall from Chapter 5, " Client-Side JavaScript," that static methods apply to the class and not to any particular instance of the class. Class file supports two static methods: byteToString() and stringToByte(). To call a static method, call it on the class, not an instance. Thus,

File.byteToString(number)

converts the number to a one-character string, but

myFile.byteToString(number)

causes a compile-time error.

In general, files can contain ASCII text or binary data. These methods make it easy to convert from one format to the other.

byteToString  byteToString() outputs a one-character string. If the argument is not a number, the output is the empty string.

stringToByte  stringToByte() takes a string as its parameter and returns the numeric code of the first character.

State Management Methods

Class file supports five methods that can be used to change the state of the file itself: open(), close(), flush(), setPosition(), and clearError().

open  The file constructor makes a new file object that is associated with the specified path. Before you read or write the file, you must call open, specifying the mode. Valid modes are given in Table 6.3.

Table 6.3  The Modes of open

Mode
Description Fails if…
r
Opens the file as for readingFile does not exist or is not readable
w
Opens the file for writingCannot write to the specified directory
a
Opens the file for appendingCannot write to the file (if it exists) or the directory
r+
Opens the file for reading and writing File does not exist or is not readable
w+
Opens the file for reading and writing Cannot write to the file (if it exists) or the directory
a+
Opens the file for reading and writing, starting at the end of the file. Cannot write to the file (if it exists) or the directory

Note
Windows makes a distinction between text files and binary files. (UNIX does not.) If the application may run on a Windows server, append a "b" to the mode to denote binary mode. The default is text mode.

Note
In addition to the reasons for failure given in Table 6.3, open() also fails if the file is already open.

close  When you are done with the file, call its close() method. close() fails if the file is not open.

flush  flush() is used with the write methods. JavaScript's write methods are buffered-to force a write, call flush().

setPosition  The file object maintains an internal pointer to the current read/write position. If you open a file in append mode, this pointer is at the end of the file. Otherwise, when the file first opens, the pointer is at the beginning. Use setPosition() to move the pointer around the file. The syntax is

fileObject.setPosition(position [, reference])

where position is an integer offset from the reference and reference can be

Thus, at the end of the following code, the position is five characters from the beginning of the file if all of the function calls succeed.

var theFile = new File("/tmp/theFile.txt")
theFile.open("r+")
theFile.setPosition(3)
theFile.write("xy")

The file position is at the beginning when the file is opened. setPosition() advances the position three characters from the beginning, and write() advances the position two more characters.

clearError  clearError() resets the error number and the eof() method, both of which are described later in this section.

Info Methods

Six methods on file report information about the file without changing anything about it: getPosition(), getLength(), exists(), eof(), error(), and toString().

getPosition  getPosition() is the companion function to setPosition(). getPosition() returns the read/write position, with zero being the first character in the file. If there is an error, getPosition() returns -1.

getLength  getLength() returns the number of bytes in the file. If the file is a text file on a Windows system, the answer represents the number of characters in the file.

exists  To check to see whether the file named by the file object actually exists, call its exists() method.

eof  eof returns true if the read/write position is at the end of the file. Thus, an application can open a file for read (positioning the pointer at the beginning of the file) and issue a series of reads until eof() is true.

Tip
eof() returns true after the first read operation that attempts to read past the end of the file. Thus, the last read() before eof() that becomes true cannot be relied on to hold the expected result. The common idiom is to write
while (!theFile.eof())
  {
    line = theFile.readln()
    if (!theFile.eof())
       write(line)
  }

Tip
so that eof() is called twice-once after the read to determine whether the data is meaningful to write it, and again at the top of the loop to be sure it is safe to read again.

error  If error() returns a -1, the file is not open. If error() returns zero, the file is open and has no error. Error codes (positive non-zero values) are operating system-dependent. By using error(), you can now interpret the last few lines in the demo code that appeared at the beginning of this section:

if (theFile.error() != 0)
 
write("Error reading file " + theFile + ".<BR>")

If, after the series of calls to readln(), error() is set to a non-zero value, either the file failed to open or an error occurred during one of the reads. Either way, the application complains about the problem and closes the file.

Read Methods

The file class enables the programmer to issue a read of a predefined number of bytes or of a single byte. For text files, however, it is often convenient to read until the end of the line.

read  The read() method is most commonly used when the data is binary. When reading files of fixed-length records, the read() method may also be used. This method takes one parameter-a count-and reads that number of bytes from the file. The read()method returns a string. If the read attempts to read past the end of the file, the valid characters are returned and eof() becomes true.

readln  readln() is appropriate for text files-it reads to the end of the line.

Tip
The end of the line in a text file depends on the computer on which the server is running. UNIX machines denote the end of the line with a linefeed, also known as a newline, and denote \n. Windows machines use a carriage return followed by a newline (\r\n). These line separator characters are not included in the returned string.

readByte  readByte() reads a single byte from the file and returns its numeric value. This is most appropriate for use with binary files.

Write Methods

Like the read methods, file can handle writes at several levels of granularity: the byte, the character string, or the line.

Tip
JavaScript write methods are internally buffered. To force the write, call flush() on the file object.

write  write() takes a single parameter, a string, and writes it to the file.

writeLn  writeln() works like write, but includes the line separator characters appropriate for the operating system (\n for UNIX, \r\n for Windows).

writeByte  writeByte() outputs a single numeric parameter that is useful when writing to binary files or devices. For example, to write an ASCII NULL character, use writeByte(0).

Locking Files

When multiple users attempt to write to the same file, the potential always exists for one user's activity to interfere with another's activity-and a strong likelihood exists that the file will become corrupted. In server-side JavaScript, this problem is easily solved by using the lock() and unlock() method of the project object. Before accessing the file (for either read or write), call project.lock(). Be sure to call project.unlock() when the file operation is done.

If a file is shared between applications, use server.lock() and server.unlock() instead of the calls to the project object.

Tip
Do not rely on the read/write pointer being where you left it after the last unlock. After you unlock, another user of your application can get a lock and move the pointer. After they unlock and you get the lock back, you should reposition the pointer if its location is important to you.

In the SDK…

This chapter provides a description of server-side JavaScript. Server-side JavaScript is a powerful language that is closely related to client-side JavaScript, but is designed to be compiled into bytecodes and executed on the server.

In addition to its capabilities of serving pages dynamically, server-side JavaScript can give the Web developer access to server files and databases. The Netscape ONE SDK site contains information about JavaScript programming. Some Netscape products, such as LiveWire Pro, are implemented as libraries of JavaScript functions that run on the server. If you have a Netscape server and LiveWire (or LiveWire Pro), you can write JavaScript on the server.

ON THE WEB
http://developer.netscape.com/library/one/sdk/livewire/livewire.html
In addition to the client-side JavaScript documentation sites (given at the end of Chapter 5, "Client-Side JavaScript,"), you should visit this site to learn the specifics of LiveWire.