home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Book Home Java Servlet Programming Search this book

5.7. When Things Go Wrong

All right, let's face it. Sometimes things go wrong. Sometimes the dog bites, and sometimes the bee stings. There are any number of possible causes: bad parameters, missing resources, and (gasp!) actual bugs. The point here is that a servlet has to be prepared for problems, both expected and unexpected. There are two points of concern when things go wrong:

  • Limiting damage to the server

  • Properly informing the client

Because servlets are written in Java, the potential damage they can cause to their server is greatly minimized. A server can safely embed servlets (even within its process), just as a web browser can safely embed downloaded applets. This safety is built on Java's security features, including the use of protected memory, exception handling, and security managers. Java's memory protection guarantees that servlets cannot accidentally (or intentionally) access the server's internals. Java's exception handling lets a server catch every exception raised by a servlet. Even if a servlet accidentally divides by zero or calls a method on a null object, the server can continue to function. Java's security manager mechanism provides a way for servers to place untrusted servlets in a sandbox, limiting their abilities and keeping them from intentionally causing problems.

You should be aware that trusted servlets executing outside a security manager's sandbox are given abilities that could potentially cause damage to the server. For example, a servlet can overwrite the server's file space or even call System.exit(). It is also true that a trusted servlet should never cause damage except by accident, and it's hard to accidentally call System.exit(). Still, if it's a concern, even trusted servlets can be (and often are) run inside a fairly lenient but sanity-checking security manager.

Properly describing a problem to the client cannot be handled by Java language technology alone. There are many things to consider:

How much to tell the client?

Should the servlet send a generic status code error page, a prose explanation of the problem, or (in the case of a thrown exception) a detailed stack trace? What if the servlet is supposed to return nontextual content, such as an image?

How to record the problem?

Should it be saved to a file, written to the server log, sent to the client, or ignored?

How to recover?

Can the same servlet instance handle subsequent requests? Or is the servlet corrupted, meaning that it needs to be reloaded?

The answers to these questions depend on the servlet and its intended use, and they should be addressed for each servlet you write on a case-by-case basis. How you handle errors is up to you and should be based on the level of reliability and robustness required for your servlet. What we'll look at next is an overview of the servlet error-handling mechanisms that you can use to implement whatever policy you select.

5.7.1. Status Codes

The simplest (and arguably best) way for a servlet to report an error is to use the sendError() method to set the appropriate 400 series or 500 series status code. For example, when the servlet is asked to return a file that does not exist, it can return SC_NOT_FOUND. When it is asked to do something beyond its capabilities, it can return SC_NOT_IMPLEMENTED. And when the entirely unexpected happens, it can return SC_INTERNAL_SERVER_ERROR.

By using sendError() to set the status code, a servlet provides the server an opportunity to give the response special treatment. For example, some servers, such as the Java Web Server, replace the servlet's response body with a server-specific page that explains the error. If the error is such that a servlet ought to provide its own explanation to the client in the response body, it can set the status code with setStatus() and send the appropriate body--which could be text based, a generated image, or whatever is appropriate.

