Servlet Life Cycle and Servlet Containers

Servlets are Java classes that extend javax.servlet.http.HttpServlet class. These classes handle requests from web clients, usually browsers. A servlet container is a server program that accepts Internet connections, examines the request and invokes the relevant method in the servlet.

The life cycle of a servlet object consists of:

Only one servlet object is created per server.

Service methods for a HttpServlet

The servlet class will usually override one or more of the following:

All of the above methods are protected, why?

The public methods: init() and destroy() can be overridden. Logging is provided with log(String msg).

HttpServletRequest methods

Some of the most relevant methods are:

String getServletPath()
The path that activated this servlet.
Enumeration getHeaderNames()
return the an enumeration of the header names.
String getHeader(String name)
return value of first header that matches name.
Enumeration getHeaders( String name)
return an enumeration of all the entries with the named header.
String getMethod()
return the HTTP method
String getPathInfo()
return part of URL not used to select the servlet.
String getQueryString()
return query part of URL
int getContentLength()
length in bytes of content stream or -1 if length not known
String getContentType()
return MIME type of content
ServletInputStream getInputStream()
return byte stream of content (cannot use getReader() if getInputStream() used).
BufferedReader getReader()
return character stream of content (cannot use getInputSteram() if getReader() used).
String getCharacterEncoding()
return the character encoding of the document

HttpServletResponse methods

Some of the most relevant methods are:

void sendError(int sc, String msg)
Send an error response.
void sendRedirect(String location)
redirect client to location.
void setDateHeader(String name, long date)
set the header field with name to a date string.
void addHeader(String name, String value)
append name to header with value.
void setStatus(int sc)
set the status code for the HTTP response (e.g., 200 is OK).
int setContentLength(int len)
length of content in response in bytes.
void setContentType( String type )
set MIME type of content.
void flushBuffer()
flushes buffer and send header if not already sent.
ServletOutputStream getOutputStream()
Output byte stream ( getWriter() cannot be used, if getOutputStream() is used ).
PrintWriter getWriter()
Output character stream ( getOutputStream() cannot be used, if getWriter() is used ).
boolean isCommitted()
return if response header has already been sent.
void setCharacterEncoding(String charset)
Set the character encoding of the outgoing document.

Uniform resource location syntax

The general URL form is:

protocol://host/path#name?query
protocol
http, ftp, telnet, javascript, gopher ...
host
domain name of host, can include ":port-number"
path
the path name to the document
name
name in an anchor in the document (optional)
query
query string (optional), name/value pairs are specified as n1=v1&n2=v2&n3=v3..., characters can be encoded with %xx, where xx is the ASCII hex code

Links and Forms

Consider the following HTML page:

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&amp;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>

Examining HTTP requests

Design a servlet to:

The servlet should handle GET and POST requests.

Request and header information

Information on HTTP request and header is produced by:

    private void fieldInfo(
        PrintWriter o, String name, String info )
    {
        o.print( "<b>" + name + "</b>:" );
        o.print( info );
        o.print( "<br>" );
    }

Field information is formatted with:

    private void dumpInfo(
        PrintWriter o, HttpServletRequest request )
    {
        o.print("<h2>url</h2>");
        o.print("<p>");
        fieldInfo(o, "method", request.getMethod() );
        fieldInfo(o, "servlet-path", request.getServletPath() );
        fieldInfo(o, "path-info", request.getPathInfo() );
        fieldInfo(o, "query", request.getQueryString() );
        o.print("</p>");
        o.print("<h2>headers</h2>");
        o.print("<p>");
        Enumeration names = request.getHeaderNames();
        while ( names.hasMoreElements() ) {
            String name = names.nextElement().toString();
            fieldInfo(o, name, request.getHeader(name) );
        }
        o.print("</p>");
    }

Decoding a query

A query string contains a sequence of name/value pairs separated by the & character. Each name/value is separated by =. Special characters are escaped with %xx, where xx is two hexadecimal digits. A space is converted into a +.

