19.7 Debugging

Java Servlet Programming, 2nd Edition > 19. Odds and Ends > 19.7 Debugging

 
< BACKCONTINUE >

19.7 Debugging

The testing/debugging phase can be 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.

19.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 zero bytes), so you can see problems as they occur. Be sure to reset the buffer size to a reasonable value afterward.

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

19.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, Tomcat is a pure Java web server perfect for debugging servlets. The only trick is that Tomcat must be started from within a debugger. The exact procedures vary depending on the version of Tomcat (or other Java-based web server) you're using, but the idea is always the same:

  1. Set your debugger's classpath so that it can find the classes and JARs needed to run Tomcat. You can look to the output emitted on startup and the server startup scripts (tomcat.sh and tomcat.bat) for help in determining this classpath.

  2. Set your debugger's classpath so that it can also find your servlets and support classes, typically the directory WEB-INF/classes and the files in WEB-INF/lib. You normally wouldn't want these directories and JARs in your classpath because that 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 responsible for loading classes from WEB-INF loads the servlet.

  3. Once you have set the proper classpath, start debugging the server by running the server class containing the primary main( ) method. For Tomcat 3.2 the class is org.apache.tomcat.startup.Tomcat. Other Java-based servers, and future versions of Tomcat, may use a different class. Look to the startup script for clues to the primary class name.

  4. Tomcat may complain about certain system properties or environment variables needing to be set. For example, Tomcat 3.2 looks to the system property tomcat.home or the environment variable TOMCAT_HOME to determine its base directory. Set these as necessary.

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

Many IDE debuggers hide these details and allow integrated servlet debugging using a built-in server to execute the servlets. It's often good to know the manual procedure, however, because the servers provided with IDE debuggers are usually a revision or two behind.

Some servlet container plug-ins (that normally work only in conjunction with a non-Java web server) have standalone pure Java versions created explicitly for use in debugging as described here. The advantage to using these servers, when they're available, is that you can easily move any custom web server configuration files from the production server to the test environment and back again.

19.7.4 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]

[2] Of course, if you're not familiar with the structure of HTTP, it may be you who is getting confused. In that case, we recommend reading the HTTP primer in Chapter 2, and the book HTTP Pocket Reference by Clinton Wong (O'Reilly).

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 19-8 shows such a server.

Example 19-8. 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();     }   } }

Start this server listening on port 8080 by typing the following command in a shell:

java SocketWatch 8080

Note that two applications can't listen to the same socket at the same time, so 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 is likely to be busy waiting for a response that will never come. End its wait by clicking 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/4.7 [en] (X11; U; IRIX 6.2 IP22) Pragma: no-cache Host: localhost:8080 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* Cookie: JSESSIONIN=To1010mC10934500694587412At

19.7.5 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: Tomcat Web Server/3.2 Content-Type: text/plain Connection: close Date: Sun, 25 Jun 2000 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 95/98/NT 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 19-9.

Example 19-9. 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 /index.html HTTP/1.0 HTTP/1.0 200 OK Content-Type: text/html Content-Length: 2582 Last-Modified: Fri, 15 Sep 2000 22:20:15 GMT Servlet-Engine: Tomcat Web Server/3.2 (JSP 1.1; Servlet 2.2; Java 1.2.2; Windows NT 4.0 x86; java.vendor=Sun Microsystems Inc.) <!doctype html public "-//w3c//dtd html 4.0 transitional//en"> <html> <head>   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">   <title>Tomcat v3.2</title> </head> ...

19.7.6 Use a Third-Party Tool

Third-party tools are bringing new capabilities and ease of use to the task of servlet debugging. IBM AlphaWorks produces a program called Distributed Application Tester (DAT) that snoops HTTP and HTTPS requests and responses, making it possible to view and record both sides of client/server traffic. DAT includes the ability to do functional tests and performance tests of your web application by autogenerating requests and scanning the responses. The program is pure Java but comes with an install tool that works only on Windows. Its only license is a free 90-day evaluation because the software is "alpha," and curiously has been since January 1999. DAT is available at http://www.alphaworks.ibm.com.

Allaire, maker of the popular JRun servlet plug-in (after their purchase of Live Software), has a little-known tool for servlet debugging named ServletDebugger. It's designed to help programmatically test and debug a servlet. ServletDebugger doesn't require using a web server 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 hidden deep on Allaire's price sheet at http://www.allaire.com.[3]

[3] We wouldn't be surprised if Allaire drops support for ServletDebugger in the not too distant future. If that happens, or maybe even if it doesn't happen, keep an eye out for an open source version.

19.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 classes found in the server's direct classpath (server_root/classes) probably don't reload, nor on most servers do nonservlet support classes in the web application classes directory (WEB-INF/classes). Normally only servlet classes under the web application classes directory will reload.

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

  • If you override the version of init( ) that takes a ServletConfig, verify that the overriding method calls super.init(config) right away.


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