Java Servlets

We describe how to implement java servlets. Based on

1 Dynamic Content

1.1 Common Gateway Interface

R e q u e s t f o r C G I 1 R e q u e s t f o r C G I 2 R e q u e s t f o r C G I 1 C h i l d P r o c e s s 1 W e b S e r v e r C h i l d P r o c e s s 2 C h i l d P r o c e s s 3

1.2 FastCGI

R e q u e s t f o r C G I 1 R e q u e s t f o r C G I 2 R e q u e s t f o r C G I 1 C h i l d P r o c e s s 1 W e b S e r v e r C h i l d P r o c e s s 2

1.3 mod_perl

1.4 Server Extensions

1.5 Embedding Code in HTML

1.6 Servlet Process

Servlet Process

2 Why Servlets?

3 Basic HTTP Servlet

Servlet Get Post

3.1 HelloWorld

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {

    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    out.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
    out.println("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">");
    out.println("<head><title>Hello World</title></head>");
    out.println("<body>");
    out.println("<h1>Hello World</h1>");
    out.println("</body></html>");
  }
}

3.2 Hello Forms

<html>
  <head>
    <title>Introductions</title>
  </head>
  <body>
    <form method="get" action="/servlet/hello">
      If you don't mind me asking, what is your name?
      <input type="text" name="name"/><p/>
        <input type="submit"/>
    </form>
  </body>
</html>
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class Hello extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {

    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    String name = req.getParameter("name");
    out.println("<html>");
    out.println("<head><title>Hello, " + name + "</title></head>");
    out.println("<body>");
    out.println("Hello, " + name);
    out.println("</body></html>");
  }

  public String getServletInfo() {
    return "A servlet that knows the name of the person to whom it's" + 
           "saying hello";
  }
}

3.3 Handling POST



public void doPost(HttpServletRequest req, HttpServletResponse res)
  throws ServletException, IOException {

  doGet(req, res);
}

3.4 Handling HEAD

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {

    res.setContentType("text/html");

    if (req.getMethod().equals("HEAD")) return;

    //its really a GET.....
  }
}

3.5 Web Applications

3.6 Deployment Descriptor

<?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>
            hi
        </servlet-name>
        <servlet-class>
            HelloWorld
        </servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>
            hi
        </servlet-name>
        <url-pattern>
            /hello.html
        </url-pattern>
    </servlet-mapping>
</web-app>

4 Threads in the Server

  1. Create and initialize servlet.
  2. Handle zero or more calls from clients.
  3. Destroy servlet and garbage collect it.

4.1 Simple Counter

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class SimpleCounter extends HttpServlet {

  int count = 0;

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/plain");
    PrintWriter out = res.getWriter();
    count++;
    out.println("Since loading, this servlet has been accessed " +
                count + " times.");
  }
}
Note:

If you have not used Java threads recently I suggest you review them. Remember that all threads can have access to the same static variables and even the same objects if the threads can communicate with each other (for example, via a socket or a file). Each thread has a start() method which gets called when it is created and a run() method which does the actual work of the thread.

4.1.1 Synchronicity Problems

  1. Synchronize method:
    public synchronized void doGet(HttpServletRequest req,
                                   HttpServletResponse res)
    
  2. Synchronize code:
    PrintWriter out = res.getWriter();
    synchronized(this) {
      count++;
      out.printl(...);
    }
    
  3. Synchronize only needed functionality:
    PrintWriter out = res.getWriter();
    int local_count;
    synchronized(this) {
      local_count = ++ count;}
    out.println(..);
    
  4. Live with it.

4.2 Counting with Globals

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HolisticCounter extends HttpServlet {

  static int classCount = 0;  // shared by all instances
  int count = 0;              // separate for each servlet
  static Hashtable instances = new Hashtable();  // also shared

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/plain");
    PrintWriter out = res.getWriter();

    count++;
    out.println("Since loading, this servlet instance has been accessed " +
                count + " times.");

    // Keep track of the instance count by putting a reference to this
    // instance in a Hashtable. Duplicate entries are ignored.
    // The size() method returns the number of unique instances stored.
    instances.put(this, this);
    out.println("There are currently " +
                instances.size() + " instances.");

    classCount++;
    out.println("Across all instances, this servlet class has been " +
                "accessed " + classCount + " times.");
  }
}

