5.7 When Things Go Wrong

Java Servlet Programming, 2nd Edition > 5. Sending HTML Information > 5.7 When Things Go Wrong

 
< BACKCONTINUE >

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, the server can 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 the response is committed. 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 have sent any data down the wire to the client, it's too late to change your status code or your HTTP headers. The best way to guarantee you don't find yourself in this "too late" situation is to check for errors early and use buffering to postpone the sending of data to the client.

5.7.2 Configuring Error Pages

Sometimes, instead of using the server-standard error pages, you may want to develop a set of standard error pages for a web application, pages to be used no matter where the application is deployed. For example, a web application could be configured with a 404 error page containing a search engine entry field should the user want help locating the Not Found resource. You could accomplish this directly by using setStatus( ) and having every servlet generate an identical error page, but a better approach is to set up an <error-page> rule in the web.xml deployment descriptor. Example 5-8 demonstrates.

Example 5-8. Configuring 400 and 404 Error Pages
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app     PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"     "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd"> <web-app>     <!-- ..... -->     <error-page>         <error-code>             400         </error-code>         <location>             /400.html         </location>     </error-page>     <error-page>         <error-code>             404         </error-code>         <location>             /404.html         </location>     </error-page> </web-app>

These two <error-page> entries tell the server that any call to sendError( ) with a 400 status code should display the contents of the /400.html resource, and any 404 should display /404.html. It's common for web servers to support error page customization similar to this, however entries in the web.xml file override the default server configuration and provide a mechanism for a web application to mandate a set of standard error pages to be used across all server implementations. Servers are required to respect the <error-page> rules for all content served from the web application, even static files.

Some things to remember: the value of the <location> must begin with a slash, is treated as based in the context root, and must refer to a resource within the context. To reference a resource outside the current context, you can point to an HTML file within the context that contains an immediate redirect outside the context:

<META HTTP-EQUIV="Refresh" CONTENT="0; URL=http://www.errors.com/404.html">

The <location> target can be a dynamic resource, such as a servlet or JSP. For dynamic resources the server makes available two special request attributes telling information about the error:

javax.servlet.error.status_code

An Integer telling the error status code. The type was initially unspecified and some early server implementations may return the code as a String.

javax.servlet.error.message

A String telling the status message, generally passed as the second argument to sendError( ).

These attributes allow us to write a general-purpose error page display servlet, as is shown in Example 5-9. In order to use this servlet, assign it as the path for the <location> tag.

