001// Copyright (C) 1998-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>.
002// All rights reserved.  Use of this class is limited.
003// Please see the LICENSE for more information.
004
005package com.oreilly.servlet;
006
007import java.io.*;
008import java.net.*;
009import java.util.*;
010import javax.servlet.*;
011import javax.servlet.http.*;
012
013/** 
014 * A superclass for HTTP servlets that wish to accept raw socket 
015 * connections.  DaemonHttpServlet 
016 * starts listening for client requests in its <tt>init()</tt> method 
017 * and stops listening in its <tt>destroy()</tt> method.  In between, 
018 * for every connection it receives, it calls the abstract 
019 * <tt>handleClient(Socket client)</tt> method.  This method should 
020 * be implemented by the servlet subclassing DaemonHttpServlet.
021 * The port on which the servlet is to listen is determined by the 
022 * <tt>getSocketPort()</tt> method.
023 *
024 * @see com.oreilly.servlet.RemoteDaemonHttpServlet
025 *
026 * @author <b>Jason Hunter</b>, Copyright &#169; 1998
027 * @version 1.0, 98/09/18
028 */
029public abstract class DaemonHttpServlet extends HttpServlet {
030
031  /**
032   * The default listening port (1313)
033   */
034  protected int DEFAULT_PORT = 1313;
035  private Thread daemonThread;
036
037  /**
038   * Begins a thread listening for socket connections.  Subclasses
039   * that override this method must be sure to first call 
040   * <tt>super.init(config)</tt>.
041   * 
042   * @param config the servlet config
043   * @exception ServletException if a servlet exception occurs
044   */
045  public void init(ServletConfig config) throws ServletException {
046    super.init(config);
047
048    try {
049      daemonThread = new Daemon(this);
050      daemonThread.start();
051    }
052    catch (Exception e) {
053      log("Problem starting socket server daemon thread" +
054          e.getClass().getName() + ": " + e.getMessage());
055    }
056  }
057
058  /**
059   * Returns the socket port on which the servlet will listen.
060   * A servlet can change the port in three ways: by using the 
061   * <tt>socketPort</tt> init parameter, by setting the <tt>DEFAULT_PORT</tt>
062   * variable before calling <tt>super.init()</tt>, or by overriding this 
063   * method's implementation.
064   *
065   * @return the port number on which to listen
066   */
067  protected int getSocketPort() {
068    try { return Integer.parseInt(getInitParameter("socketPort")); }
069    catch (NumberFormatException e) { return DEFAULT_PORT; }
070  }
071
072  /**
073   * Handles a new socket connection.  Subclasses must define this method.
074   *
075   * @param client the client socket
076   */
077  abstract public void handleClient(Socket client);
078
079  /**
080   * Halts the thread listening for socket connections.  Subclasses
081   * that override this method must be sure to first call 
082   * <tt>super.destroy()</tt>.
083   */
084  public void destroy() {
085    try {
086      daemonThread.stop();
087      daemonThread = null;
088    }
089    catch (Exception e) {
090      log("Problem stopping server socket daemon thread: " +
091          e.getClass().getName() + ": " + e.getMessage());
092    }
093  }
094}
095
096// This work is broken into a helper class so that subclasses of
097// DaemonHttpServlet can define their own run() method without problems.
098
099class Daemon extends Thread {
100
101  private ServerSocket serverSocket;
102  private DaemonHttpServlet servlet;
103
104  public Daemon(DaemonHttpServlet servlet) {
105    this.servlet = servlet;
106  }
107
108  public void run() {
109    try {
110      // Create a server socket to accept connections
111      serverSocket = new ServerSocket(servlet.getSocketPort());
112    }
113    catch (Exception e) {
114      servlet.log("Problem establishing server socket: " +
115                  e.getClass().getName() + ": " + e.getMessage());
116      return;
117    }
118
119    try {
120      while (true) {
121        // As each connection comes in, call the servlet's handleClient().
122        // Note this method is blocking.  It's the servlet's responsibility
123        // to spawn a handler thread for long-running connections.
124        try {
125          servlet.handleClient(serverSocket.accept());
126        }
127        catch (IOException ioe) {
128          servlet.log("Problem accepting client's socket connection: " +
129                      ioe.getClass().getName() + ": " + ioe.getMessage());
130        }
131      }
132    }
133    catch (ThreadDeath e) {
134      // When the thread is killed, close the server socket
135      try {
136        serverSocket.close();
137      }
138      catch (IOException ioe) {
139        servlet.log("Problem closing server socket: " +
140                    ioe.getClass().getName() + ": " + ioe.getMessage());
141      }
142    }
143  }
144}