★ wanayoo — archive 1999 http://developer.iplanet.com/viewsource/fields_java/fields_java.htmlNouvelle recherche | Portail wanayoo
iPlanet

You are here:  Home > Developers > View Source Articles > Server-Side JavaScript View Source Article
Server-Side JavaScript View Source Article
 iPlanet Developers


Developer Program
  Membership
  One-to-One Support
  Newsgroups
  Member Services

Developer Publications
  View Source
  Developer News

Documentation
  Technical Manuals
  White Papers
  TechNotes
  Sample Code
  FAQs
  Books

Technologies
  Application Server   CSS
  DOM
  CORBA
  Directory & LDAP
  Dynamic HTML
  Java
  JavaScript
  Linux
  RDF
  Security
  SSJS
  XML
  XUL

Developer Downloads
  Tools & SDKs
  Patches

iPlanet Products
  Technical Resources










spacer
Expanding Your Server-Side Programming Repertoire

By Duane K. Fields


Send comments and questions about this article to View Source.
Click here for printer-friendly version

Don't get me wrong -- I love developing in server-side JavaScript. I'm lazy by nature and appreciate JavaScript's relaxed approach to variable typing and declaration. LiveConnect gives me access to Java's unique functionality when I need it, and I've created a good number of reusable libraries that speed the development process. I develop many of our internal tools here at Tivoli Systems with Enterprise Server's JavaScript capabilities and use it for rapid application development. But for our more complex, multitier applications developed using a team approach, I've begun employing Enterprise Server's Java capabilities -- through servlets.

In this article -- written primarily for those currently developing on the server in JavaScript -- I'll explain the basics of working with Java on the server through Sun's Java Servlet API. I'll start off with a brief overview of servlets and their role in server-side software development. Then I'll compare JavaScript applications with servlets in a number of crucial areas of program design. I'm assuming that you're already familiar with Java programming, but if not, I hope this article will encourage you to begin exploring the possibilities offered by Java servlets. Once you're comfortable with basic Java programming, it's fairly easy to extend that knowledge to include servlet development. Additionally, if you have experience developing in server-side JavaScript, you're already familiar with all of the application design concepts necessary to work with servlets.

SERVLETS AND THEIR PLACE IN THE WORLD

Servlets are the first standard extension to Java released by Sun Microsystems. The classes that comprise the Servlet API are typically not distributed with the core Java classes but are controlled and managed by Sun itself. What this means to web developers is that support for servlets isn't included in every web server out there, but the API is stable, easily obtainable, and consistent with the rest of the Java API. And almost every Java-enabled web server can support servlets with just a little retrofitting, described in the next section. Remember, too, than since all of our Java servlet code will be running on the server, not the client, no particular requirements for Java are placed on the end user's browser.

The benefits of developing applications in Java or JavaScript instead of with the more traditional CGI scripting method have long been touted. CGI, by comparison, is a slow and inefficient architecture for web applications, requiring as it does that a unique instance of the executable or script interpreter be invoked to handle each incoming request. Persistence and state maintenence are other problems not easily handled by CGI applications. Both Java and JavaScript applications provide good data-persistence options and much-improved performance.

If Java and JavaScript are both attractive development environments, why should JavaScript developers consider servlets for more complex application projects? First and foremost, Java is a richer, more structured language better suited to complicated programming tasks -- especially those being handled by a team of developers. Java's strong typing and memory-access protection create the safer, more predictable runtime environment demanded by mission-critical applications. Working in Java also gives you access to Java's rich core APIs, as well as the ability to easily build on the efforts of others by leveraging their classes. Last, Java servlets are portable -- not just across platforms, but across vendors.

EQUIPPING YOURSELF FOR SERVLET DEVELOPMENT

