| ## Java 1.1 Unleashed # - 55 - Serving the Net with Jeeves by Mike Fletcher IN THIS CHAPTER How the Other Half Lives: Web Servers What Makes Jeeves Different from Other HTTP Servers? HTTP Server Administration Made Easy Access Control The Servlet API Sample Servlet: A Simple Phone Database This chapter discusses Jeeves, Sun's Java-based Web server. The chapter starts with an introduction to Web servers for those not familiar with them and continues with a discussion of how Jeeves differs from other servers. An overview of some of Jeeves's features follows, including a sample "servlet" (a Java class that runs in the server to dynamically create content) and an introduction to the servlet API. ## How the Other Half Lives: Web Servers Web servers are the compliment to Web browsers. When you type a URL or click a link, your browser contacts the HTTP server residing on the host from which you want to retrieve content. Using the HTTP protocol, the browser indicates what resource it wants to obtain, and the server sends back the requested data (or an error if the request fails). You may sometimes hear an HTTP server referred to as an HTTP daemon. No, it doesn't mean you need to have your PC exorcised. This usage comes from UNIX terminology for a process that provides system services. (In mythology, a daemon is a helpful spirit.) A typical UNIX system has several daemon processes that provide services such as FTP, Telnet, and e-mail. Daemons can start running at system boot time, or they can be started by a process called `inetd`. The `inetd` daemon determines what service is requested by the port on which the request comes in. For performance reasons, an HTTP daemon is usually started at boot time rather than from `inetd` to avoid the overhead of starting up a new process every time a Web page is requested. Originally, Web servers were limited to returning HTML content from files located on the server's file systems. Originally, the only interaction between a client and a server was a simple search facility. A page would be marked as `ISINDEX,` which indicated to the client that an argument could be appended to the URL for the page. To provide interaction between a client browsing the Web and a server returning content, forms were added to the HTML standard. With the added capabilities provided by forms, the Web started moving towards its much more interactive form. Web servers also changed to support the new interaction. The Common Gateway Interface (CGI) is a standard interface that allows an HTTP server to interact with an external application. The CGI specification defines things such as how an external application is given command-line options and what environment variables contain information. ## What Makes Jeeves Different from Other HTTP Servers? Although it provides necessary functionality in today's interactive Web, CGI has some problems. One of the worst is that of security. When users access a URL provided by a CGI program, they run a program on your Web server. If a CGI program is not carefully written, it could allow a malicious user to gain access to your server or destroy data. This problem can be avoided, but it is something to be aware of. Another problem with CGI programs is that a separate external program must be started up each time someone requests a URL provided by a CGI program. This extra overhead may not be noticed on an average Web server, but it can make a difference. Several alternatives to CGI exist to address this problem, such as the FastCGI specification from Open Market (which uses persistent external processes to handle requests) or Netscape's server API (which is a C interface that allows code that handles requests to be dynamically linked into the Web servers). Jeeves addresses both these concerns. In addition to providing the usual CGI interface, Jeeves supports servlets. A servlet is a Java object run by Jeeves to handle a request from a client. Servlets can be loaded from the local machine on which the Web server is running, or they can be loaded over the network. Untrusted code loaded over the network is treated similarly to classes loaded by a Web browser and is limited in what it can access. Jeeves also takes advantage of Java's threading capabilities. In addition to using multiple threads to dispatch incoming requests, servlets can be run in their own threads. This capability reduces the overhead necessary to dispatch requests for dynamic documents, especially on multi- processor machines. ## HTTP Server Administration Made Easy Most HTTP servers, especially the freely available UNIX versions, must be configured by editing a set of configuration files with an editor. Unless you are intimately familiar with your server, it is easy to make mistakes. Jeeves' configuration is handled by means of an interactive applet accessed through your browser (see Figure 55.1). Although the information is stored in files that can be edited, Jeeves's fill-in-the-blank configuration is much easier to use than searching through manual pages for the exact syntax to turn off this or that feature. To access the server configuration screens from the `Configuration` applet, you must authenticate yourself to the server with a user name and password. The list box on the left side of the screen shows the different groups of settings you can configure. To the right are the various fields, buttons, and what-not that let you do the actual configuring of the server. Table 55.1 explains what the different sections control. #### Table 55.1. Jeeves configuration groups.
Section | Explanation
HTTP Configuration | Sets HTTP protocol parameters.
Log Configuration | Defines names used for log files that track server accesses and errors.
File Aliasing | Controls the mapping of URLs to files and directories.
Servlet Aliasing | Controls the mapping of URLs to servlets.
Servlet Loading | Defines where the code for servlets is loaded from and any parameters passed to the servlet.
MIME Section | Sets up mappings from file extensions to the MIME type returned.
Users, Groups, and ACLs | These three sections control the creation, deletion, and modification of access control-related settings.
Resource Protection | Allows you to grant privileges using the information entered with the preceding sections. More information on this is provided in "Access Control," later in this chapter.
Reauthenticate | Lets you enter (authenticate) yourself to the server with your user name and password. It allows you to log in as a different user, or to reconnect to the server if you have restarted the server while leaving your browser to run the `admin` applet.
Figure 55.1. The Jeeves `Configuration` applet. ### HTTP Configuration The HTTP Configuration section of the Jeeves `Configuration` applet enables control of the settings related to the HTTP protocol. In this section, you set the port on which the server listens. You can also define the maximum number of connections the server will accept. Related to this are the minimum and maximum numbers of threads that can start to handle requests. Jeeves provides support for the HTTP "keep-alive" directive. This extension to the HTTP protocol allows a client to ask that a connection be kept open and used to retrieve multiple URLs. The HTTP Configuration section has fields for setting the maximum number of requests for each keep-alive session and a timeout value (to prevent a client from monopolizing a connection). ### Log Configuration The Log Configuration section of the Jeeves `Configuration` applet lets you define the names of the log files to which Jeeves will write information. The level of information written to each log can be customized depending on the log. For example, the `access_log` (logs which pages are being retrieved) and the `referer_log` (logs which page the browser came from before your page) can be either on or off. The `error_log` (records any errors the server encounters, such as trying to retrieve a nonexistent page) can be turned off, set to log only major problems, set to log major and minor problems, or set to record all errors. In addition to the access log (which tracks the resources being accessed by clients) and the error log (which notes errors such as when nonexistent files are requested), Jeeves provides an event log for servlets. Servlets can use the `log()` method of the `java.servlet.Servlet` class to write a `String` to the event log. ### File Aliasing Although the primary purpose of an HTTP server is to return files in response to client requests, you don't want to give out access to your complete file system to just anyone. The File Aliasing section of the Jeeves `Configuration` applet allows you to map URLs to particular directories or files. This facility also lets you give an easy-to-remember URL to a particular resource. For example, if you have a page with a feature that changes monthly, you can set the URL `http://myhost.com/features/current` to point to the current month's page. ### Servlet Aliasing and Servlet Loading The Servlet Aliasing and Servlet Loading sections of the Jeeves `Configuration` applet provide control over a server's Java servlets. Servlet Aliasing is the servlet equivalent of the File Aliasing section (just described). The Servlet Aliasing section allows mappings between URLs and servlet names. The Servlet Loading section sets mappings from servlet names to the Java class for the servlet. The location from which the servlet code is loaded (that is, from the local disk or from the network) and any parameters to be passed to the servlet can be defined here as well. ### MIME Section The MIME Section of the Jeeves `Configuration` applet controls the mappings between file extensions and MIME content types. These mappings let a browser know what type of resource it is retrieving so that it can handle it properly. For example, if you have several Microsoft Word documents, you can use this configuration to tell Jeeves to return a content type of `application/msword` for all files that have the file extension `.doc`. Assuming that the client's browser is properly configured, it automatically launches the application to view the file. ### Users, Groups, ACLs, and Resource Protection The Users, Groups, ACLs, and Resource Protection sections of the Jeeves `Configuration` applet allow you to control who can access resources on your server. Refer to the following section, "Access Control," for a detailed description of how Jeeves provides resource controls. ## Access Control Jeeves provides a very flexible system for controlling access to Web pages and servlets. Privileges can be granted to users (referred to as principals in this context), groups of users, or network hosts. Each user has an account name (which must be unique) and a password. The User Section of the Configuration applet allows you to create and delete user accounts as well as modify a user's password. Likewise, the Groups section allows the creation and deletion of groups of users. Individual user accounts can be assigned or removed from groups. Access Control Lists (ACLs) are the basis for resource controls with Jeeves. An ACL can be made up of any combination of users, groups, and network hosts. Membership in an ACL can be either positive (the entity is in the list) or negative (the entity is not in the list). In addition to keeping track of which entities are in the ACL, the ACL defines which HTTP request (that is, `GET` or `POST`) can be sent. Once you have created an ACL, the Resource Protection section of the `Configuration` applet enables you to assign lists to a particular URL. Figure 55.2 shows what the ACL Configuration screen looks like. Figure 55.2. The Access Control List Configuration section. The practical upshot of this arrangement is that Jeeves gives you very flexible control over deciding who can get what from your server. For example, you can create an ACL that contains the host names for each department's machines. Engineering documents can be specified as available to the developer ACL and the quality assurance ACL. This information is available only to those two departments--the marketing department's machines are not allowed to retrieve it. If the marketing people want to track who is accessing a particular resource, individual user accounts (or a group account) can be placed in an ACL and that ACL can be assigned to the URL in question. ## The Servlet API The API for servlets is similar to that for applets. Servlets must subclass the `java.servlet.GenericServlet` class and override the `service(ServletRequest, ServletResponse)` method. Servlets should extend one of the base servlet classes that implement the `Servlet` interface (such as `GenericServlet` or `HttpServlet`). Like the `java.applet.Applet` class, the `GenericServlet` class provides a method to retrieve parameters: `getInitParameter()`. The `getServletContext()` method obtains a `ServletContext` object, which provides references to other servlets; the method also provides a way to find out what server the servlet is running on. If a servlet needs special initialization, an `init()` method can be provided. This method is called when the servlet class is loaded. A servlet can contain a `getServletInfo()` method to return information about what the servlet does and its author. Jeeves displays this information in the `Configuration` applet. The most important method for a servlet is the `service()` method. This method is invoked by the server whenever a request is received. Each time a client requests a URL corresponding to a servlet, Jeeves calls the `service()` method with two parameters: an object implementing the `ServletRequest` interface and an object implementing the `ServletResponse` interface. The `ServletRequest` interface provides information similar to that passed to a CGI program in other servers. The interface defines methods to retrieve information such as the URL for the request, the remote host and port the request was received from, and any user authentication information. The `ServletResponse` interface contains methods that enable a servlet to communicate the results of a request back to the client that asked for it. A `getOutputStream()` method provides an `OutputStream` object that writes to the client. Several methods are provided to set HTTP information such as the response code, the MIME Content-Type of the reply, and the status message returned. The `GenericServlet` is intended to be used as a base class for servicing any generic request; the `java.servlet.http.HttpServlet`, on the other hand, is specifically intended for servicing requests received from the HTTP protocol. The `service()` method for an `HttpServlet` receives an `HttpServletRequest` and an `HttpServletResponse` as parameters. The HTTP-specific versions extend the `ServletRequest` and `ServletResponse` interfaces to provide information specific to an HTTP request, such as the URL requested and any HTTP headers. ## Sample Servlet: A Simple Phone Database We finish off this chapter with a sample servlet. This servlet reads a text file containing people's names, titles, and phone numbers and stores this information in a `java.util.Hashtable`. When a client requests the URL corresponding to the servlet, the servlet returns a form with a text field. The user can enter a name in the field and click the Submit button. The servlet searches its hash table and returns the corresponding phone number if it exists. First off are the `import` statements and class definition. Listing 55.1 defines the `Hashtable phoneList` and initializes it to `null`. NOTE: All the code in addition to a sample phone list for the servlet example in this chapter can be found on the CD that accompanies this book. #### Listing 55.1. The phoneSearch servlet.
    import java.io.*;
    import java.util.*;
    import java.servlet.*;
    import java.servlet.http.*;
    public
    class phoneSearch extends HttpServlet {
      // Hashtable to hold phone list information.  Loaded by init()
      Hashtable phoneList = null;
Next we define the `init()` method. As with an applet, this method is called automatically after the class is loaded. The `init()` code takes care of opening the phone database file and then calls the `readDatabase()` method to load the information into the hash table. It uses the `Servlet.log()` method to provide status updates that are written into the server's event log (see Listing 55.2). #### Listing 55.2. The phoneSearch.init() method.
    // Initialize servlet.  Reads in phone database from file.
      public void init( ) throws ServletException
      {
        FileInputStream infile = null; // For reading data file
        String phoneFile = null;       // Filename of database
        // Log when we start up.
        log( "phoneSearch Servlet Started." );
        // If a filename is given in our parameters use it
        if( (phoneFile = getInitParameter( "file" )) == null ) {
          phoneFile = "phone.txt";    // otherwise default to phone.txt
        }

        // Log what phone database file we're using
        log( "Using phone database file '" + phoneFile + "'." );
        // Try and open the phone database file
        try {
          infile = new FileInputStream( phoneFile );
        } catch( FileNotFoundException e ) {
          log( "Database file '" + phoneFile + "' does not exist." );
          log( "Error was: " + e.getMessage() );
          throw new ServletException( e.getMessage() );    // Rethrow exception
        } catch( IOException e ) {
          log( "I/O Error opening database file '" + phoneFile + "'." );
          log( "Error was: " + e.getMessage() );
          throw new ServletException( e.getMessage() );    // Rethrow exception
        }
        // Read the database into our hashtable
        try {
          readDatabase( new DataInputStream( infile ) );
        } catch( java.io.IOException e ) {
          throw new ServletException( "Error opening database file:"
                                      + e.getMessage() );
        }
        // Log that we're ready for business.
        log( "Read database.  phoneSearch ready." );
      }
The `readDatabase()` method takes a `DataInputStream` from which it reads the phone information (see Listing 55.3). Lines starting with a `#` character are ignored. Each line should have three fields: the person's name, title, and phone number. The format of each line is the three fields separated by pipe (`|`) characters. If the line does not contain a `|` character, we note the line number to the event log and go on to the next line of the file. Correctly formatted lines are split into two parts: the name field, and the title and phone fields. The title and phone string is inserted into the hash table with the name (converted to all lowercase) as the key. #### Listing 55.3. The phoneSearch.readDatabase() method.
    public void readDatabase( DataInputStream in )
        throws IOException
      {
        int pos, oldpos, lineNumber;
        String name, info;
        // Get an empty hashtable
        phoneList = new Hashtable( );
        lineNumber = 0;    // Initilaize line numbers
        try {
          try {
            // Read in first line
            String line = in.readLine( );
            // While there are lines to read . . .
            while( line != null ) {
              lineNumber++;    // Increment line count
              // Allow comment lines starting with an octothorpe
              if( line.charAt( 0 ) == '#' ) {
                line = in.readLine( ); // Read next line && loop
                continue;
              }
              // If line doesn't have a | character log it and go on
              if( (pos = line.indexOf( '|' )) < 0 ) {
                log( "Malformed phone database line at line " + lineNumber );
                line = in.readLine( ); // Read next line && loop
                continue;
              }
              // Copy name from line
              name = line.substring( 0, pos ).toLowerCase();
              // Leave title and # with | separator as one item
              info = line.substring( pos + 1, line.length() );
              // Place info into hashtable with name as key
              phoneList.put( name, info );
              line = in.readLine( ); // Read next line
            }
          } catch( EOFException e ) {
           ;
          }
        } catch( IOException e ) {
          log( "Error while reading database: " + e.getMessage( ) );
          throw e;
        }
        log( "phoneSearch read " + lineNumber + " lines." );
        log( "phoneSearch hashtable has " + phoneList.size() + " items." );
        return;    // Done reading database
      }
The `service()` method is the heart of any servlet. Whenever Jeeves receives a request for a URL that maps to a servlet, it calls that servlet's `service()` method. Two parameters are passed with this call: one representing the request (an object implementing the `ServletRequest` interface) and one representing the servlet's reply (an object implementing the `ServletResponse` interface). Because we are extending the `HttpServlet` class, the parameters are an `HttpServletRequest` and an `HttpServletResponse`. The servlet can use information from the `HttpServletRequest` to determine how it was called and who called it. The `HttpServletResponse` allows the servlet to set the HTTP headers for its reply, as well as provide an `OutputStream` on which to write the reply. #### Listing 55.4. The phoneSearch.service() method.
    public void service( HttpServletRequest req, HttpServletResponse res )
        throws ServletException, IOException
      {
        PrintWriter out = new PrintWriter( res.getOutputStream() );
        // Set our content type, that the output shouldn't be cached
        res.setContentType( "text/html" );
        res.setHeader( "Pragma", "no-cache" );
        // Write out HTML for our search form
        out.println( "<html>" );
        out.println( "<head><title>Phone List Example Servlet</title></head>" );
        out.println( "<body bgcolor=\"#ffffff\">" );
        // Start of form
        out.println( "<form method=\"GET\">" );
        out.println( "<h1>Phone List Servlet</h1><hr>" );
        // Print some instructions for our user
        out.println( "Enter the name of the person you want to search for." );
        out.println( "Names are recorded as all lowercase.  Search terms" );
        out.println( "are converted to all lowercase." );
        // Create a text field for search term
        out.print( "<p><input TYPE=\"text\" NAME=\"search\" VALUE=\"" );
        // See if we were given a query parameter (i.e. someone's called
        // us already).
        String search = req.getParameter( "search" );
        // If we have search will be non-null and we will use that
        // as the default value in our input box
        if( search != null )
          out.print( search );
        out.println( "\">" );    // Finish tag for search INPUT
        // Create a submit button
        out.println( "<input TYPE=\"submit\" NAME=\".submit\"><p><hr>" );
        // If we were given a search parameter and its length is non-zero
        if( search != null && search.length() != 0 ) {
          // Make the search item all lowercase
          search = search.toLowerCase();
          // See if search term is a key in hashtable
          String info = (String) phoneList.get( search );
          if( info != null ) {
            // Find separator in info
            int pos;
            pos = info.indexOf( '|' );
            // Format the data in a spiffy table
            out.println( "<table width=\"75%\" border=\"2\">" );
            out.println( "<tr><th>Name</th><th>Title</th><th>Phone</th></tr>" );
            // Print out a row with the data from the query and hashtable
            out.println( "<tr><td>" + search + "</td>" );
            out.println( "<td>" + info.substring( 0, pos ) + "</td>" );
            out.println( "<td>" + info.substring( pos + 1, info.length() ) + "</td>" );
            out.println( "</table>" ); // Mark the end of our table
          } else {
            // Search term wasn't in hashtable, so let them know that
            out.println( "No one by the name '" + search + "' was found." );
          }
        }
        // Close out form, body, and html tags
        out.println( "<p></form>" );
        out.println( "</body></html>" );
        out.flush();
        out.close();
        return;    // We're done
      }
Last are the `getServletInfo()` and `destroy()` methods (see Listing 55.5). The `getServletInfo()` method should return a `String` with information such as what the servlet does, who the author is, and information about the version. The `destroy()` method for this servlet just logs to the event log the fact that it was called. If you have a servlet that has, for example, connections to a database or information that has to be written out to disk, the `destroy()` method can handle those tasks. #### Listing 55.5. The phoneSearch.getServletInfo() and phoneServlet.destroy() methods.
    // Provide a little information about what servlet does
      public String getServletInfo( )
      {
        return "Simple Phone Database";
      }
      public void destroy( )
      {
        // Log when we're destroyed
        log( "phoneSearch Servlet destroy() called." );
      }
    }
### Using the phoneSearch Servlet The simplest way to use the servlet created in this chapter is to compile the code (`phoneSearch.java`, located on the CD-ROM that accompanies this book) and place the class file into the `servlet` directory under the main Jeeves directory. The phone database file should be placed in the Jeeves root directory and be named `phone.txt`. If you name the file differently or place it in another directory (in the `servlet` directory, for example), you must specify the location with the `file` parameter in the Servlet Loading section of the `Configuration` applet. Figure 55.3 shows the `phoneSearch` servlet in action. Figure 55.3. The `phoneSearch` servlet in action. ## Summary After reading this chapter, you should have an understanding of Jeeves's capabilities and what sets it apart from other HTTP servers. You should have an idea how to configure Jeeves to map URLs to different files or servlets, as well as how to write your own servlets to provide dynamic content.
| ©Copyright, Macmillan Computer Publishing. All rights reserved.