The java.net.URLDecoder can be used to decode escaped URL name/values. The decoding code is:

    private void dumpQuery( PrintWriter o, String query ) {
        o.print("<h2>query</h2>");
        o.print("<p>");
        String[] pairs = query.split("\\&");
        for ( int i=0; i < pairs.length; i++) {
            String[] fields = pairs[i].split("=");
            try {
                String n =
                    URLDecoder.decode(fields[0], "ISO-8859-1");
                String v =
                    URLDecoder.decode(fields[1], "ISO-8859-1");
                fieldInfo( o, n, v );
            }
            catch( UnsupportedEncodingException ex ) {
                // ignore
            }
        }
        o.print("</p>");
    }

Are there any problems in the decoding code?

Handling GET requests

A class extending HttpServlet must override doGet to handle GET requests. The overridden method in HttpRequestServlet is:

    protected void doGet(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println( htmlHead );
        dumpInfo( out, request );
        String q = request.getQueryString();
        if ( q != null ) {
            dumpQuery(out, q );
        }
        out.println( htmlTail );
    }

Handling POST requests

A class extending HttpServlet must override doPost to handle POST requests. The overridden method in HttpRequestServlet is:

    protected void doPost(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println( htmlHead );
        dumpInfo( out, request );
        BufferedReader rd = request.getReader();
        out.println("<h2>content</h2>");
        int ch;
        StringBuilder sb = new StringBuilder();
        while ( (ch=rd.read()) != -1 ) {
            sb.append( (char)ch);
        }
        dumpQuery(out, sb.toString() );
        out.println( htmlTail );
    }

Why is StringBuilder preferred over StringBuffer?

Source for HttpRequestServlet

HttpRequestServlet.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.Enumeration;
import java.net.URLDecoder;
import java.io.UnsupportedEncodingException;

public class HttpRequestServlet extends HttpServlet {
    private static String htmlHead = "<html><head></head><body>";
    private static String htmlTail = "</body></html>";

    private void fieldInfo(
        PrintWriter o, String name, String info )
    {
        o.print( "<b>" + name + "</b>:" );
        o.print( info );
        o.print( "<br>" );
    }

    private void dumpInfo(
        PrintWriter o, HttpServletRequest request )
    {
        o.print("<h2>url</h2>");
        o.print("<p>");
        fieldInfo(o, "method", request.getMethod() );
        fieldInfo(o, "servlet-path", request.getServletPath() );
        fieldInfo(o, "path-info", request.getPathInfo() );
        fieldInfo(o, "query", request.getQueryString() );
        o.print("</p>");
        o.print("<h2>headers</h2>");
        o.print("<p>");
        Enumeration names = request.getHeaderNames();
        while ( names.hasMoreElements() ) {
            String name = names.nextElement().toString();
            fieldInfo(o, name, request.getHeader(name) );
        }
        o.print("</p>");
    }

    private void dumpQuery( PrintWriter o, String query ) {
        o.print("<h2>query</h2>");
        o.print("<p>");
        String[] pairs = query.split("\\&");
        for ( int i=0; i < pairs.length; i++) {
            String[] fields = pairs[i].split("=");
            try {
                String n =
                    URLDecoder.decode(fields[0], "ISO-8859-1");
                String v =
                    URLDecoder.decode(fields[1], "ISO-8859-1");
                fieldInfo( o, n, v );
            }
            catch( UnsupportedEncodingException ex ) {
                // ignore
            }
        }
        o.print("</p>");
    }

    protected void doGet(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println( htmlHead );
        dumpInfo( out, request );
        String q = request.getQueryString();
        if ( q != null ) {
            dumpQuery(out, q );
        }
        out.println( htmlTail );
    }

    protected void doPost(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println( htmlHead );
        dumpInfo( out, request );
        BufferedReader rd = request.getReader();
        out.println("<h2>content</h2>");
        int ch;
        StringBuilder sb = new StringBuilder();
        while ( (ch=rd.read()) != -1 ) {
            sb.append( (char)ch);
        }
        dumpQuery(out, sb.toString() );
        out.println( htmlTail );
    }
}