As mentioned earlier, while very few web servers (Sun's Java Web Server being a notable exception) support the latest edition of the Servlet API out of the box, it's relatively easy and cheap to retrofit a web server with a servlet engine. A servlet engine is a server extension, written in a server-specific API such as NSAPI or ISAPI, that acts as a harness for running servlets on the web server. While Netscape Enterprise Server 3.5.1 does provide support for the 1.0 release of the Java Servlet API, the 2.0 API now available adds some essential new features, so your best best is to upgrade to the 2.0 level of support with a servlet engine.

Servlet engines are available for Enterprise Server on most platforms, and for many other web servers as well. Each has its own relatively straightforward interface for configuring, starting, and stopping servlets. The most popular of the engines currently available are the following:

This issue of View Source contains an article by Paul Colton, CEO of Live Software, on adding the JRun Servlet Engine to a Netscape server. (Adding Java Servlet API Capability to Netscape Servers)

To develop servlets, you'll also need a copy of Sun's Java Servlet Development Kit (JSDK), version 2.0 or later. The JSDK contains the Java classes necessary to develop servlets, as well as ServletRunner, a standalone application that enables you to test your servlet code without a web server. (Note: The ServletRunner documentation isn't too clear on a couple of things. First, when you specify the location of your servlet directory with the -d option, you need to specify the full path. Second, if you need to specify an alternate document root, use the -r option.)

DESIGNING YOUR APPLICATION

In this article I'll discuss working with servlets in their most popular form, which is similar to working with a CGI script: you write the servlet, put it on the server, and let it handle requests. However, more advanced options modeled after features that were until recently unique to Sun's Java Web Server are becoming available. One such feature, Server Side Include, enables you to embed references to servlets in a web page in much the same way you would embed an applet. At delivery time, this embedded statement is replaced with the output of the corresponding servlet. Another option, Java Server Pages, even more closely follows the JavaScript application model by allowing you to distribute Java code throughout HTML documents. Since these features aren't as commonly available and build on the basic concepts anyway, I won't go into any detail on them here.

Once you're comfortable with basic Java programming, switching gears from JavaScript development to servlets isn't as hard as you might think. The development techniques involved share many aspects, although beginning Java programmers might have some difficulty getting their minds around the idea of one instance of the servlet object being called on by multiple, simultaneous threads. Like JavaScript, servlets are built around a request/response model. When a page request comes into the web server, it's routed to the servlet for handling, and the servlet's output is returned.

To get an idea of what a Java servlet looks like, take a look at the ever-popular "Hello World!" program in Listing 1. This example shows a pretty typical servlet implementation that extends the HttpServlet class and overloads the doGet() and doPost() methods. For an example of an even simpler servlet implementation, see Listing 1 in Paul Colton's article. In that example, Paul has overloaded the service() method (whose job is normally to dispatch the request to either the doGet() or the doPost() method) to handle outputting the "Hello World" statement.

The sample code included here is provided for your use on an "AS IS" basis, under the Netscape License Agreement - Terms of Use.


Listing 1. A simple "HelloWorld!" servlet

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloServlet extends HttpServlet
{
   public void doGet (HttpServletRequest  request, HttpServletResponse response) throws ServletException, IOException
   {
      // Prepare the client to receive HTML and get our output stream.
      PrintWriter out = response.getWriter();
      response.setContentType("text/html");
      out.println("<HTML><BODY>Hello World!</BODY></HTML>");
   }

   public void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOExceoption
   {
      // Dispatch this to the doGet() method to make post and get synonymous.
      doGet(request, response);
   }
}

As you can see, the basic model for a servlet is to provide methods to handle the doGet() or doPost() methods, using the request object to gather information and the response object to return it. Once compiled, the resulting HelloServlet.class file can be configured through your servlet engine's configuration panel. Any request (either GET or POST methods) will return the earth shatteringly thought-provoking "Hello World!" message.

Don't worry about the details just yet; we'll delve into these later on. Let's first step back and look at how a Java application is put together.

In JavaScript, you start off by creating a set of HTML documents. Some of these documents are plain HTML, while others contain embedded JavaScript designated for use on the server. You generally have some data-entry documents that collect data through a form, and some form-handling code to interpret and work with the data. These are all compiled into a single .web package and placed on the server, mapped to the URL specified in JavaScript's application manager. For example, say we have a two-document application that we call dbtest. Its two documents, query.html and show_results.html, would be compiled into a single package called dbtest.web. We would then use the application manager to set up our dbtest application. To query the database, the user would visit /dbtest/query.html, which when submitted would post the data from its input form to the /dbtest/show_results.html URL, to be handled by the code inside of the show_results.html document.

Thus, JavaScript has a structured system for combining multiple documents (some containing code) into a discreet application file. By contrast, there's no standardized system for compiling a collection of servlets into a single application. Servlets are generally independent of each other, and a collection of servlets is an application only in the mind of the developer and the spirit of the design. For convenience, related servlets can be combined into a JAR file -- a Java Archive -- for easy shuffling between servers, but as far as the server is concerned, this is just a packaging ploy.

If our dbtest application were developed with servlets, we would probably make the query page a plain HTML page, call it query.html, and design a Java servlet called something like ResultsServlet. The ResultsServlet.class file would be installed on the server through our servlet engine's configuration interface and mapped to any valid URL we wanted, typically something like /servlet/ResultsServlet. (Notice that unlike in JavaScript, we're usually not passing control to an .html extensioned file but to the servlet class itself.) The query.html input form file would be placed on the web server (or any web server, for that matter) as a regular document. While there's no particular benefit in doing so, we could have used a servlet to generate the HTML form that made up our query. Or we could define the doGet() method to return the HTML form, and the doPost() method to handle the results. This has been a common tactic in the CGI programming world for some time.

If you're familiar with developing CGI applications, it's helpful to look at servlets from that design perspective. You can think of servlets as Java CGI scripts, but with an important distinction. A servlet is instantiated and initialized only once, at startup, and then handles requests from the main web server as needed. Unlike CGI scripts or programs, which are evoked separately to handle each incoming request, a single instance of the servlet handles multiple requests simultaneously. The servlet isn't brought up and torn down with each request. This application persistence happens when JavaScript code is executing -- the server dispatches a separate thread to process the code. We'll look at how to avoid conflicts that may arise between competing threads later.

CONFIGURING YOUR APPLICATION'S STARTUP PARAMETERS

In JavaScript, one of the challenges programmers face is managing host- or application-specific initialization parameters. For example, if you have one application that's used on multiple hosts, its data files might live on different paths on each. Or perhaps you have configuration parameters that need to be changed depending on the exact behavior desired. Other than recompiling the application each time, JavaScript can accomplish this by creating routines to manage persistent INI files on the server, or retrieving configuration data from a database. Unfortunately, this means more work for you, the developer, since none of these techniques are built into the core language.

Servlets, on the other hand, do have a built-in mechanism for passing in initialization parameters. Although it's implemented a little differently by each servlet engine, the basic idea is the same across all of them. A collection of name/value pairs is associated with each servlet on the server. When the servlets are first initialized (say at a reboot), each servlet's init() method is called. Within the init() method, you can use the getInitParameter() method to access these configuration paramaters and can alter the behavior of your application accordingly. Likewise, Java provides a built-in class for handling INI-style configuration files -- java.util.Properties.

COMMUNICATING BETWEEN CLIENT AND SERVER

One of the critical elements of application design is passing information from the user (on the client) to the server for processing. Let's first review how you pass this information in JavaScript. Form data from a GET or POST action surfaces automatically as properties of the request object on the receiving JavaScript page. Variables that may have multiple values -- such as a select list -- are accessed through the getOptionValue() function, which takes an identifier string and an index variable. To access information from a named form element, we simply read the corresponding property of the request object, which returns a string representing the property's value (or a null if it's undefined).

Java also employs the concept of a request object that's passed into the receiving code. In Java, an object that's an instance of HttpServletRequest is passed into the servlet's doGet() or doPost() service method, depending on the request method used. This object encapsulates any incoming form data as well as information about the request itself. In typical Java style, form data isn't accessed directly but through the getParameter() method. A slew of other methods like getRemoteHost() and getRemoteUser() are available to obtain other specifics about the request. For variables that may have multiple values, use the getParameterValues() method, which returns an array of string values. All in all, it's a very convenient and clean way to get to the incoming data.

Let's look, in Listing 2, at a snippet of JavaScript code that retrieves some form data stored in the request object, and at its Java equivalent. Let's assume that the form we're accessing was configured with the following HTML:

<INPUT TYPE="text" NAME="username">
<INPUT TYPE="text" NAME="userid">


Listing 2. A comparison of form data retrieval code

JavaScript version:

username = request.username;
if (request.userid)
   userid = parseInt(request.userid);
else
   userid = 0;

Java version:

String username = request.getParameter("username");

int userid;

try
{
   userid = Integer.parseInt(request.getParameter("userid"));
}

catch (NumberFormatException e)
{
   userid = 0;
}

Notice that in both languages the values come through as strings, and integers (or other nonstring data types) must be parsed out of the parameters. We'll see some examples of this a little later. (Yes, catching exceptions is a pain, but it's for our own good.)

GETTING ENVIRONMENTAL INFORMATION

Information about the environment a request was delivered in -- such as the IP, hostname, and other request- or server-specific values -- is necessary for lots of common tasks in web development. JavaScript uses its server and request objects, along with the ssjs_getCGIVariable() function, to expose such information. In Java, this information is provided through access methods of the request object passed to your service methods. A few examples of common attributes are given in the following table, along with the JavaScript properties/functions and Java methods used to retrieve them.


 

Table 1. Environmental Access Methods

Property

JavaScript property/function

Java method

Hostname of server

server.hostname

getServerName()

Port number of server

server.port

getServerPort()

IP of client

request.ip

getRemoteAddr()

Remote username

request.auth_user

getRemoteUser()

Query string

ssjs_getCGIVariable("QUERY_STRING")

getQueryString()

Request type (get/post)

ssjs_getCGIVariable("REQUEST_METHOD")

getMethod()



As you can see, everything you're used to working with is still there, but in Java it's been corralled into a more consistent and manageable interface. In JavaScript, the server object is also used to store global data -- information accessible by all applications running on the server. We'll look at how Java handles global data later in this article.

MAINTAINING STATE ACROSS REQUESTS

Now let's see how we can keep some of our information around between requests -- persistent data. Both JavaScript and Java applications are built around a series of stateless requests -- that's the nature of HTTP -- but it's critical that we somehow maintain client-specific data from request to request. JavaScript accomplishes this through its client object, which it uses to store data for a particular user and application across requests. You can keep information, such as a shopping basket, in a client object that's unique to each user. This data is maintained with cookies, either on the client or the server, depending on the options you've selected. JavaScript takes care of sharing the data and moving the cookies around.

JavaScript's client object can store only string values. Any other values, such as numbers, dates, or Booleans, must be parsed out of the resulting string. More advanced techniques can be employed to get around the string-only limitation: you can access the ssjs_getClientID() method and use it as a user-specific key to data stored on the project object. However, you the developer are responsible for creating, managing, and destroying this data on the project object, making this a somewhat daunting task.

The 2.0 release of the Java Servlet API provides for session-specific data management through a technique similar in design to the ssjs_getClientID() indexing idea in JavaScript. A client-specific identifier serves as a key to accessing a pool of stored data. Fortunately, Java does most of the cleanup and data shuffling for you. To work with session-specific data, you need to associate the servlet with the session identifier early in its code, before writing any output or working with the response object. To establish a session, call the getSession() method of the request object. Once you have a reference to the user's session object, you can use this object's getValue() and putValue() methods to retrieve and store Java objects. Any type of object, not just strings, can be stored in a session object. Be warned, however, that you may need to cast the data as you get it back out to let the compiler know what you're intending. Another hint: you'll need to use wrapper classes like Integer or Boolean to store data primitives since putValue() accepts only objects!

Listing 3 is an example of a simple servlet that remembers your last visit by storing a Date object in your session object.


Listing 3. A guest log servlet

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.Date;

public class GuestLogServlet extends HttpServlet
{
   public void doGet (HttpServletRequest request,
   HttpServletResponse response) throwsServletException,

IOException
   {
      // Retrieve the session object or create a new one if it doesn't exist.
      HttpSession client = request.getSession(true);
      
      // Prepare the client to receive HTML and get our output stream.
      PrintWriter out = response.getWriter();
      response.setContentType("text/html");
      out.println("<HTML><BODY>");
      
      // In case this is a new visitor...
      if (client.isNew())
         out.println("Welcome first-time visitor!");

      else
      {
         Date lastVisit = (Date) client.getValue("GuestLog.lastVisit");
         out.println("Your last visit was on " + lastVisit);
      }
      
      
      out.println("</BODY></HTML>");

      // Now store the current date and time for next time.
      client.putValue("GuestLog.lastVisit", new Date());
   }

}


While the hash key used by the session-tracking object's getValue() and putValue() methods can be any string, we must remember that unlike in JavaScript, the client-specific session object is shared by all servlets running on the server and isn't partitioned by application. Therefore, we must be sure to avoid collisions between servlets by establishing a consistent naming scheme. A useful technique is to append the servelet or application name to the front of the key to assure a unique entry.

One important difference between Java's session-tracking capabilities and JavaScript's client object is how the information is stored between requests. JavaScript can be configured to store its information in client-side cookies -- which are stored on the end user's machine and passed back and forth between requests. As covered in detail in Writing Server-Side JavaScript Applications, client-side cookies have storage and speed limitations but maintain the information across servers and through server reboots.

By contrast, Java's session-tracking technique stores only the client's session ID information through client cookies. The actual data itself is maintained in memory on the server. Java provides a mechanism to use cookies for the data as well, though a little less transparently than through JavaScript. You must instantiate a Cookie object and set its attributes directly. Another option is URL rewriting, which passes the information around in the URL itself, the only option available if your application must support users who can't or won't use cookies in their browser. Fortunately, Java provides methods that determine the cookie-storing capabilities (and preferences) of the client, allowing a single application to support cookies where available and URL rewriting where necessary.

The lifetime of the session object is configured by the web server's servlet engine and typically defaults to something short -- 30 minutes or so, since it's primarily intended for "life on transaction" style values. If the servlet or server is restarted, the session is cleared and your data will be lost. If you need persistence over a longer period of time, or beyond the life of the servlet, you'll need to employ more advanced options such as storing the client's information in a database or server-side file. One way to do this is to override the servlet's destroy() method, which gets called before the servlet is shut down (unless your server crashes unexpectedly). This is a good place to clean up any open resources or temporary files, and to save off any state information. This information can then be restored during the init() phase of the servlet as it starts up.

SHARING DATA GLOBALLY

JavaScript provides a project object as a mechanism to hold global data and share information among clients accessing an application. You simply assign properties to project and access them later. The persistent nature of Java gives you a natural way to share information among clients accessing a particular servlet -- the class's instance variables. Since servlet classes are instantiated only once, you can store data of interest to all clients accessing the servlet in the instance variables of the servlet itself. You'll need to protect writable instance variables from collision, as they're accessed simultaneously from multiple threads, as described below. Let's look, in Listing 4, at an example of a servlet that can track the number of times it's been accessed. The counter is maintained in an instance variable, accessible by all threads that call on the servlet.


Listing 4. A counter servlet

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class CounterServlet extends HttpServlet
{
   int counter;
   public void init(ServletConfig config) throws ServletException
   {
      super.init(config);
      counter = 0;
   }

   public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      response.setContentType("text/html");
      PrintWriter out = response.getWriter();
      out.println("<HTML><BODY>");
      synchronized(this)
      {
         counter++;
         out.println("Page has been accessed " + counter + " times.");
      }
      out.println("</BODY></HTML>");
    }
}