A servlet must be careful to catch and handle any errors before it sends any part of its response body. As you probably recall (because we've mentioned it several times), HTTP specifies that the status code and HTTP headers must be sent before the response body. Once you've sent even one character of a response body, it may be too late to change your status code or your HTTP headers. The easy way to guarantee you don't find yourself in this "too late" situation is to send your content all at once when the servlet is done processing, using an ByteArrayOutputStream buffer or HTML generation package, as shown earlier in this chapter.

5.7.2. Logging

Servlets have the ability to write their actions and their errors to a log file using the log() method:

public void ServletContext.log(String msg)
public void ServletContext.log(Exception e, String msg)

The single-argument method writes the given message to a servlet log, which is usually an event log file. The two-argument version writes the given message and exception's stack trace to a servlet log. Notice the nonstandard placement of the optional Exception parameter as the first parameter instead of the last for this method. For both methods, the output format and location of the log are server-specific.

The GenericServlet class also provides a log() method:

public void GenericServlet.log(String msg)

This is another version of the ServletContext method, moved to GenericServlet for convenience. This method allows a servlet to call simply:

log(msg); 

to write to the servlet log. Note, however, that GenericServlet does not provide the two-argument version of log(). The absence of this method is probably an oversight, to be rectified in a future release. For now, a servlet can perform the equivalent by calling:

getServletContext().log(e, msg);

The log() method aids debugging by providing a way to track a servlet's actions. It also offers a way to save a complete description of any errors encountered by the servlet. The description can be the same as the one given to the client, or it can be more exhaustive and detailed.

Now we can go back and improve ViewFile further, so that it uses log() to record on the server when requested files do not exist, while returning a simple "404 Not Found" page to the client:

// Return the file
try {
  ServletUtils.returnFile(file, out);
}
catch (FileNotFoundException e) {
  log("Could not find file: " + e.getMessage());
  res.sendError(res.SC_NOT_FOUND);
}

For more complicated errors, a servlet can log the complete stack trace, as shown here:

// Return the file
try {
  ServletUtils.returnFile(file, out);
}
catch (FileNotFoundException e) {
  log("Could not find file: " + e.getMessage());
  res.sendError(res.SC_NOT_FOUND);
}
catch (IOException e) {
  getServletContext().log(e, "Problem sending file");
  res.sendError(res.SC._INTERNAL_SERVER_ERROR);
}

5.7.3. Reporting

In addition to logging errors and exceptions for the server administrator, during development it's often convenient to print a full description of the problem along with a stack trace. Unfortunately, an exception cannot return its stack trace as a String--it can only print its stack trace to a PrintStream or PrintWriter. To retrieve a stack trace as a String, we have to jump through a few hoops. We need to let the Exception print to a special PrintWriter built around a ByteArrayOutputStream. That ByteArrayOutputStream can catch the output and convert it to a String. The com.oreilly.servlet.ServletUtils class has a getStackTraceAsString() method that does just this:

public static String getStackTraceAsString(Exception e) {
  ByteArrayOutputStream bytes = new ByteArrayOutputStream();
  PrintWriter writer = new PrintWriter(bytes, true);
  e.printStackTrace(writer);
  return bytes.toString();
}

Here's how ViewFile can provide information that includes an IOException stack trace:

// Return the file
try {
  ServletUtils.returnFile(file, out);
}
catch (FileNotFoundException e) {
  log("Could not find file: " + e.getMessage());
  res.sendError(res.SC_NOT_FOUND);
}
catch (IOException e) {
  getServletContext().log(e, "Problem sending file");
  res.sendError(res.SC_INTERNAL_SERVER_ERROR, 
                ServletUtils.getStackTraceAsString(e));
}

The output for a sample exception is shown in Figure 5-3.

figure

Figure 5-3. Keeping the client well informed

5.7.4. Exceptions

As we said before, any exception that is thrown but not caught by a servlet is caught by its server. How the server handles the exception is server-dependent: it may pass the client the message and the stack trace, or it may not. It may automatically log the exception, or it may not. It may even call destroy() on the servlet and reload it, or it may not.

Servlets designed and developed to run with a particular server can optimize for that server's behavior. A servlet designed to interoperate across several servers cannot expect any particular exception handling on the part of the server. If such a servlet requires special exception handling, it must catch its own exceptions and handle them accordingly.

There are some types of exceptions a servlet has no choice but to catch itself. A servlet can propagate to its server only those exceptions that subclass IOException, ServletException, or RuntimeException. The reason has to do with method signatures. The service() method of Servlet declares in its throws clause that it throws IOException and ServletException exceptions. For it (or the doGet() and doPost() methods it calls) to throw and not catch anything else causes a compile time error. The RuntimeException is a special case exception that never needs to be declared in a throws clause. A common example is a NullPointerException.

The init() and destroy() methods have their own signatures as well. The init() method declares that it throws only ServletException exceptions, and destroy() declares that it throws no exceptions.

ServletException is a subclass of java.lang.Exception that is specific to servlets--the class is defined in the javax.servlet package. This exception is thrown to indicate a general servlet problem. It has the same constructors as java.lang.Exception: one that takes no arguments and one that takes a single message string. Servers catching this exception may handle it any way they see fit.

The javax.servlet package defines one subclass of ServletException, UnavailableException, although you can, of course, add your own. This exception indicates a servlet is unavailable, either temporarily or permanently. Servers (services) that catch an UnavailableException are expected to behave as described in the Servlet API documentation:

Servlets may report this exception at any time, and the network service running the servlet should behave appropriately. There are two types of unavailability, and sophisticated services will deal with these differently:

Permanent unavailability. The servlet will not be able to handle client requests until some administrative action is taken to correct a servlet problem. For example, the servlet might be misconfigured, or the state of the servlet may be corrupted. Well written servlets will log both the error and the corrective action which an administrator must perform to let the servlet become available.

Temporary unavailability. The servlet cannot handle requests at this moment due to a system-wide problem. For example, a third-tier server might not be accessible, or there may be insufficient memory or disk storage to handle requests. The problem may be self-correcting, such as those due to excessive load, or corrective action may need to be taken by an administrator.

Network services may safely treat both types of exceptions as "permanent," but good treatment of temporary unavailability leads to more robust network services. Specifically, requests to the servlet might be blocked (or otherwise deferred) for a servlet-suggested amount of time, rather than being rejected until the service itself restarts.

UnavailableException has two constructors:

javax.servlet.UnavailableException(Servlet servlet, String msg)
javax.servlet.UnavailableException(int seconds, Servlet servlet, String msg)

The two-argument constructor creates a new exception that indicates the given servlet is permanently unavailable, with an explanation given by msg. The three-argument version creates a new exception that indicates the given servlet is temporarily unavailable, with an explanation given by msg. The duration of its unavailability is given by seconds. This time is only an estimate. If no estimate can be made, a nonpositive value may be used. Notice the nonstandard placement of the optional seconds parameter as the first parameter instead of the last. This may be changed in an upcoming release. UnavailableException provides the isPermanent(), getServlet(), and getUnavailableSeconds() methods to retrieve information about an exception.

5.7.5. Knowing When No One's Listening

Sometimes clients hang up on servlets. Sure, it's rude, but it happens. Sometimes the client makes a mistake and goes to the wrong page. Sometimes the servlet takes too long to respond. Remember, all the while a servlet is preparing its response, the user is being tempted by the browser's big, glowing Stop button that is just begging to be pushed. You may be wondering, just what happens to the servlet once that button is pushed?

Unfortunately, a servlet is not given any immediate indication that the user has pressed the Stop button--there is no interrupt that tells it to stop processing. The servlet discovers the client has stopped the request only when it tries to send output to the nonexistent client, at which point an error condition occurs.

A servlet that sends information using a ServletOutputStream sees an IOException when it tries to write output. For servers that buffer their output, the IOException is thrown when the buffer fills up and its contents are flushed.

Because an IOException may be thrown any time a servlet tries to output, a well-written servlet frees its resources in a finally block. (The finally block is an optional part of a try/catch/finally construct. It comes after zero or more catch blocks, and its code is executed once regardless of how the code in the try block executes.) Here's a version of the returnFile() method from the View-File servlet that uses a finally block to guarantee the closure of its FileInputStream:

void returnFile(String filename, OutputStream out)
                         throws FileNotFoundException, IOException {
  FileInputStream fis = null;
  try {
    fis = new FileInputStream(filename);
    byte[] buf = new byte[4 * 1024];  // 4K buffer
    int bytesRead;
    while ((bytesRead = fis.read(buf)) != -1) {
      out.write(buf, 0, bytesRead);
    }
  }
  finally {
    if (fis != null) fis.close();
  }
}

The addition of a finally block does not change the fact that this method propagates all exceptions to its caller, but it does guarantee that, before that propagation, the method gets a chance to close the open FileInputStream.

A servlet sending character data using a PrintWriter doesn't get an IOException when it tries to write output, because the methods of PrintWriter never throw exceptions. Instead, a servlet that sends character data has to call the checkError() method of PrintWriter. This method flushes the output and returns a boolean that indicates if there was a problem writing to the underlying OutputStream. It returns true if the client has stopped the request.

A long-running servlet should call checkError() regularly to determine if it can halt processing early. If there hasn't been any output since the last check, a servlet can send filler content. For example:

out.println("<H2>Here's the solution for your differential equation:</H2>"); if (out.checkError()) return;

// Preliminary calculation here
out.print(" "); // filler content, extra whitespace is ignored in HTML  
if (out.checkError()) return;
 
// Additional calculation here

It's important to note that a server is not required to throw an IOException or set the error flag of the PrinWriter after the client disconnects. A server may elect to let the response run to completion with its output ignored. Generally this does not cause a problem, but it does mean that a servlet running inside such a server should always have a set end point and should not be written to continuously loop until the user hits Stop.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.