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

Book Home Java Servlet Programming Search this book

13.7. Debugging

The testing/debugging phase is one of the hardest aspects of developing servlets. Servlets tend to involve a large amount of client/server interaction, making errors likely--but hard to reproduce. It can also be hard to track down the cause of nonobvious errors because servlets don't work well with standard debuggers, since they run inside a heavily multithreaded and generally complex web server. Here are a few hints and suggestions that may aid you in your debugging.

13.7.1. Check the Logs

When you first think there might be a problem, check the logs. Most servers output an error log where you can find a list of all the errors observed by the server and an event log where you can find a list of interesting servlet events. The event log may also hold the messages logged by servlets through the log() method, but not always.

Note that many servers buffer their output to these logs to improve performance. When hunting down a problem, you may want to stop this buffering (usually by reducing the server's buffer size to 0 bytes), so you can see problems as they occur. Be sure to reset the buffer size to a reasonable value afterward.

13.7.2. Output Extra Information

If you don't see an indication of the problem in the server's logs, try having your servlet log extra information with the log()method. As you've seen in examples elsewhere in this book, we habitually log stack traces and other error situations. During debugging, you can add a few temporary log() commands as a poor man's debugger, to get a general idea of the code execution path and the values of the servlet's variables. Sometimes it's convenient to leave the log() commands in a servlet surrounded by if clauses so they trigger only when a specific debug init parameter is set to true.

Extracting the extra information from the server's logs can at times be unwieldy. To make the temporary debugging information easier to find, you can have a servlet output its debug information to the client (through the PrintWriter) or to a console on the server (through System.out). Not all servers have a console associated with a servlet's System.out; some redirect the output to a file instead.

13.7.3. Use a Standard Debugger

It's also possible to use a standard debugger to track down servlet problems, although exactly how might not be intuitively obvious. After all, you can't debug a servlet directly because servlets aren't standalone programs. Servlets are server extensions, and, as such, they need to run inside a server.

Fortunately, Sun provides a simple "servlet runner" server perfect for debugging servlets. This servlet runner acts as a small all-Java web server that is simpler and more lightweight than the Java Web Server--it handles only HTTP requests for servlets, and it doesn't even serve files. You'll find the servlet runner packaged as part of the Java Servlet Development Kit (JSDK), available from http://jserv.java.sun.com.

The servlet runner can be executed from the command line as the servletrunner shell script on a Unix system or the servletrunner.exe program on Windows. What servletrunner does is set the classpath to include the appropriate classes and then execute java sun.servlet.http.HttpServer. The HttpServer class contains the main() method that listens for incoming requests for servlets. By default, it listens on port 8080.

To debug a servlet, we can debug sun.servlet.http.HttpServer, then watch as HttpServer executes servlets in response to HTTP requests we make from a browser. This is very similar to how applets are debugged. The difference is that with applets, the actual program being debugged is sun.applet.AppletViewer. Most debuggers hide this detail by automatically knowing how to debug applets. Until they do the same for servlets, you have to help your debugger by doing the following:

  1. Set your debugger's classpath so that it can find sun.servlet.http.Http-Server and associated classes.

  2. Set your debugger's classpath so that it can also find your servlets and support classes, typically server_root/servlets and server_root/classes. You normally wouldn't want server_root/servlets in your classpath because it disables servlet reloading. This inclusion, however, is useful for debugging. It allows your debugger to set breakpoints in a servlet before the custom servlet loader in HttpServer loads the servlet.

Once you have set the proper classpath, start debugging sun.servlet.http.HttpServer. You can set breakpoints in whatever servlet you're interested in debugging, then use a web browser to make a request to the HttpServer for the given servlet (http://localhost:8080/servlet/ServletToDebug). You should see execution stop at your breakpoints.

13.7.4. Use a Third-Party Tool

Third-party tools promise to bring new capabilities and ease of use to the task of servlet debugging. LiveSoftware, maker of the popular JRun servlet plug-in, was the first company to market a tool for servlet debugging. The product, named ServletDebugger, is designed to help programmatically test and debug a servlet. ServletDebugger doesn't require using HttpServer or a browser to make a request. Instead, you use a set of classes to write a small stub class that prepares and executes a servlet request. The stub specifies everything: the servlet's init parameters, the request's HTTP headers, and the request's parameters. ServletDebugger is fairly straightforward and is well suited to automated testing. The largest drawback is that it takes extra effort to properly prepare a realistic request. ServletDebugger is available from http://www.livesoftware.com.

You can expect additional third-party debugging tools to become available as servlets become more popular.

13.7.5. Examine the Client Request

Sometimes when a servlet doesn't behave as expected, it's useful to look at the raw HTTP request to which it's responding. If you're familiar with the structure of HTTP, you can read the request and see exactly where a servlet might get confused.[2] One way to see the raw request is to replace the web server process with a custom server application that prints out everything it receives. Example 13-9 shows such a server.

[2] Of course, if you're not familiar with the structure of HTTP, it may be you who are getting confused. In that case, we recommend reading the HTTP primer in Chapter 2, "HTTP Servlet Basics" and the book Web Client Programmingby Clinton Wong (O'Reilly).

Example 13-9. Catching a client request

import java.io.*;
import java.net.*;
import java.util.*;

public class SocketWatch {

  private static void printUsage() {
    System.out.println("usage: java SocketWatch port");
  }