Because servlets are generally independent of each other, this technique won't work when the data needs to be accessed from more than one particular servlet instance. Suppose you need to have an instance variable that's accessible by threads accessing any servlets on the server. In JavaScript you might use the project object as a storage bin for such a variable, since all files that make up the JavaScript application have access to it. In Java, a good strategy is to employ a programming technique called the singleton. In this model, you create a class that can only be instantiated once per virtual machine. The instance itself is accessible through static methods of the class, giving any servlet access to the singleton object. Let's look at the counter example again, this time implemented, in Listing 5, through a singleton class so that any servlet can access it. This makes it a counter that can report its access count to any servlets on the server.


Listing 5. A singleton-style counter class

public class CounterSingleton
{
   private static CounterSingleton theInstance = null;

   private static int counter;

   public static CounterSingleton getInstance()
   {
      if (theInstance == null)
         theInstance = new CounterSingleton();
      return theInstance;
   }
   
   private CounterSingleton()
   {
      counter = 1;
   }

   public synchronized void incValue()
   {
      counter++;
   }

   public synchronized int getValue()
   {
      return counter;
   }
}

Then, from within a servlet access method you could work with the current counter value as shown in the following snippet of code:

CounterSingleton accessCount = CounterSingleton.getInstance();
out.println("I have been accessed " + accessCount.getValue() + " times.");
accessCount.incValue();

