CSCI A348/A548

Lecture Notes 27

Fall 1999


In which we build a chat application from a web client (applet) and a web servlet (Java servlet) that communicate using the HTTP protocol. This is an implementation different from the RMI approach exemplified in the lectures before. It's used here to show alternative means to achieve the same results. A comparison with RMI is included at the end, for us to know the differences, later.

This is going to be an involved assignment. At the end you will have seen a lot of essential Java elements in action, and will have solved in the process your homework 8 assignment and a great deal (more than 90%) of your semester group project. For this project teams could merge, creating larger teams, if you think it helps you.

I start by creating all the code in a directory

/u/dgerman/November/apache_1.3.9/htdocs/hw8
That's where I will create the following source code files:
tucotuco.cs.indiana.edu% ls -l
total 4
-rw-r--r--   1 dgerman  students      708 Nov 30 12:09 ChatServer.java
-rw-r--r--   1 dgerman  students     1254 Nov 30 09:07 HttpChatApplet.java
-rw-r--r--   1 dgerman  students      645 Nov 30 10:13 HttpMessage.java
tucotuco.cs.indiana.edu% pwd
/nfs/paca/home/user1/dgerman/November/apache_1.3.9/htdocs/1201
tucotuco.cs.indiana.edu% 
Well, all right, then. What do the files contain?

HttpChatApplet.java looks like this:

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() 
  {
    URL codebase = getCodeBase(); 

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

    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(), "/servlet/ChatServer");  
        HttpMessage     msg  = new HttpMessage(url);  
        InputStream     in   = msg.sendGetMessage(); 
        DataInputStream data = new DataInputStream(
                                 new BufferedInputStream(
                                   in)); 
        nextMessage          = data.readLine(); 
      } catch (Exception e) { 
        try { Thread.sleep(5000); } 
        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; 
    try {
      URL url          = new URL(getCodeBase(), "/servlet/ChatServer"); 
      HttpMessage msg  = new HttpMessage(url); 
      Properties props = new Properties(); 
      props.put("message", message); 
      msg.sendPostMessage(props); 
    } catch (Exception ignored) { } 
  } 

  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; 
  } 
}
Notice how it talks to the server named ChatServer by using an object of type HttpMessage that is exemplified below. (We also notice that it makes use of the 1.0 (deprecated) version of the AWT but that can be fixed easily).
import java.io.*;
import java.net.*;
import java.util.*;

public class HttpMessage {

  URL    servlet = null; 
  String args    = 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); 
    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"
    ); 

    DataOutputStream out = new DataOutputStream(con.getOutputStream()); 
    out.writeBytes(argString); 
    out.flush(); 
    out.close(); 

    return con.getInputStream(); 
  } 

  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(); 
  } 
}
Now that we have these two files we can compile the applet, and two classes are going to be created, one for the applet the other one for the helper class (that abstracts HTTP messages). Now we write the servlet:
import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*; 

public class ChatServer extends HttpServlet { 

  MessageSource source = new MessageSource(); 

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

  public void doPost(HttpServletRequest req, HttpServletResponse res) 
    throws ServletException, IOException 
  { 
    String message = req.getParameter("message");
    if (message != null) broadcastMessage(message); 
    res.setStatus(res.SC_NO_CONTENT);     
  }

  public String getNextMessage() 
  {
    return new MessageSink().getNextMessage(source); 
  }

  public void broadcastMessage(String message) 
  {
    source.sendMessage(message); 
  }
} 

class MessageSource 
  extends Observable 
{
  public void sendMessage(String message) 
  {
    setChanged();
    notifyObservers(message); 
  } 
}

class MessageSink 
  implements Observer 
{
  String message = null; 

  synchronized public void update(Observable o, Object arg) 
  {
    message = (String)arg; 
    notify(); 
  } 
  
  synchronized public String getNextMessage(MessageSource source) 
  {
    source.addObserver(this); 

    while (message == null) {
      try {
        wait(); 
      } catch (Exception ignored) { } 
    } 

    source.deleteObserver(this); 
    String messageCopy = message;
    message = null;
    return messageCopy; 
  } 
}
This file contains the source code for three classes. If we compile it we obtain three class files. Once we obtain the bytecode files we move them to the servlets directories and get ready to run the application.

But how do we distribute the clients?

We need to send the applets to the browsers.

So we create three files which differ in only one place.

First we create Larry.html,

<html>
<applet code=HttpChatApplet
        codebase=/1201
        width=400 height=400>
<param name=user value="Larry"> 
</applet>
</html>
then Michael.html,
<html>
<applet code=HttpChatApplet
        codebase=/1201
        width=400 height=400>
<param name=user value="Michael">
</applet>
</html>
and finally Tony.html:
<html>
<applet code=HttpChatApplet
        codebase=/1201
        width=400 height=400>
<param name=user value="Tony">
</applet>
</html>
They differ only in the value of one parameter (the user's name, marked in red). Now you can connect to either one of them and participate in the conversation under one of these three names:


What's left?

You need to

RMI vs. HTTP:
  1. The code for RMI is simpler, closer to the problem to be solved
  2. The ancillary compilation phases are much more complicated for RMI
  3. For HTTP each file is compiled independently
  4. The code for HTTP is slightly more complicated, but only just


Last updated: December 2, 1999 by Adrian German