Web Archive - WAR

The WAR format specifies the standard directory structure and files that make up a web application understood by a servlet container. The directory structure can be archived using zip to create a WAR file. The WAR directory structure contains:

WAR file example

A demonstration of the WAR directory structure using the weather description servlet and the request servlet combined with the index.html file is:

The demo directory is contained in a directory called webapp. The webapp directory contains the source for the servelet container and the servlets.

index.html

index.html
<html>
<head>
</head>
<body>

<h2>Request tests</h2>

<ul>
    <li>
        <a href="servlet/request">request</a>
    </li>
    <li>
        <a href="servlet/request/one/two/three">request/one/two/three</a>
    </li>
    <li>
        <a href="servlet/request/one?a=1&b=2">request/one?a=1&amp;b=2</a>
    </li>
    <li>
        <a href="servlet/request/one?x=foo;y=bar">request/one?x=foo;y=bar</a>
    </li>
    <li>
        <form method="get" action="servlet/request">
        <label for="firstname">First name: </label>
        <input type="text" size="6" name="firstname" value="abc">
        <label for="lastname">Last name: </label>
        <input type="text" size="12" name="lastname" value="def">
        <input type="submit" value="Send">
        </form>
    </li>
    <li>
        <form method="post" action="servlet/request">
        <label for="firstname">First name: </label>
        <input type="text" size="6" name="firstname" value="first">
        <label for="lastname">Last name: </label>
        <input type="text" size="12" name="lastname" value="last">
        <input type="submit" value="Send">
        </form>
    </li>
</ul>

<h2> Weather </h2>
<p>
    <a href="servlet/weather/list">list weather</a>
</p>

<form method="post" action="servlet/weather/update">
    <label for="location">Location: </label>
    <input type="text" size="20" name="location" value="St. John's">
    <br>
    <label for="temp">Current temperature: </label>
    <input type="text" size="6" name="temp" value="15.0">
    <br>
    <label for="synopsis">Synopsis: </label>
    <input type="text" size="30" name="synopsis" value="--">
    <br>
    <input type="submit" value="Send">
</form>

</body>
</html>

web.xml

The web.xml specifies how URLs are decoded to activate the specified servlets.