The singleton model assures that only one actual counter object is ever instantiated; thus, everyone using the class is accessing the same instance. This strategy often comes into play during the design of applications that connect to databases. In JavaScript, we might instantiate a dbPool object and store a reference to it in the project object. In Java, however, we would access our pool of database connections through a singleton-style class.

PROTECTING DATA INTEGRITY AT RUNTIME

JavaScript's locking feature is used to ensure that shared data on the project or server object isn't corrupted by simultaneous modifications from competing threads. In Java, each request that comes in for the servlet accesses the servlet object's methods and instance variables in its own thread. Servlets, therefore, must be written to handle multiple service requests simultaneously, making it the servlet writer's responsibility to control access to any shared resources through Java's synchronization techniques. Such resources include in-memory data such as instance or class variables of the servlet, as well as external components such as files, databases, and network connections.

In JavaScript, if there were a variable stored in the project object -- say an access-tracking variable referenced by project.counter -- that we wanted to increment, we would protect the code with a lock on the project object, make our changes, and then remove the lock. Here's an example:

project.lock();
project.counter++;
project.unlock();

In Java, locking is handled through Java's thread synchronization methods. Routines or blocks of code should be prefaced with the synchronized keyword if they're attempting to access or modify data that could potentially be in use by another, simultaneous thread.