  public static void main(String[] args) {
    if (args.length < 1) {
      printUsage();
      return;
    }

    // The first argument is the port to listen on
    int port;
    try {
      port = Integer.parseInt(args[0]);
    }
    catch (NumberFormatException e) {
      printUsage();
      return;
    }

    try {
      // Establish a server socket to accept client connections
      // As each connection comes in, pass it to a handler thread
      ServerSocket ss = new ServerSocket(port);
      while (true) {
        Socket request = ss.accept();
        new HandlerThread(request).start();
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

class HandlerThread extends Thread {

  Socket s;

  public HandlerThread(Socket s) {
    this.s = s;
  }

  public void run() {
    try {
      // Print each byte as it comes in from the socket
      InputStream in = s.getInputStream();
      byte[] bytes = new byte[1];
      while ((in.read(bytes)) != -1) {
        System.out.print((char)bytes[0]);
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

You can start this server listening on port 8080 by typing java SocketWatch 8080 in a shell. Note that two applications can't listen to the same socket at the same time, so you should first make sure there's no other server listening on your chosen port. Once you have the server running, you can make HTTP requests to it as if it were a normal web server. For example, you can use a web browser to surf to http://localhost:8080. When SocketWatch receives the browser's HTTP request, it sends the request to its standard out for your examination. The browser, meanwhile, is likely to be busy waiting for a response that will never come. You can end its wait by hitting the Stop button.

Here is some sample output from SocketWatch that shows the details of a GET request made to http://localhost:8080:

GET / HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/3.03C-SGI (X11; I; IRIX 6.2 IP22)
Pragma: no-cache
Host: localhost:8080
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Cookie: jwssessionid=USN1RLIAAAAABQDGPM5QAAA

13.7.6. Create a Custom Client Request

In addition to catching and examining a client's HTTP request, you may find it useful to create your own HTTP request. You can do this by connecting to the server socket on which the web server is listening, then manually entering a properly structured HTTP request. To establish the connection, you can use the telnet program, available on all Unix machines and most Windows machines with networking. The telnet program accepts as arguments the host and port number to which it should connect. Once you're connected, you can make a request that looks like what you saw in the last section. Fortunately, your request can be far simpler--all you need to specify is the first line, saying what to get, and the last line, which must be an empty line that indicates the end of the request. For example:

% telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /servlet/ParameterSnoop?name=value HTTP/1.0

HTTP/1.1 200 OK
Server: JavaWebServer/1.1.1
Content-Type: text/plain
Connection: close
Date: Sun, 12 Apr 1998 20:29:06 GMT

Query String:
name=value

Request Parameters:
name (0): value
Connection closed by foreign host.

As is too often the case, Windows behaves a little differently than you'd like. The default Windows telnet.exe program misformats many web server responses because it doesn't understand that on the Web, a line feed should be treated the same as a line feed and carriage return. In lieu of telnet.exe, Windows programmers can use the better-behaved Java program shown in Example 13-10.

Example 13-10. Another way to connect to a web server

import java.io.*;
import java.net.*;
import java.util.*;

public class HttpClient {

  private static void printUsage() {
    System.out.println("usage: java HttpClient host port");
  }

  public static void main(String[] args) {
    if (args.length < 2) {
      printUsage();
      return;
    }

    // Host is the first parameter, port is the second
    String host = args[0];
    int port;
    try {
      port = Integer.parseInt(args[1]);
    }
    catch (NumberFormatException e) {
      printUsage();
      return;
    }

    try {
      // Open a socket to the server
      Socket s = new Socket(host, port);

      // Start a thread to send keyboard input to the server
      new KeyboardInputManager(System.in, s).start();

      // Now print everything we receive from the socket 
      BufferedReader in = 
        new BufferedReader(new InputStreamReader(s.getInputStream()));
      String line;
      while ((line = in.readLine()) != null) {
        System.out.println(line);
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

class KeyboardInputManager extends Thread {

  InputStream in;
  Socket s;

  public KeyboardInputManager(InputStream in, Socket s) {
    this.in = in;
    this.s = s;
    setPriority(MIN_PRIORITY);  // socket reads should have a higher priority
                                // Wish I could use a select() !
    setDaemon(true);  // let the app die even when this thread is running
  }

  public void run() {
    try {
      BufferedReader keyb = new BufferedReader(new InputStreamReader(in));
      PrintWriter server = new PrintWriter(s.getOutputStream());

      String line;
      System.out.println("Connected... Type your manual HTTP request");
      System.out.println("------------------------------------------");
      while ((line = keyb.readLine()) != null) {
        server.print(line);
        server.print("\r\n");  // HTTP lines end with \r\n
        server.flush();
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

This HttpClient program operates similarly to telnet:

% java HttpClient localhost 8080
Connected... Type your manual HTTP request
------------------------------------------
GET / HTTP/1.0

HTTP/1.1 200 OK
Server: JavaWebServer/1.1.1
Content-Length: 1999
Content-Type: text/html
Last-Modified: Mon, 12 Jan 1998 08:26:20 GMT
Date: Wed, 08 Apr 1998 23:41:50 GMT

<html>
  <head>
    <title>Java(TM) Web Server Default Page</title>
  </head>
  ...

13.7.7. Some Final Tips

If all the advice so far hasn't helped track down your bug, here are some final tips on servlet debugging:

  • Use System.getProperty("java.class.path") from your servlet to help debug classpath problems. Because servlets are often run from web servers with embedded JVMs, it can be hard at times to identify exactly what classpath the JVM is searching. The property "java.class.path" will tell you.

  • Be aware that server_root/classes doesn't reload and that server_root/servlets probably does.

  • Ask a browser to show the raw content of the page it is displaying. This can help identify formatting problems. It's usually an option under the View menu.

  • Make sure the browser isn't caching a previous request's output by forcing a full reload of the page. With Netscape Navigator, use Shift-Reload; with Internet Explorer use Shift-Refresh.

  • Verify that your servlet's init() method takes a ServletConfig parameter and calls super.init(config) right away.



Library Navigation Links

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







??????????????@Mail.ru