Handling POST and GET queries

A form can be submitted with either the GET method or the POST method. The servlet handling the form is only interested in the name/value pairs. Construct a utility class to hide the difference between a POST and GET request. The pairs can be represented with a inner class, defined as:

    public static class Pair {
        public String name;
        public String value;
        public Pair( String name, String value ) {
            this.name = name;
            this.value = value;
        }
    }

QueryDecodeServlet

The QueryDecodeServlet is an abstract class that decodes the query for a POST or GET request. The query is placed in a list of Pair objects. The handling code then invokes:

    protected abstract void processQuery(
        List<Pair> list,
        HttpServletRequest request,
        HttpServletResponse response )
        throws ServletException, IOException;

The subclass that handles the queries must implement this method. The QueryDecodeServlet is declared as:

public abstract class QueryDecodeServlet extends HttpServlet {

Decoding a query

A list of pairs is created with:

    private List<Pair> decodeQuery( String query ) {
        ArrayList<Pair> list = new ArrayList<Pair>();

        if ( query == null ) return list;

        String[] pairs = query.split("\\&");
        for ( int i=0; i < pairs.length; i++) {
            String[] fields = pairs[i].split("=");
            try {
                String n =
                    URLDecoder.decode(fields[0], "ISO-8859-1");
                String v =
                    URLDecoder.decode(fields[1], "ISO-8859-1");
                list.add( new Pair(n, v) );
            }
            catch( UnsupportedEncodingException ex ) {
                // ignore, is this a good idea
            }
        }
        return list;
    }

How should the exception be handled?

Processing a GET and POST

GET and POST requests are handled by overriding doGet and doPost methods.

    protected void doGet(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        List<Pair> list = decodeQuery(request.getQueryString());
        processQuery( list, request, response );
    }

    protected void doPost(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        BufferedReader rd = request.getReader();
        StringBuilder sb = new StringBuilder();
        int ch;
        while ( (ch=rd.read()) != -1 ) {
            sb.append( (char)ch);
        }
        List<Pair> list = decodeQuery( sb.toString() );
        processQuery( list, request, response );
    }

Why can't content length be used to improve the reading efficiency?

Source for QueryDecodeServlet

QueryDecodeServlet.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.BufferedReader;
import java.util.List;
import java.util.ArrayList;
import java.net.URLDecoder;
import java.io.UnsupportedEncodingException;

public abstract class QueryDecodeServlet extends HttpServlet {

    public static class Pair {
        public String name;
        public String value;
        public Pair( String name, String value ) {
            this.name = name;
            this.value = value;
        }
    }

    protected abstract void processQuery(
        List<Pair> list,
        HttpServletRequest request,
        HttpServletResponse response )
        throws ServletException, IOException;

    private List<Pair> decodeQuery( String query ) {
        ArrayList<Pair> list = new ArrayList<Pair>();

        if ( query == null ) return list;

        String[] pairs = query.split("\\&");
        for ( int i=0; i < pairs.length; i++) {
            String[] fields = pairs[i].split("=");
            try {
                String n =
                    URLDecoder.decode(fields[0], "ISO-8859-1");
                String v =
                    URLDecoder.decode(fields[1], "ISO-8859-1");
                list.add( new Pair(n, v) );
            }
            catch( UnsupportedEncodingException ex ) {
                // ignore, is this a good idea
            }
        }
        return list;
    }

    protected void doGet(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        List<Pair> list = decodeQuery(request.getQueryString());
        processQuery( list, request, response );
    }

    protected void doPost(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        BufferedReader rd = request.getReader();
        StringBuilder sb = new StringBuilder();
        int ch;
        while ( (ch=rd.read()) != -1 ) {
            sb.append( (char)ch);
        }
        List<Pair> list = decodeQuery( sb.toString() );
        processQuery( list, request, response );
    }
}

WeatherServlet

Design a servlet that produces a list of weather descriptions and allows the descriptions to be updated from a web page. A weather description can be stored with:

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;
    }
}