ACCESSING A DATABASE

One of the big benefits of server-side JavaScript is its built-in support for communicating with a wide variety of databases. Rest assured that Java provides equally powerful (if not more so) database support through its JDBC (Java Database Connectivity) package in combination with platform-specific drivers provided by each database manufacturer. Java also provides support for ODBC (Open Database Connectivity), a cross-vendor abstraction layer, allowing you to create truly platform- and vendor-independent database applications.

JDBC works very similarly to JavaScript's LiveWire interface. Once you've created a valid connection object, you can execute queries and access cursors of information through it. As in form-data passing, data is retrieved through access methods rather than as direct properties of a cursor object as in JavaScript. Here's a quick snippet of Java code, just so you can get the idea:

Connection con = DriverManager.getConnection("jdbc:odbc:mydb.db");
Statement stmt = con.createStatement();
ResultSet rs = executeQuery("SELECT name, id FROM users");

while (rs.next())
{
   String name = rs.getString("name");
   Int id = rs.getInt("id");
}

stmt.close();
con.close();

While I won't attempt to cover the details of JDBC in this article, you may be wondering about that getConnection() method. Notice that Java has mapped database connection calls into a URL-style syntax. This gives you a vendor-independent way to address a database as a network resource.

One particularly cool thing about JDBC is that the interface to the underlying data is the same no matter what type of database you're actually working with. And since the interface is extensible, any manufacturer can create drivers to tie into JDBC. For example, there's a JDBC driver that stores all of your information in simple flat files but gives you RDBMS-style access to the information!

PARTING WORDS

Java's rich APIs and good formal structure make it an ideal language for the team development of complex web applications. Looking ahead, it's likely that servlets (or their offspring) will become essential components in the development of web-based applications, as well as one of the key methods of extending functionality in web application servers and other multitier architectures. That's reason enough for web developers to pick up this important technology.


FURTHER RESOURCES


View Source wants your feedback!
Write to us and let us know
what you think of this article.

Duane K. Fields is a web applications engineer with IBM's Tivoli Systems, designing interactive tools for internal and external customers. Duane is an active writer and a frequent speaker on the technical and aesthetic aspects of developing and running commercial web sites.

(10.98)


Related Readings:


Any sample code included above is provided for your use on an "AS IS" basis, under the Netscape License Agreement - Terms of Use

spacer spacer


                                                       
iPlanet International | Year 2000 | Site Map | Feedback
Products | Solutions | Support | Services | Download | About Us | Developer
© 2000 Sun-Netscape Alliance. All Rights Reserved  Privacy Policy