Home | FAQ | Contact me

That web.xml Mess

When I first faced using web.xml, I was totally perplexed. I thought this file was nothing short of magic. Now, I'm not the brightest Crayola® in the box, and the relationships between the elements, with the servlet code, and to the browser address line took a very long time to sink in for me.

One of the ways of getting a web.xml right is to let Eclipse create it for you. To create the servlet code, don't merely create a new class with the name you've chosen and write your servlet class, use instead Eclipse's new Servlet wizard. It will

  1. Create a new class using the name you give it.
  2.  
  3. Precode the HttpServlet stubs for you.
  4.  
  5. Create and fill out WebContent/WEB-INF/web.xml for you.

This capability is offered by the Eclipse Web Tools Platform which is part of the Eclipse IDE for Java EE Developers package.

Here is a URL or address line used in a browser when hitting a server I wrote using Eclipse. I'm using color to help show which part of the line corresponds to elements in web.xml.

http://localhost:8080/FunServer/FunServlet?user=russ&password=russ&id=8192

Please note the protocol designator, http, the hostname, here localhost because we're running locally to test our server, and Tomcat's port number, 8080. This latter you can change in a file named server.xml inside your Tomcat installation if you prefer a different one (which would happen if you were running more than one Tomcat, or your IS folk were not letting port 8080 traffic through, etc.).

This is all we'll say about these three elements since this discussion really isn't about them except to point out that you can change the port of the Tomcat server you're using from Eclipse by double-clicking the server name, something like Tomcat v6.0 Server at localhost in the Servers tab in the Eclipse workbench. (This is usually in the bottom-center pane of workbench views.)

Here's a configuration file for one servlet. Please draw a correspondence between the colors of the URL above and the contents below.

web.xml:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>FunServer</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <servlet> <servlet-name>FunServlet</servlet-name> <servlet-class>com.etretatlogiciels.fun.server.FunServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>FunServlet</servlet-name> <url-pattern>/FunServlet</url-pattern> </servlet-mapping> </web-app>

Here are some relevant details. You could go over these to make certain your configuration file is correct when you're having trouble getting your servlet working.

An enhanced example

In the world of JavaServer Faces, Facelets and REST servlets, it's a common practice to include and additional element in the URI path:

http://localhost:8080/FunServer/faces/FunServlet?user=russ&password=russ&id=8192 http://localhost:8080/FunServer/rest/FunServlet?user=russ&password=russ&id=8192

That extra path element translates to a url-pattern in the servlet-mapping construct in web.xml:

<servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping>

...or...