Updating with a form

The web page with the form to update the weather is:

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

The QueryDecodeServlet class can be used to decode the form. The WeatherServlet is declared with:

public class WeatherServlet extends QueryDecodeServlet {

Handling a list or an update

The processQuery method is overridden to handle the request. The weather description is updated if the URL contains update. It is listed if the URL contains list.

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

Why doesn't processQuery do all the work (i.e., how cohesive is processQuery)?

Updating a weather description

Code to handle the update is:

    private synchronized void updateWeatherDesc( 
        List<QueryDecodeServlet.Pair> list )
    {
        String location = null;
        double temp = Double.NaN;
        String synopsis = null;

        Iterator<QueryDecodeServlet.Pair> it = list.iterator();
        while ( it.hasNext() ) {
            QueryDecodeServlet.Pair p = it.next();
            if ( p.name.equals( "location" ) ) {
                location = p.value;
            }
            else if ( p.name.equals( "temp" ) ) {
                temp = Double.parseDouble( p.value );
            }
            else if ( p.name.equals( "synopsis" ) ) {
                synopsis = p.value;
            }
        }
        weather.put(
            location,
            new WeatherDesc(location,temp,synopsis) );
    }

Replying to an update

A brief message thanking the user for the update is done with:

    private void update(
        List<QueryDecodeServlet.Pair> list,
        HttpServletRequest request,
        HttpServletResponse response )
        throws ServletException, IOException
    {
        updateWeatherDesc( list );
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println( htmlHead );
        out.println("<p>Thank you for your report.</p>");
        out.println( htmlTail );
    }

Is this a good or a bad example of a user interface?

Generating a listing

The weather report web page is generated with:

    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 );
    }

Source for WeatherServlet

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 QueryDecodeServlet {

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

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

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

    private synchronized void updateWeatherDesc( 
        List<QueryDecodeServlet.Pair> list )
    {
        String location = null;
        double temp = Double.NaN;
        String synopsis = null;

        Iterator<QueryDecodeServlet.Pair> it = list.iterator();
        while ( it.hasNext() ) {
            QueryDecodeServlet.Pair p = it.next();
            if ( p.name.equals( "location" ) ) {
                location = p.value;
            }
            else if ( p.name.equals( "temp" ) ) {
                temp = Double.parseDouble( p.value );
            }
            else if ( p.name.equals( "synopsis" ) ) {
                synopsis = p.value;
            }
        }
        weather.put(
            location,
            new WeatherDesc(location,temp,synopsis) );
    }

    private void update(
        List<QueryDecodeServlet.Pair> list,
        HttpServletRequest request,
        HttpServletResponse response )
        throws ServletException, IOException
    {
        updateWeatherDesc( list );
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println( htmlHead );
        out.println("<p>Thank you for your report.</p>");
        out.println( htmlTail );
    }

    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 );
    }
}

Source for ServletLauncher

The servlet container for these examples is:

SimpleServer.java
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.DefaultHandler;
import org.mortbay.jetty.handler.HandlerList;
import org.mortbay.jetty.handler.ResourceHandler;

import org.mortbay.jetty.handler.ContextHandlerCollection;
import org.mortbay.jetty.servlet.Context;

public class SimpleServer {

    public static void main(String[] args)
        throws Exception
    {
        int port = 8008;
	String baseDir = ".";
        if (args.length >= 1 ) {
	    port = Integer.parseInt(args[0]);
	}
        Server server = new Server( port );
        
        ResourceHandler resource = new ResourceHandler();
        resource.setResourceBase( baseDir );
        
	ContextHandlerCollection contexts = new ContextHandlerCollection();
	Context servlets = new Context(contexts,"/servlet" );
	servlets.addServlet( HttpRequestServlet.class, "/request/*" );
	servlets.addServlet( WeatherServlet.class, "/weather/*" );

	server.addHandler( resource );
	server.addHandler( contexts );
	server.addHandler( new DefaultHandler() );

        server.start();
        server.join();
    }
}