Example 5-9. Dynamically Creating a Status Code Error Page
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ErrorDisplay extends HttpServlet {   public void doGet(HttpServletRequest req, HttpServletResponse res)                                throws ServletException, IOException {     res.setContentType("text/html");     PrintWriter out = res.getWriter();     Object codeObj = req.getAttribute("javax.servlet.error.status_code");     Object messageObj = req.getAttribute("javax.servlet.error.message");     // The code and message should never be null on API 2.2 compliant servers     String code = (codeObj != null ?                     codeObj.toString() : "Missing Status Code");     String message = (messageObj != null ?                       messageObj.toString() : "Missing Error Message");     out.println("<HTML>");     out.println("<HEAD><TITLE>" + code + ": " + message + "</TITLE></HEAD>");     out.println("<BODY>");     out.println("<H1>" + code + "</H1>");     out.println("<H2>" + message + "</H2>");     out.println("<HR>");     out.println("<I>Error accessing " + req.getRequestURI() + "</I>");     out.println("</BODY></HTML>");   } }

A path to this servlet can be configured as the target for any error codes that need a simple error page rendering. An advanced version of this servlet could generate a more interesting page and could track the error codes being generated to detect odd trends, such as an abundance of 404 errors for the same URI, probably indicating a newly posted bad link somewhere that should be fixed.

To summarize, here's the cheat sheet for sending error status codes. The setStatus( ) method allows you to set an error status code and retain full control of the response. The sendError( ) method lets you set an error status code and pass control of page creation to the server. By default the server will send its standard error page for that code. With an <error-page> entry you can tell the server to send a special error page.

5.7.3 Logging

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

public void GenericServlet.log(String msg) public void GenericServlet.log(String msg, Throwable t)

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 the Throwable's stack trace to a servlet log. The exact output format and location of the log are server-specific but generally include a timestamp and the registered name of the servlet.

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) {   log("Problem sending file", e);   res.sendError(res.SC_INTERNAL_SERVER_ERROR); }

5.7.4 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 print its stack trace only 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(Throwable t) {   ByteArrayOutputStream bytes = new ByteArrayOutputStream();   PrintWriter writer = new PrintWriter(bytes, true);   t.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) {   log("Problem sending file", e);   res.sendError(res.SC_INTERNAL_SERVER_ERROR,                 ServletUtils.getStackTraceAsString(e)); }

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

Figure 5-2. Keeping the client well informed

5.7.5 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.

5.7.5.1 ServletException

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 by a servlet 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:

javax.servlet.ServletException() javax.servlet.ServletException(String msg)

ServletException can also support a "root cause" Throwable object. This lets ServletException act as a wrapper around any type of exception or error, giving the server a way to know what "root" problem caused the ServletException to be thrown. To support this, ServletException has two additional constructors:

javax.servlet.ServletException(Throwable rootCause) javax.servlet.ServletException(String msg, Throwable rootCause)

Using the root cause capability you can pass on any underlying exception or error:

try {   thread.sleep(60000); } catch (InterruptedException e) {   throw new ServletException(e);  // includes full underlying exception }

The server can retrieve and examine the underlying exception, stack trace and all, by calling getRootCause( ) :

public Throwable ServletException.getRootCause()

The call returns null if there is no nested exception.

5.7.5.2 UnavailableException

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.

Permanent unavailability means that the servlet instance throwing the UnavailableException cannot recover from the error. The servlet might be misconfigured, or the state of the servlet may be corrupted.

A servlet that throws a permanent UnavailableException during request handling will be removed from service, and a new instance will be created to handle requests. If no "available" servlet instance can be created, the client will receive an error. A servlet that throws a permanent UnavailableException (or a regular ServletException) during its init( ) method will never enter into service; instead, the server will attempt to initialize a new instance to handle future requests.

Temporary unavailability means the servlet cannot handle requests for some duration due to a systemwide 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 an administrator may need to take corrective action.

During the unavailability, the server handles requests for the servlet by returning an SC_SERVICE_UNAVAILABLE (503) status code with a Retry-After header telling the client the estimated end time of the unavailability. If a servlet throws a temporary UnavailableException exception during its init( ) method, the servlet will never enter into service, and the server will attempt to initialize a new instance after the period of unavailability. For simplicity's sake, servers are allowed to treat temporary unavailability like permanent unavailability.

UnavailableException has two constructors:

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

The one-argument constructor creates a new exception that indicates the servlet is permanently unavailable, with an explanation given by msg. The two-argument version creates a new exception that indicates the servlet is temporarily unavailable, with an explanation given by msg. A well-written servlet should include in the explanation the reason for the problem and any corrective action the server administrator should perform to let the servlet become available. 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. UnavailableException provides the isPermanent( ) and getUnavailableSeconds( ) methods to retrieve information about an exception.

5.7.6 Configuring Exception Pages

The full behavior when a server catches a servlet exception varies based on the server, however a web application can indicate, via its deployment descriptor, a set of error pages for handling particular kinds of exceptions. The error pages are specified using the <error-page> tag, just as with status codes, except we replace <error-code> with <exception-type>. See Example 5-10.

Example 5-10. Configuring Exception Error Pages
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app     PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"     "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd"> <web-app>     <!-- ..... -->     <error-page>         <exception-type>             javax.servlet.ServletException         </exception-type>         <location>             /servlet/ErrorDisplay         </location>     </error-page> </web-app>

This entry indicates that any ServletException thrown to the server should be displayed using the ErrorDisplay servlet. Note that the <exception-type> must be fully qualified with the package name; ServletException alone will not work. Also, this rule applies to all ServletException subclasses like UnavailableException, unless there's a more specific UnavilableException-handling rule in the deployment descriptor that takes precedence.

For dynamic <location> targets, the server makes available two request attributes to describe the thrown exception:

javax.servlet.error.exception_type

A java.lang.Class instance telling the exception type. The attribute type was initially unspecified and some early server implementations may return a String form of the class name.

javax.servlet.error.message

A String telling the exception message, passed to the exception constructor. There exists no way to get the exception or its stack trace.

Using these attributes we can enhance ErrorDisplay from Example 5-9 to act as a general error display resource, supporting the display of both error status codes and exceptions, as shown in Example 5-11.

Example 5-11. Dynamically Creating a General-Purpose Error Page
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ErrorDisplay extends HttpServlet {   public void doGet(HttpServletRequest req, HttpServletResponse res)                                 throws ServletException, IOException {     res.setContentType("text/html");     PrintWriter out = res.getWriter();     String code = null, message = null, type = null;     Object codeObj, messageObj, typeObj;     // Retrieve the three possible error attributes, some may be null     codeObj = req.getAttribute("javax.servlet.error.status_code");     messageObj = req.getAttribute("javax.servlet.error.message");     typeObj = req.getAttribute("javax.servlet.error.exception_type");     // Convert the attributes to string values     if (codeObj != null) code = codeObj.toString();     if (messageObj != null) message = messageObj.toString();     if (typeObj != null) type = typeObj.toString();     // The error reason is either the status code or exception type     String reason = (code != null ? code : type);     out.println("<HTML>");     out.println("<HEAD><TITLE>" + reason + ": " + message + "</TITLE></HEAD>");     out.println("<BODY>");     out.println("<H1>" + reason + "</H1>");     out.println("<H2>" + message + "</H2>");     out.println("<HR>");     out.println("<I>Error accessing " + req.getRequestURI() + "</I>");     out.println("</BODY></HTML>");   } }

Unfortunately, only the exception type and message not the exception's stack trace are available to the ErrorDisplay servlet, limiting the usefulness of the exception error page. To guarantee display or logging of the exception stack trace, you must catch the exception within the original servlet and deal with the exception before it propagates to the server. You'll have to write a little extra code, but by handling exceptions yourself you can guarantee consistent and proper error handling. It's expected a new request attribute containing the exception itself will be added in Servlet API 2.3 using the name javax.servlet.error.exception.

5.7.6.1 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 clicked. 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 clicked 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 ViewFile 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 committing the response 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 that doesn't mind committing the response early should call checkError( ) regularly to determine if it can halt processing before completion. 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; preliminaryCalculation(); out.print(" "); // filler content, extra whitespace is ignored in HTML if (out.checkError()) return; additionalCalculation();

It's important to note that a server is not required to throw an IOException or set the error flag of the PrintWriter 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.


Last updated on 3/20/2003
Java Servlet Programming, 2nd Edition, © 2001 O'Reilly

< BACKCONTINUE >


Java servlet programming
Java Servlet Programming (Java Series)
ISBN: 0596000405
EAN: 2147483647
Year: 2000
Pages: 223

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net