... <servlet-name>Jersey REST Service</servlet-name> <url-pattern>/rest/*</url-pattern> ...

More on this next...

Servlet mappings

Servlets are registered with the container; this is done via web.xml.

When a servlet is defined, it's given a name and a supplying class. In this case, what comes out of javax.faces.webapp.FacesServlet is given the name Faces Servlet, which is used as the name in all servlet mapping references.

You can name the servlet anything you want, but unless every significant (read: consumed) occurrence of it is identical to what you used for <servlet><servlet-name>name</servlet-name> ... </servlet>, Tomcat will fail to start up or, at least, execution will fail.

A servlet mapping specifies the web container for which the servlet should be invoked for a URI given by the client. It maps (the end of) URI patterns to servlets. In this way, when there's a request from a client to the container, the latter decides which (if there is more than one) application will handle the request. Of course, the pattern given could fail to match any pattern and no servlet will be passed the request. When this happens, an HTTP Status 404 is issued.

Mapping order

The first successful match fires and no further attempt is made. So, it follows that a strict order must be made that will allow this.

Explicit (path) mappings "/name/*"
Implicit (extension) mappings "*.suffix"
Default mappings exactly "/"

In web.xml example below, the *.jsf, *.xhmtl and *.faces are unnecessary for running guessnumber.xhtml.

<servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.jsf</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.xhtml</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.faces</url-pattern> </servlet-mapping>

Tomcat deploy

Developing your application, you'll be using a server container such as Tomcat, Glassfish, JBoss, WebSphere, etc. Here we discuss Tomcat.

Tomcat, and other server containers, are integrated into Eclipse such that you can easily run your application and use the Eclipse debugger to perfect it. For more on setting up Tomcat in Eclipse, see here.

Once you've finished developing your application and are ready to deploy it to Tomcat, you right-click on the project and choose Export -> Web -> WAR File. There you get a dialog asking for a Web project: name. The name defaults to your Eclipse project name, what's also filled in for you in display-name name in web.xml, but you can choose differently. What you do choose, however, affects the URI path element used to reach it as discussed above.

Incidentally, all you must do to deploy your application, depending on the version of Tomcat, is drop the WAR file into Tomcat's webapps subdirectory. Sometimes, depending on the version, I've had to restart Tomcat to get it to see the file. Once it does, it explodes it (a WAR file is a fancy Java archive or JAR file) and deploys it into service.

$ /etc/init.d/tomcat restart

Conclusion

So, what else can we do with this?

Let's say you thought /FunServlet was a pretty stupid thing to let show on the address line. I mean, it's the Java file (and class) name of your servlet. It's possible to change the url-pattern so that doesn't show. The display-name (or application name) is also arbitrary as already discussed.

What causes these names to work? It's the magic of Apache Tomcat which uses this configuration file as a map to your application. It lives (and often dies) by this information.

What is crucially essential? What you simply cannot botch are:

  1. The servlet-class, the package path in which your servlet code resides. Without this, there is no way Tomcat could ever guess where to go to find the servlet you're running.
  2.  
  3. The servlet-name between servlet and servlet-mapping. This may seem a little silly, but the way it's done enables you to field multiple servlets in your one web/server application. A servlet and servlet-mapping pair for each servlet you field.*
  4.  
  5. As already noted, the display-name and url-pattern must be observed by you and any wishing to reach out and touch your servlet.

* I haven't tried doing it with a simple HTTP servlet, but I have done one following Jersey JAXB technology. In that case, you cannot implement more than one servlet per Java package name. I tried it; it wouldn't work.


Appendix: a servlet code sample and other fun

In the prototype servlet code below, only method doGet() has been implemented. (No, this isn't a very real implementation, but it serves the purpose.)

FunServlet.java:
package com.etretatlogiciels.fun.server;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.etretatlogiciels.fun.model.Game;
import com.etretatlogiciels.fun.model.Scoreboard;

/**
 * Implements the Fun server, a RESTful service, as an HTTP servlet. We could use Jersey JAXB,
 * but we're trying to show basic servlet programming here.
 *
 * @author Russell Bateman
 */
public class FunServlet extends HttpServlet
{
   private static final long   serialVersionUID = 1L;
   private static final String ERR_PREFIX       = "Fun! server: ";

   /**
    * @see HttpServlet#HttpServlet()
    */
    public FunServlet()
    {
        super();
    }

   /**
    * Basic GET operation: accept request from user who requests to have fun by game id.
    *
    * http://localhost:8080/FunServer/FunServlet?user=username&password=password&gameid=id
    *
    * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
    */
   protected void doGet( HttpServletRequest request, HttpServletResponse response )
      throws ServletException, IOException
   {
      String username = request.getParameter( "user" );
      String password = request.getParameter( "password" );
      String gameid   = request.getParameter( "gameid" );

      checkParametersExist( username, password, gameid );
      authenticateUser( username, password );
      .
      .
      .
   }

   /**
    * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
    */
   protected void doPost( HttpServletRequest request, HttpServletResponse response )
      throws ServletException, IOException
   {
   }

   /**
    * @see HttpServlet#doPut(HttpServletRequest request, HttpServletResponse response)
    */
   protected void doPut( HttpServletRequest request, HttpServletResponse response )
      throws ServletException, IOException
   {
   }

   /**
    * @see HttpServlet#doDelete(HttpServletRequest request, HttpServletResponse response)
    */
   protected void doDelete( HttpServletRequest request, HttpServletResponse response )
      throws ServletException, IOException
   {
   }

   /**
    * @see HttpServlet#doHead(HttpServletRequest request, HttpServletResponse response)
    */
   protected void doHead( HttpServletRequest request, HttpServletResponse response )
      throws ServletException, IOException
   {
   }

   /**
    * Call this with the parameters, strings, etc. that must not be null or then
    * we suspect a bad request or missing parameters.
    *
    * @param args - list of String that must not be nil.
    * @throws ServletException
    */
   private void checkParametersExist( String... args ) throws ServletException
   {
      for( String string : args )
      {
         if( string == null )
            throw new ServletException( ERR_PREFIX
                                      + "Ill-formed request or missing parameter(s)" );
      }
   }
}