4.3 Init and Destroy

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class InitDestroyCounter extends HttpServlet {

  int count;

  public void init() throws ServletException {
    // Try to load the initial count from our saved persistent state
    FileReader fileReader = null;
    BufferedReader bufferedReader = null;
    try {
      fileReader = new FileReader("InitDestroyCounter.initial");
      bufferedReader = new BufferedReader(fileReader);
      String initial = bufferedReader.readLine();
      count = Integer.parseInt(initial);
      return;
    }
    catch (FileNotFoundException ignored) { }  // no saved state
    catch (IOException ignored) { }            // problem during read
    catch (NumberFormatException ignored) { }  // corrupt saved state
    finally {
      // Make sure to close the file
      try {
        if (bufferedReader != null) {
          bufferedReader.close();
        }
      }
      catch (IOException ignored) { }
    }

    // Default to an initial count of "0"                            
    count = 0;                                                       
  }                                                                  
                                                                     
  public void doGet(HttpServletRequest req, HttpServletResponse res) 
                               throws ServletException, IOException {
    res.setContentType("text/plain");                                
    PrintWriter out = res.getWriter();                               
    count++;                                                         
    out.println("Since the beginning, this servlet has been accessed " +
                count + " times.");                                  
  }                                                                  
                                                                     
  public void destroy() {                                            
    super.destroy();  // entirely optional

    FileWriter fileWriter = null;
    PrintWriter printWriter = null;
    try {                                                            
      fileWriter = new FileWriter("InitDestroyCounter.initial");
      printWriter = new PrintWriter(fileWriter);         
      printWriter.println(count);                                  
      return;                                                        
    }                                                                
    catch (IOException e) {  // problem during write                 
      // Log the exception. See Chapter 5.                           
    }
    finally {
      // Make sure to close the file
      if (printWriter != null) {
        printWriter.close();
      }
    }
  }
}

4.4 In the Background

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class PrimeSearcher extends HttpServlet implements Runnable {

  long lastprime = 0;                    // last prime found
  Date lastprimeModified = new Date();   // when it was found
  Thread searcher;                       // background search thread

  public void init() throws ServletException {
    searcher = new Thread(this);
    searcher.setPriority(Thread.MIN_PRIORITY);  // be a good citizen
    searcher.start();
  }

  public void run() {
    //do work, say, search for primes.
  }

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/plain");
    PrintWriter out = res.getWriter();
    out.println("The last prime discovered was " + lastprime);
    out.println(" at " + lastprimeModified);
  }

  public void destroy() {
    searcher.stop();
  }
}

4.5 Avoid Sending Unchanged Data

5 Getting Information

5.1 Information About the Server

5.2 Context Init Parameters

5.3 What Machine?

5.4 Who Are You?

5.5 Getting at the Parameters

5.6 Getting the Path

5.7 What Did You Want?

5.8 Request Headers

5.9 Reading the Input Stream

6 Sending A Response

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {

    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    out.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
    out.println("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">");
    out.println("<head><title>Hello World</title></head>");
    out.println("<body>");
    out.println("<h1>Hello World</h1>");
    out.println("</body></html>");
  }
}

6.1 Persistent Connections

6.2 Buffering

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class Buffering extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setBufferSize(8 * 1024); // 8K buffer
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    int size = res.getBufferSize(); // returns 8096 or greater

    out.println("The client won't see this");
    res.reset();
    out.println("Nor will the client see this!");
    res.reset();
    out.println("And this won't be seen if sendError() is called");
    if (req.getParameter("important_parameter") == null) {
      res.sendError(res.SC_BAD_REQUEST, "important_parameter needed");
    }
  }
}

6.3 Response Code

Mnemonic Code Meaning
SC_OK 200 Everything's OK.
SC_NO_CONTENT 204 There is nothing to return.
SC_MOVED_PERMANENTLY 301 The requested resource has moved. Put new URL in Location header.
SC_MOVED_TEMPORARILY 302 Temporary move. Put new URL in Location header.
SC_UNAUTHORIZED 401 Not authorized.
SC_NOT_FOUND 404 Document was not found.
SC_INTERNAL_SERVER_ERROR 500 Server is broken.

6.4 Setting Headers

6.5 Client Refresh

6.6 Handling Errors

<?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>
  <error-page>
    <error-code>
      400
    </error-code>
    <location>
      /400.html
    </location>
  </error-page>
    <error-page>
    <error-code>
      404
    </error-code>
    <location>
      /404.html
    </location>
  </error-page>
</web-app>

6.7 Log Files

6.8 Exceptions

6.9 The Stop Button

7 Serving Dynamic Images

import java.io.*;
import java.awt.*;
import javax.servlet.*;
import javax.servlet.http.*;

import Acme.JPM.Encoders.GifEncoder; //3rd party

public class HelloWorldGraphics extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    ServletOutputStream out = res.getOutputStream();  // binary output!

    Graphics g = null;

    try {
      Image image = new BufferedImage(400, 60, TYPE_INT_BGR);
      g = image.getGraphics();
      
      // Draw "Hello World!" to the off-screen graphics context
      g.setFont(new Font("Serif", Font.ITALIC, 48));
      g.drawString("Hello World!", 10, 50);

      // Encode the off-screen image into a GIF and send it to the client
      res.setContentType("image/gif");
      GifEncoder encoder = new GifEncoder(image, out);
      encoder.encode();
    }
    finally {
      // Clean up resources
      if (g != null) g.dispose();
    }
  }
}

7.1 About Images

8 Serving Compressed Content

  1. Set the Content-Encoding header to the appropriate of gzip (best), compress, or deflate.
  2. Wrap the output in a GZIPOutputStream or ZipOutputStream, make sure to close().