web.xml
<?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>

  <servlet>
      <servlet-name>http_request</servlet-name>
      <servlet-class>HttpRequestServlet</servlet-class>
  </servlet>
  <servlet>
      <servlet-name>weather</servlet-name>
      <servlet-class>WeatherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>http_request</servlet-name>
      <url-pattern>/servlet/request/*</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
      <servlet-name>weather</servlet-name>
      <url-pattern>/servlet/weather/*</url-pattern>
  </servlet-mapping>
</web-app>

The /servlet/request/* URL activates the HttpRequestServlet servlet. The /servlet/weather/* URL activates the WeatherServlet servlet.

Building the demo

Assuming that the parent directory of the demo directory contains the src directory with:

then the servlets can be compiled and placed in the classes directory with:

comp
javac -d ../demo/WEB-INF/classes/ *java

If the parent directory contains LoggingAdapter.java and WebAppServer.java, then the server container is compiled with:

javac LoggingAdapter.java WebAppServer.java

The server is run with:

java WebAppServer

RequestServlet

Parameters are retrieved with:

    private void dumpParameters( PrintWriter o,
	HttpServletRequest request )
    {
        o.print("<h2>paramters</h2>");
        Enumeration names = request.getParameterNames();
        o.print("<pre>");
	while( names.hasMoreElements() ) {
	    String name = (String)names.nextElement();
	    o.print( name + ":");
	    String[] values = request.getParameterValues(name);
	    for( String v : values ) {
	        o.print( " " + v );
	    }
	    o.println();
	}
        o.print("</pre>");
    }

WeatherServlet and getParameter

A form's paramters can be retrieved with the getParameter method. This works for both GET and POST requests.

WeatherServlet.java
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.BufferedReader;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.TreeMap;
import java.net.URLDecoder;
import java.io.UnsupportedEncodingException;

public class WeatherServlet extends HttpServlet {

    private static String htmlHead = "<html><head></head><body>";
    private static String htmlTail = "</body></html>";

    protected void doGet(
        HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException
    {
        String path = request.getPathInfo();
        path = path.substring(1);
        String[] parts = path.split("/");
        if ( parts[0].equals("update") ) {
            updateWeatherDesc( request );
	    report( response );
        }
        else if ( parts[0].equals("list") ) {
            report( response );
        }
    }

    protected void doPost(
        HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException
    {
        doGet( request, response);
    }

    private TreeMap<String,WeatherDesc> weather =
        new TreeMap<String,WeatherDesc>();

    private synchronized void updateWeatherDesc( 
        HttpServletRequest request )
    {
        String location = request.getParameter("location" );
        double temp = Double.NaN;
	try {
	    String t = request.getParameter("temp" );
	    if ( t != null ) {
		temp = Double.parseDouble( t );
	    }
	}
	catch( Exception ex ) {
	}
        String synopsis = request.getParameter("synopsis" );

        weather.put(
            location,
            new WeatherDesc(location,temp,synopsis) );
    }

    private void report( HttpServletResponse response )
        throws ServletException, IOException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println( htmlHead );

        out.println("<table border='1'>");
        synchronized ( this ) {
            Iterator<WeatherDesc> it = weather.values().iterator();
            while ( it.hasNext() ) {
                WeatherDesc w = it.next();
                out.print("<tr>");
                out.print("<td>"+w.getLocation()+"</td>");
                out.print("<td>"+w.getTemperature()+"</td>");
                out.print("<td>"+w.getSynopsis()+"</td>");
                out.print("</tr>");
            }
        }
        out.println("</table>");
        out.println( htmlTail );
    }
}

WeatherDesc

WeatherDesc.java
public class WeatherDesc {
    private String location;
    private double currentTemp;
    private String synopsis;

    public WeatherDesc(
        String location,
        double currentTemp, 
        String synopsis )
    {
        this.location = location;
        this.currentTemp = currentTemp;
        this.synopsis = synopsis;
    }

    public String getLocation() {
        return location;
    }

    public double getTemperature() {
        return currentTemp;
    }

    public String getSynopsis() {
        return synopsis;
    }
}

Jetty servlet container

A servlet server (container) can be created with the following code using the Jetty framework:

WebAppServer.java
import org.mortbay.jetty.Server;
import org.mortbay.jetty.webapp.WebAppContext;
import org.mortbay.thread.BoundedThreadPool;
import org.mortbay.log.Log;

public class WebAppServer {
    public static void main(String[] args) {
	int port = 4004;
	if ( args.length >= 1 ) {
	    try {
		port = Integer.parseInt( args[0] );
	    }
	    catch( Exception ex ){ /*ignore*/ }
	}
	try {
	    //replace the default logger
	    Log.setLog( new LoggingAdapter(
		    java.util.logging.Logger.getLogger("jetty") ) );

	    Server server = new Server( port );
	    // set up a thread pool
	    BoundedThreadPool threadPool = new BoundedThreadPool();
	    threadPool.setMaxThreads( 100 );
	    server.setThreadPool(threadPool);

	    WebAppContext wac = new WebAppContext();
	    wac.setContextPath("/");
	    wac.setWar("demo");

	    server.addHandler( wac );

	    server.setStopAtShutdown(true);
	    server.setSendServerVersion(true);
	    
	    server.start();
	    server.join();
	}
	catch( Exception ex ) {
	    Log.warn( ex );
	}
    }
}

The LoggingAdapter class adapts the standard Java logger to the logger used by the Jetty Package.

How could WebAppServer be modified to handler more than one web application.

Logger Adapter

LoggingAdapter.java
import java.util.logging.Level;
/**
 * Adapts sun's logger to the jetty package logger.
 */

public class LoggingAdapter implements org.mortbay.log.Logger {

    // copied from org.mortbay.log.StdErrLog
    private String format(String msg, Object arg0, Object arg1) {
	int i0=msg.indexOf("{}");
	int i1= i0 < 0 ? -1 : msg.indexOf("{}", i0+2);

	if (arg1!=null && i1>=0)
	    msg = msg.substring(0,i1)+arg1+msg.substring(i1+2);
	if (arg0!=null && i0>=0)
	    msg = msg.substring(0,i0)+arg0+msg.substring(i0+2);
	return msg;
    }

    private java.util.logging.Logger logger;
    private boolean debugEnabled;

    public LoggingAdapter ( java.util.logging.Logger logger ) {
	this.logger = logger;
	this.debugEnabled = false;
    }

    public void debug(String msg, Object arg0, Object arg1) {
        if ( debugEnabled ) {
	    logger.log( Level.FINEST, format(msg, arg0, arg1) );
	}
    }

    public void debug(String msg, Throwable th) {
        if ( debugEnabled ) {
	    logger.log( Level.FINEST, msg, th );
	}
    }

    public org.mortbay.log.Logger getLogger(String name) {
        return this;
    }

    public void info(String msg, Object arg0, Object arg1) {
	logger.log( Level.INFO, format(msg, arg0, arg1) );
    }

    public boolean isDebugEnabled() {
        return debugEnabled;
    }

    public void setDebugEnabled(boolean enabled) {
        debugEnabled = enabled;
    }

    public void warn(String msg, Object arg0, Object arg1) {
	logger.log( Level.WARNING, format(msg, arg0, arg1) );
    }

    public void warn(String msg, Throwable th) {
	logger.log( Level.WARNING, msg, th );
    }
}