Spring Semester 2002


Lecture Notes Twenty-Five: The Triple Web Chat Application

In this set of notes we will try to wrap up your lab for last week.

Homework five is asking you to implement a web chat application.

That's a step further from the lab you need to turn in this week.

But the networking is done in only one way.

We want now to extend your DaytimeServlet to support chatting.

Here's what we'll do:

These are the files that you need:

  1. HttpUser.html (entry point for HTTP applet)
  2. SocketUser.html
  3. RMIUser.html

  4. HttpChatApplet.java (the HTTP applet)
  5. HttpMessage.java (utility class)

  6. SocketChatApplet.java (the Socket applet)

  7. RMIChatApplet.java (the RMI applet)
  8. ChatClient.java (the RMI applet's interface)

  9. ChatServlet.java (the servlet)
  10. ChatServer.java (the servlet's interface)

  11. RemoteDaemonHttpServlet.java (utility class extended by the servlet)
  12. DaemonHttpServlet.java (utility class extended by the class the servlet extends)

Yes, it may seem overwhelming, but that's only a perception.

Now I will print the contents of the files here and tell you what needs to be changed.

You can master all of this, so let's get it installed first, then start thinking about it.

The overview for the impatient looks like this:

  1. Put all of the files inside a folder in webapps.

  2. (Make sure there's a context for it in server.xml).

  3. Compile everything.

  4. Run rmic on the chat servlet class and the RMI applet class.

  5. Copy everything in WEB-INF/classes then access the three HTML files.

  6. The RMI applet may work only in one way so please use the appletviewer.

That's all there is to it.

Now the files.

1. HttpUser.html

<HTML>
<HEAD><TITLE>An Absurdly Simple Chat</TITLE></HEAD>
<BODY>
<H1>An Absurdly Simple Chat</H1>
<APPLET CODE=HttpChatApplet CODEBASE=/two WIDTH=500 HEIGHT=170>
  <param name=user value=httpUser> 
</APPLET>
</BODY></HTML>
2. SocketUser.html

<HTML>
<HEAD><TITLE>An Absurdly Simple Chat</TITLE></HEAD>
<BODY>
<H1>An Absurdly Simple Chat</H1>
<APPLET CODE=SocketChatApplet CODEBASE=/two WIDTH=500 HEIGHT=170>
  <param name=user value=socketUser>
</APPLET>
</BODY></HTML>
3. RMIUser.html

<HTML>
<HEAD><TITLE>An Absurdly Simple Chat</TITLE></HEAD>
<BODY>
<H1>An Absurdly Simple Chat</H1>
<APPLET CODE=RMIChatApplet CODEBASE=/two WIDTH=500 HEIGHT=170>
  <param name=user value=rmiUser>
</APPLET>
</BODY></HTML>
4. HttpChatApplet.java

import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;

public class HttpChatApplet extends Applet implements Runnable {

  TextArea text;
  Label label;
  TextField input;
  Thread thread;
  String user;

  public void init() {

    // Check if this applet was loaded directly from the filesystem.
    // If so, explain to the user that this applet needs to be loaded
    // from a server in order to communicate with that server's servlets.

    URL codebase = getCodeBase();

    if (!"http".equals(codebase.getProtocol())) {

      System.out.println();
      System.out.println("*** Whoops! ***");
      System.out.println("This applet must be loaded from a web server.");
      System.out.println("Please try again, this time fetching the HTML");
      System.out.println("file containing this servlet as");
      System.out.println("\"http://server:port/file.html\".");
      System.out.println();
      System.exit(1);  // Works only from appletviewer
                       // Browsers throw an exception and muddle on

    }

    // Get this user's name from an applet parameter set by the servlet
    // We could just ask the user, but this demonstrates a
    // form of servlet->applet communication.

    user = getParameter("user");

    if (user == null) user = "anonymous";

    // Set up the user interface...
    // On top, a large TextArea showing what everyone's saying.
    // Underneath, a labeled TextField to accept this user's input.

    text = new TextArea();
    text.setEditable(false);
    label = new Label("Say something: ");
    input = new TextField();
    input.setEditable(true);

    setLayout(new BorderLayout());
    Panel panel = new Panel();
    panel.setLayout(new BorderLayout());

    add("Center", text);
    add("South", panel);

    panel.add("West", label);
    panel.add("Center", input);
  }

  public void start() {
    thread = new Thread(this);
    thread.start();
  }

  String getNextMessage() {

    String nextMessage = null;

    while (nextMessage == null) {

      try {

        URL url = new URL(getCodeBase(), "/two/servlet/ChatServlet");

        HttpMessage msg = new HttpMessage(url);
        InputStream in = msg.sendGetMessage();
        DataInputStream data = new DataInputStream(
                               new BufferedInputStream(in));
        nextMessage = data.readLine();

      }
      catch (SocketException e) {
        // Can't connect to host, report it and wait before trying again
        System.out.println("Can't connect to host: " + e.getMessage());
        try { Thread.sleep(5000); } catch (InterruptedException ignored) { }
      }
      catch (FileNotFoundException e) {
        // Servlet doesn't exist, report it and wait before trying again
        System.out.println("Resource not found: " + e.getMessage());
        try { Thread.sleep(5000); } catch (InterruptedException ignored) { }
      }
      catch (Exception e) {
        // Some other problem, report it and wait before trying again
        System.out.println("General exception: " +
          e.getClass().getName() + ": " + e.getMessage());
        try { Thread.sleep(1000); } catch (InterruptedException ignored) { }
      }
    }
    return nextMessage + "\n";
  }

  public void run() {
    while (true) {
      text.appendText(getNextMessage());
    }
  }

  public void stop() {
    thread.stop();
    thread = null;
  }

  void broadcastMessage(String message) {
    message = user + ": " + message;  // Pre-pend the speaker's name
    try {

      URL url = new URL(getCodeBase(), "/two/servlet/ChatServlet");

      HttpMessage msg = new HttpMessage(url);
      Properties props = new Properties();
      props.put("message", message);
      msg.sendPostMessage(props);
    }
    catch (SocketException e) {
      // Can't connect to host, report it and abandon the broadcast
      System.out.println("Can't connect to host: " + e.getMessage());
    }
    catch (FileNotFoundException e) {
      // Servlet doesn't exist, report it and abandon the broadcast
      System.out.println("Resource not found: " + e.getMessage());
    }
    catch (Exception e) {
      // Some other problem, report it and abandon the broadcast
      System.out.println("General exception: " +
        e.getClass().getName() + ": " + e.getMessage());
    }
  }

  public boolean handleEvent(Event event) {
    switch (event.id) {
      case Event.ACTION_EVENT:
        if (event.target == input) {
          broadcastMessage(input.getText());
          input.setText("");
          return true;
        }
    }
    return false;
  }
}
5. HttpMessage.java

import java.io.*; 
import java.net.*; 
import java.util.*; 

public class HttpMessage {
    URL servlet = null; 
    Hashtable headers = null; 
    public HttpMessage(URL servlet) {
	this.servlet = servlet; 
    }
    public InputStream sendGetMessage() throws IOException {
	return sendGetMessage(null); 
    }
    public InputStream sendGetMessage(Properties args) throws IOException {
	String argString = ""; 
	if (args != null) {
	    argString = "?" + toEncodedString(args); 
	}
	URL url = new URL(servlet.toExternalForm() + argString); 
	URLConnection con = url.openConnection(); 
	con.setUseCaches(false); 
	sendHeaders(con); 
	return con.getInputStream();
    }
    public InputStream sendPostMessage() throws IOException {
	return sendPostMessage(null); 
    }
    public InputStream sendPostMessage(Properties args) throws IOException {
	String argString = ""; 
	if (args != null) {
	    argString = toEncodedString(args);
	}
	URLConnection con = servlet.openConnection(); 
	con.setDoInput(true); 
	con.setDoOutput(true); 
	con.setUseCaches(false); 
	con.setRequestProperty("Content-Type",
			       "application/x-www-form-urlencoded"); 
	sendHeaders(con); 
	DataOutputStream out = new DataOutputStream(con.getOutputStream()); 
	out.writeBytes(argString); 
	out.flush(); 
	out.close();
	return con.getInputStream(); 
    }
    public InputStream sendPostMessage(Serializable obj) throws IOException {
	URLConnection con = servlet.openConnection(); 
	con.setDoInput(true); 
	con.setDoOutput(true); 
	con.setUseCaches(false); 
	con.setRequestProperty("Content-Type", 
			       "application/x-java-serialized-object"); 
	sendHeaders(con); 
	ObjectOutputStream out = new ObjectOutputStream(con.getOutputStream()); 
	out.writeObject(obj); 
	out.flush(); 
	out.close(); 
	return con.getInputStream(); 
    }
    public void setHeader(String name, String value) {
	if (headers == null) {
	    headers = new Hashtable(); 
	}
	headers.put(name, value); 
    }
    private void sendHeaders(URLConnection con) {
	if (headers != null) {
	    Enumeration enum = headers.keys(); 
	    while (enum.hasMoreElements()) {
		String name = (String) enum.nextElement(); 
		String value = (String) headers.get(name); 
		con.setRequestProperty(name, value);
	    }
	}
    }
    private String toEncodedString(Properties args) {
	StringBuffer buf = new StringBuffer(); 
	Enumeration names = args.propertyNames(); 
	while (names.hasMoreElements()) {
	    String name = (String) names.nextElement();
	    String value = args.getProperty(name); 
	    buf.append(URLEncoder.encode(name) + "=" + URLEncoder.encode(value)); 
	    if (names.hasMoreElements()) buf.append("&"); 
	}
	return buf.toString(); 
    }
}
6. SocketChatApplet.java

import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;

public class SocketChatApplet extends Applet implements Runnable {

  static final int PORT = 36440; // 2428;

  DataInputStream serverStream;

  TextArea text;
  Label label;
  TextField input;
  Thread thread;
  String user;

  public void init() {

    // Check if this applet was loaded directly from the filesystem.
    // If so, explain to the user that this applet needs to be loaded
    // from a server in order to communicate with that server's servlets.

    URL codebase = getCodeBase();

    if (!"http".equals(codebase.getProtocol())) {

      System.out.println();
      System.out.println("*** Whoops! ***");
      System.out.println("This applet must be loaded from a web server.");
      System.out.println("Please try again, this time fetching the HTML");
      System.out.println("file containing this servlet as");
      System.out.println("\"http://server:port/file.html\".");
      System.out.println();
      System.exit(1);  // Works only from appletviewer
                       // Browsers throw an exception and muddle on

    }

    // Get this user's name from an applet parameter set by the servlet
    // We could just ask the user, but this demonstrates a
    // form of servlet->applet communication.

    user = getParameter("user");

    if (user == null) user = "anonymous";

    // Set up the user interface...
    // On top, a large TextArea showing what everyone's saying.
    // Underneath, a labeled TextField to accept this user's input.

    text = new TextArea();
    text.setEditable(false);
    label = new Label("Say something: ");
    input = new TextField();
    input.setEditable(true);

    setLayout(new BorderLayout());
    Panel panel = new Panel();
    panel.setLayout(new BorderLayout());

    add("Center", text);
    add("South", panel);

    panel.add("West", label);
    panel.add("Center", input);
  }

  public void start() {
    thread = new Thread(this);
    thread.start();
  }

  String getNextMessage() {
    String nextMessage = null;
    while (nextMessage == null) {
      try {
        // Connect to the server if we haven't before
        if (serverStream == null) {
          Socket s = new Socket(getCodeBase().getHost(), PORT);
          serverStream = new DataInputStream(
                         new BufferedInputStream(
                         s.getInputStream()));
        }
  
        // Read a line
        nextMessage = serverStream.readLine();
      }
      catch (SocketException e) {
        // Can't connect to host, report it and wait before trying again
        System.out.println("Can't connect to host: " + e.getMessage());
        serverStream = null;
        try { Thread.sleep(5000); } catch (InterruptedException ignored) { }
      }
      catch (Exception e) {
        // Some other problem, report it and wait before trying again
        System.out.println("General exception: " +
          e.getClass().getName() + ": " + e.getMessage());
        try { Thread.sleep(1000); } catch (InterruptedException ignored) { }
      }
    }
    return nextMessage + "\n";
  }

  public void run() {
    while (true) {
      text.appendText(getNextMessage());
    }
  }

  public void stop() {
    thread.stop();
    thread = null;
  }

  void broadcastMessage(String message) {
    message = user + ": " + message;  // Pre-pend the speaker's name
    try {
      URL url = new URL(getCodeBase(), "/two/servlet/ChatServlet");
      HttpMessage msg = new HttpMessage(url);
      Properties props = new Properties();
      props.put("message", message);
      msg.sendPostMessage(props);
    }
    catch (SocketException e) {
      // Can't connect to host, report it and abandon the broadcast
      System.out.println("Can't connect to host: " + e.getMessage());
    }
    catch (FileNotFoundException e) {
      // Servlet doesn't exist, report it and abandon the broadcast
      System.out.println("Resource not found: " + e.getMessage());
    }
    catch (Exception e) {
      // Some other problem, report it and abandon the broadcast
      System.out.println("General exception: " +
        e.getClass().getName() + ": " + e.getMessage());
    }
  }

  public boolean handleEvent(Event event) {
    switch (event.id) {
      case Event.ACTION_EVENT:
        if (event.target == input) {
          broadcastMessage(input.getText());
          input.setText("");
          return true;
        }
    }
    return false;
  }
}
7. RMIChatApplet.java

import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.rmi.*;
import java.rmi.registry.*;
import java.rmi.server.*;
import java.util.*;

public class RMIChatApplet extends Applet implements ChatClient {

  TextArea text;
  Label label;
  TextField input;
  Thread thread;
  String user;

  ChatServer chatServer;

  private int getRegistryPort() {

    try { return Integer.parseInt(getParameter("port")); }
    catch (NumberFormatException ignored) { return Registry.REGISTRY_PORT; }

  }

  private String getRegistryName() {

    String name = getParameter("name");
    return (name == null ? "ChatServletByDGerman" : name);

  }

  // Returns a reference to the remote chat server/servlet
  // Tries to exit if there's a problem.

  private ChatServer getChatServer() {

    try {
      Registry registry =
        LocateRegistry.getRegistry(getCodeBase().getHost(), getRegistryPort());
      Object obj = registry.lookup(getRegistryName());
      return (ChatServer)obj;
    }

    catch (java.rmi.UnknownHostException e) {
      // Don't know the registry host, try to exit
      System.out.println("Host unknown in url: " + e.getMessage());
      System.exit(1);
    }

    catch (NotBoundException e) {
      // Can't find our object, try to exit
      System.out.println("Name not bound: " + e.getMessage());
      System.exit(1);
    }

    catch (ClassCastException e) {
      // The object wasn't a ChatServer, try to exit
      System.out.println(getRegistryName() + " was not a ChatServer:" +
                         e.getMessage());
      System.exit(1);
    }

    catch (RemoteException e) {
      // General RMI problem, try to exit
      System.out.println("Remote exception: " + e.getMessage());
      System.exit(1);
    }

    catch (Exception e) {
      // Some other problem, try to exit
      System.out.println("General exception: " +
        e.getClass().getName() + ": " + e.getMessage());
      System.exit(1);
    }

    return null;  // return null if the exit() doesn't work

  }

  // Add ourselves as a client of the chat server
  // Notice there's no need for an RMI registry

  private void registerWithChatServer(ChatServer server) {
    try {
      UnicastRemoteObject.exportObject(this);
      server.addClient(this);
    }
    catch (RemoteException e) {
      // General RMI problem, try to exit
      System.out.println("Remote exception: " + e.getMessage());
      System.exit(1);
    }
    catch (Exception e) {
      // Some other problem, try to exit
      System.out.println("General exception: " +
        e.getClass().getName() + ": " + e.getMessage());
      System.exit(1);
    }
  }

  public void init() {
    // Check if this applet was loaded directly from the filesystem.
    // If so, explain to the user that this applet needs to be loaded
    // from a server in order to communicate with that server's servlets.
    URL codebase = getCodeBase();
    if (!"http".equals(codebase.getProtocol())) {
      System.out.println();
      System.out.println("*** Whoops! ***");
      System.out.println("This applet must be loaded from a web server.");
      System.out.println("Please try again, this time fetching the HTML");
      System.out.println("file containing this servlet as");
      System.out.println("\"http://server:port/file.html\".");
      System.out.println();
      System.exit(1);  // Works only from appletviewer
                       // Browsers throw an exception and muddle on
    }

    // Get the remote chat server
    chatServer = getChatServer();

    // Register ourselves as one of its clients
    registerWithChatServer(chatServer);

    // Get this user's name from an applet parameter set by the servlet
    // We could just ask the user, but this demonstrates a
    // form of servlet->applet communication.
    user = getParameter("user");
    if (user == null) user = "anonymous";

    // Set up the user interface...
    // On top, a large TextArea showing what everyone's saying.
    // Underneath, a labeled TextField to accept this user's input.
    text = new TextArea();
    text.setEditable(false);
    label = new Label("Say something: ");
    input = new TextField();
    input.setEditable(true);

    setLayout(new BorderLayout());
    Panel panel = new Panel();
    panel.setLayout(new BorderLayout());

    add("Center", text);
    add("South", panel);

    panel.add("West", label);
    panel.add("Center", input);
  }

  String getNextMessage() {
    String nextMessage = null;
    while (nextMessage == null) {
      try {
        nextMessage = chatServer.getNextMessage();
      }
      catch (RemoteException e) {
        // Remote exception, report and wait before trying again
        System.out.println("Remote Exception:" + e.getMessage());
        try { Thread.sleep(1000); } catch (InterruptedException ignored) { }
      }
    }
    return nextMessage + "\n";
  }

  public void setNextMessage(String message) {
    text.appendText(message + "\n");
  }

  void broadcastMessage(String message) {
    message = user + ": " + message;  // Pre-pend the speaker's name
    try {
      chatServer.broadcastMessage(message);
    }
    catch (RemoteException e) {
      // Remote exception, report it and abandon the broadcast
      System.out.println("Remote exception: " + e.getMessage());
    }
    catch (Exception e) {
      // Some other exception, report it and abandon the broadcast
      System.out.println("General exception: " +
        e.getClass().getName() + ": " + e.getMessage());
    }
  }

  public boolean handleEvent(Event event) {
    switch (event.id) {
      case Event.ACTION_EVENT:
        if (event.target == input) {
          broadcastMessage(input.getText());
          input.setText("");
          return true;
        }
    }
    return false;
  }
}
8. ChatClient.java

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface ChatClient extends Remote {
  public void setNextMessage(String message) throws RemoteException;
}
9. ChatServlet.java

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

public class ChatServlet extends RemoteDaemonHttpServlet
                         implements ChatServer {

  // source acts as the distributor of new messages
  MessageSource source = new MessageSource();

  // socketClients holds references to all the socket-connected clients
  Vector socketClients = new Vector();

  // rmiClients holds references to all the RMI clients
  Vector rmiClients = new Vector();

  // doGet() returns the next message.  It blocks until there is one.
  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/plain");
    PrintWriter out = res.getWriter();

    // Return the next message (blocking)
    out.println(getNextMessage());
  }

  // doPost() accepts a new message and broadcasts it to all
  // the currently listening HTTP and socket clients.
  public void doPost(HttpServletRequest req, HttpServletResponse res)
                                throws ServletException, IOException {
    // Accept the new message as the "message" parameter
    String message = req.getParameter("message");

    // Broadcast it to all listening clients
    if (message != null) broadcastMessage(message);

    // Set the status code to indicate there will be no response
    res.setStatus(res.SC_NO_CONTENT);
  }

  // getNextMessage() returns the next new message.
  // It blocks until there is one.
  public String getNextMessage() {
    // Create a message sink to wait for a new message from the
    // message source.
    return new MessageSink().getNextMessage(source);
  }

  // broadcastMessage() informs all currently listening clients that there
  // is a new message.  Causes all calls to getNextMessage() to unblock.
  public void broadcastMessage(String message) {
    // Send the message to all the HTTP-connected clients by giving the
    // message to the message source
    source.sendMessage(message);
    
    // Directly send the message to all the socket-connected clients
    Enumeration enum = socketClients.elements();
    while (enum.hasMoreElements()) {
      Socket client = null;
      try {
        client = (Socket)enum.nextElement();
        PrintStream out = new PrintStream(client.getOutputStream());
        out.println(message);
      }
      catch (IOException e) {
        // Problem with a client, close and remote it
        try {
          if (client != null) client.close();
        }
        catch (IOException ignored) { }
        socketClients.removeElement(client);
      }
    }

    // Directly send the message to all RMI clients
    enum = rmiClients.elements();
    while (enum.hasMoreElements()) {
      ChatClient chatClient = null;
      try {
        chatClient = (ChatClient)enum.nextElement();
        chatClient.setNextMessage(message);
      }
      catch (RemoteException e) {
        // Problem communicating with a client, remove it
        deleteClient(chatClient);
      }
    }
  }

  protected int getSocketPort() {
    // We listen on port 2428 (look at a phone to see why)
      return 36440; // 2428;
  }

  public void handleClient(Socket client) {
    // We have a new socket client.  Add it to our list.
    socketClients.addElement(client);
  }

  public void addClient(ChatClient client) {
    // We have a new RMI client.  Add it to our list.
    rmiClients.addElement(client);
  }

  public void deleteClient(ChatClient client) {
    // Remote the specified client from our list.
    rmiClients.removeElement(client);
  }
}

// MessageSource acts as the source for new messages.
// Clients interested in receiving new messages can
// observe this object.
class MessageSource extends Observable {
  public void sendMessage(String message) {
    setChanged();
    notifyObservers(message);
  }
}

// MessageSink acts as the receiver of new messages.
// It listens to the source.
class MessageSink implements Observer {

  String message = null;  // set by update() and read by getNextMessage()

  // Called by the message source when it gets a new message
  synchronized public void update(Observable o, Object arg) {
    // Get the new message
    message = (String)arg;

    // Wake up our waiting thread
    notify();
  }

  // Gets the next message sent out from the message source
  synchronized public String getNextMessage(MessageSource source) {
    // Tell source we want to be told about new messages
    source.addObserver(this);

    // Wait until our update() method receives a message
    while (message == null) {
      try { wait(); } catch (Exception ignored) { }
    }

    // Tell source to stop telling us about new messages
    source.deleteObserver(this);

    // Now return the message we received
    // But first set the message instance variable to null
    // so update() and getNextMessage() can be called again.
    String messageCopy = message;
    message = null;
    return messageCopy;
  }
}
10. ChatServer.java

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface ChatServer extends Remote {
  public String getNextMessage() throws RemoteException;
  public void broadcastMessage(String message) throws RemoteException;

  public void addClient(ChatClient client) throws RemoteException;
  public void deleteClient(ChatClient client) throws RemoteException;
}
11. RemoteDaemonHttpServlet.java

import java.io.*;
import java.net.*; 

import java.rmi.*; 
import java.rmi.server.*; 
import java.rmi.registry.*; 

import java.util.*;

import javax.servlet.*;
import javax.servlet.http.*;

public abstract class RemoteDaemonHttpServlet    extends DaemonHttpServlet 
    implements Remote 
{
    protected Registry registry; 
    
    public void init(ServletConfig config) throws ServletException {
	
	super.init(config); 
	
	try {
	    
	    // export ourself 
	    UnicastRemoteObject.exportObject(this); 
	    
	    // register ourself
	    bind(); 
	    
	} catch(RemoteException e) {
	    
	    getServletContext().log(e, "Problem binding to RMI registry."); 
	    
	}
    }
    
    public void destroy() {
	
	super.destroy(); 
	
	// unregister yourself 
	unbind(); 
	
    }
    
    // returns the name under which we are to be registered
    protected String getRegistryName() {
	
	// first name choice is the "registryName" init parameter
	String name = getInitParameter("registryName"); 
	if (name != null) return name; 
	
	// fallback choice is the name of this class followed by your username
	return this.getClass().getName() + "ByDGerman"; 
	
    }
    
    // returns the port on which the registry server 
    // is listening (or should be listening)
    protected int getRegistryPort() {
	
	// first port choice is the "registryPort" init parameter 
	String name = getInitParameter("registryName"); 
	try { 
	    
	    return Integer.parseInt(getInitParameter("registryPort")); 
	    
	} catch (NumberFormatException e) {
	    
	    // fallback choice is the default registry port (1099) 
	    return Registry.REGISTRY_PORT; 
	}
	
    }
    
    protected void bind() {
	
	// try to find the appropriate registry already running 
	try {
	    
	    registry = LocateRegistry.getRegistry(getRegistryPort()); 
	    registry.list(); // verify it's alive and well 
	} catch (Exception e) {
	    
	    // couldn't get a valid registry
	    registry = null; 
	}
	
	// if we couldn't find it, we need to create it 
	// (equivalent to running "rmiregistry")
	if (registry == null) {
	    try {
		
		registry = LocateRegistry.createRegistry(getRegistryPort()); 
		
	    } catch (Exception e) {
		
		log("Could not get or create RMI registry on port " + 
		    getRegistryPort() + ": " + e.getMessage()); 
		
		return; 
	    }
	    
	}
	
	// if we get here, we must have a valid registry.
	// now register this servlet instance with that registry.
	// "rebind" to replace any other objects using our name
	
	try {
	    
	    registry.rebind(getRegistryName(), this); 
	    
	} catch (Exception e) {
	    
	    log("Could not bind to RMI registry: " + e.getMessage()); 
	    return; 
	}
	
    }
    
    protected void unbind() {
	
	try {
	    
	    if (registry != null) registry.unbind(getRegistryName()); 
	    
	} catch (Exception e) {
	    
	    getServletContext().log(e, "Problem unbinding from RMI registry");
	    
	}
	
    }  
}
12. DaemonHttpServlet.java

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

public abstract class DaemonHttpServlet extends HttpServlet {
    protected int DEFAULT_PORT = 36600; // not static or final 
    private Thread daemonThread; 
    
    public void init(ServletConfig config) throws ServletException {
	super.init(config); 
	// start a daemon thread 
	try {
	    daemonThread = new Daemon(this); 
	    daemonThread.start(); 
	} catch (Exception e) {
	    this.getServletContext().log
		(e, "Problem starting socket server daemon thread"); 
	}
    }
    
    // returns the socket port on which this servlet will listen
    // a servlet can specify the port in three ways: 
    //  1. by using the socketPort init parameter, 
    //  2. by setting the DEFAULT_PORT variable before calling super.init(), or
    //  3. by overriding this method's implementation
    protected int getSocketPort() {
	try {
	    return Integer.parseInt(getInitParameter("socketPort")); 
	} catch (NumberFormatException e) {
	    return DEFAULT_PORT; 
	}
    }
    
    abstract public void handleClient(Socket client);
    
    public void destroy() {
	// stop the daemon thread 
	try {
	    daemonThread.stop();
	    daemonThread = null; 
	} catch (Exception e) {
	    getServletContext().log
		(e, "Problem stopping server socket daemon thread"); 
	}
    }
    
}

// this work is broken into a helper class so that subclasses of 
// DaemonHttpServlet can define their own run() methods w/out problems...
class Daemon extends Thread {
    private ServerSocket serverSocket; 
    private DaemonHttpServlet servlet;
    public Daemon(DaemonHttpServlet servlet) {
	this.servlet = servlet; 
    }
    public void run() {
	// create a server socket to accept connections
	try {
	    serverSocket = new ServerSocket(servlet.getSocketPort()); 
	} catch (Exception e) {
	    servlet.getServletContext().log
		(e, "Problem establishing server socket"); 
	    return; 
	}
	
	try {
	    while (true) {
		// as each connection comes in, call the servlet's 
		// handleClient(). Note this method is blocking. It's
		// the servlet responsibility to spawn a handler thread
		// for long-running connections (as DaytimeServlet)
		try { 
		    servlet.handleClient(serverSocket.accept()); 
		} catch(IOException ioe) {
		    servlet.getServletContext()
			.log(ioe,
			     "Problem accepting client's socket connection");
		    
		}
	    }
	} catch (ThreadDeath e) {
	    // when the thread is killed, close the server socket
	    try {
		serverSocket.close(); 
	    } catch (IOException ioe) {
		servlet.getServletContext()
		    .log(ioe,
			 "Problem closing server socket"); 
	    }
	    
	} 
    }
}

Here are my entry points:

Don't forget to use both the browser and the appletviewer for the last client.


Last updated: Apr 9, 2002 by Adrian German for A348/A548