9 Session Tracking

Dory

Dory suffers from short-term memory loss.

9.1 User Authentication

  1. It requires everyone to create account and log in.
  2. There is no logout mechanism, just kill browser.
  3. Cannot have more than one session at a time.

9.2 Hidden Form Fields

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class ShoppingCartViewerHidden extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    out.println("...");
    
    // Cart items are passed in as the item parameter.
    String[] items = req.getParameterValues("item");

    // Ask if the user wants to add more items or check out.
    // Include the current items as hidden fields so they'll be passed on.
    out.println("<form action=\"/servlet/ShoppingCart\" method=\"POST\">");
    if (items != null) {
      for (int i = 0; i < items.length; i++) {
        out.println("<input type=\"HIDDEN\" name=\"item\" value=\"" +
          items[i] + "\">");
      }
    }
    out.println("...");
  }
}

9.3 URL Rewriting

9.4 Cookies

9.4.1 Cookies Example

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class ShoppingCartViewerCookie extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    // Get the current session ID by searching the received cookies.
    String sessionid = null;
    Cookie[] cookies = req.getCookies();
    if (cookies != null) {
      for (int i = 0; i < cookies.length; i++) {
        if (cookies[i].getName().equals("sessionid")) {
          sessionid = cookies[i].getValue();
          break;
        }
      }
    }

    // If the session ID wasn't sent, generate one.
    // Then be sure to send it to the client with the response.
    if (sessionid == null) {
      sessionid = generateSessionId();
      Cookie c = new Cookie("sessionid", sessionid);
      res.addCookie(c);
    }

  private static String generateSessionId() {
    String uid = new java.rmi.server.UID().toString();  // guaranteed unique
    return java.net.URLEncoder.encode(uid);  // encode any special chars
  }


}

9.5 Session Tracking API

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class SessionTracker extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    // Get the current session object, create one if necessary
    HttpSession session = req.getSession();

    // Increment the hit count for this page. The value is saved
    // in this client's session under the name "tracker.count".
    Integer count = (Integer)session.getAttribute("tracker.count");
    if (count == null)
      count = new Integer(1);
    else
      count = new Integer(count.intValue() + 1);
    session.setAttribute("tracker.count", count);

    out.println("<html><head><title>SessionTracker</title></head>");
    out.println("<body><h1>Session Tracking Demo</h1>");

    // Display the hit count for this page
    out.println("You've visited this page " + count +
      ((count.intValue() == 1) ? " time." : " times."));

    out.println("<p/>");

    out.println("<h2>Here is your session data:</h2>");
    Enumeration e = session.getAttributeNames();
    while (e.hasMoreElements()) {
      String name = (String) e.nextElement();
      out.println(name + ": " + session.getAttribute(name) + "<br/>");
    }
    out.println("</body></html>");
  }
}

9.5.1 Session Tracking Customizations

9.5.2 Session Tracking: Behind the Scenes

10 Security

  1. Authentication
  2. Authorization
  3. Confidentiality
  4. Integrity

10.1 HTTP Authentication

10.2 Form-Based Authentication

10.3 Custom Authentication

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

import com.oreilly.servlet.Base64Decoder;

public class CustomAuth extends HttpServlet {

  Hashtable users = new Hashtable();

  public void init(ServletConfig config) throws ServletException {
    super.init(config);

    // Names and passwords are case sensitive!
    users.put("Wallace:cheese",     "allowed");
    users.put("Gromit:sheepnapper", "allowed");
    users.put("Penguin:evil",       "allowed");
  }

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/plain");
    PrintWriter out = res.getWriter();

    // Get Authorization header
    String auth = req.getHeader("Authorization");

    // Do we allow that user?
    if (!allowUser(auth)) {
      // Not allowed, so report he's unauthorized
      res.setHeader("WWW-Authenticate", "BASIC realm=\"users\"");
      res.sendError(res.SC_UNAUTHORIZED);
      // Could offer to add him to the allowed user list
    }
    else {
      // Allowed, so show him the secret stuff
      out.println("Top-secret stuff");
    }
  }

  // This method checks the user information sent in the Authorization
  // header against the database of users maintained in the users Hashtable.
  protected boolean allowUser(String auth) throws IOException {
    if (auth == null) return false;  // no auth

    if (!auth.toUpperCase().startsWith("BASIC "))
      return false;  // we only do BASIC

    // Get encoded user and password, comes after "BASIC "
    String userpassEncoded = auth.substring(6);

    // Decode it, using any base 64 decoder (we use com.oreilly.servlet)
    String userpassDecoded = Base64Decoder.decode(userpassEncoded);

    // Check our user list to see if that user and password are "allowed"
    if ("allowed".equals(users.get(userpassDecoded)))
      return true;
    else
      return false;
  }
}

10.4 Custom Form-Based Authorization

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class LoginHandler extends HttpServlet {

  public void doPost(HttpServletRequest req, HttpServletResponse res)
                                throws ServletException, IOException {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();