|
Fall Semester 2002 |
This is an older set of notes, brought to the front for this semester.
We start with the applet, which is our client program. The applet by itself is not enough for our understanding of the client-side software of the chat application, we also need to look into a helper class, that abstracts HTTP messages, but we'll do that shortly. Meanwhile we start from the applet class; we import a few packages, for a number of necessary classes of objects.
We need to import this package so that we can extendimport java.applet.*;
Applet and refer to
it by its short name in the process.
Some of the classes that we will be using are defined in this package, e.g.:import java.awt.*;
TextArea
Label
BorderLayout
Panel
Event
Label instead of java.awt.Label
and so forth.
The classes that we need here are:import java.io.*;
InputStream
DataInputStream
BufferedInputStream
This package contains theimport java.net.*;
URL class.
Forimport java.util.*;
Properties lists.
public class ChatApplet extends Applet implements Runnable {
We name the class (and since we make it public we make sure that the
name of the file matches exactly the name of the class, java) and extend
Applet and implement the Runnable interface.
This way the run() method provided by this applet will be executed in
parallel with anything else that the applet may be experiencing (other methods of the
applet).
Next we define the widgets that this applet will be using for its GUI.
This is a rectangular text area.TextArea text;
This is a label, that is, a piece of text.Label label;
This is a text field.TextField input;
We have not described yet how we are going to place them on the screen. All we did was to declare variables (names) that could store pointers to such objects one we create some, for our purposes.
This class is defined in theThread thread;
java.lang package that is imported by default.
Same thing forString user;
String, except that the functionality is much different.
Ever applet has anpublic void init()
init() method. Here we override it with the following definition:
{
Get aURL codebase = getCodeBase();
URL object, that represents the place on the web where this applet came from.
getCodeBase() is an instance method defined in class Applet.
user = getParameter("user");
getParameter() is another one.
This line retrieves the text that is associated with the applet's user parameter
from the HTML that serves it up. (See Larry.html, Michael.html,
Tony.html and so forth).
If there is no such parameter we'll call the user "anonymous".if (user == null) user = "anonymous";
Note that atext = new TextArea();
TextArea extends (and inherits from) TextComponent. Now we create a few GUI objects: a textarea, that we then set as not editable,
a label, that prompts the user to participate in the discussiontext.setEditable(false);
label = new Label("Type here: ");
and a text field whose goal is to collect the messages from clients:
This being used as input channel for the humans using the client software, it must be editable, unlike the text area (which is a read-only area).input = new TextField();
(Please find theinput.setEditable(true);
setEditable method in the
TextField's
web description page). So the text field is readable and writeable at the same time.
Now we set a graphics layout.
Every applet is a
Panel,
and every Panel is a
Container.
We set the layout for this applet to be a border layout.
Then we create a separatesetLayout(new BorderLayout());
Panel that we call panel,
and set a border layout for it too, if we ever put things in it.Panel panel = new Panel();
We place the text area in the center of the applet:panel.setLayout(new BorderLayout());
add("Center", text);
and the new panel at the bottom (in the south)
add("South", panel);
The panel contains two widgets: a label on its left
panel.add("West", label);
and the text field on its right
panel.add("Center", input);
We're done with the GUI now. The rest of the interface is event-handling.
The init() method ends with the applet showing where it's coming from.
tex.appendText("URL: " + codebase + "\n");
Every applet has a}
start() method.
public void start()
{
Ours creates a new Thread whose run() method is defined in this class.
And then starts the thread.thread = new Thread(this);
Once we look at thethread.start();
run() we'll be able to realize what we're starting
here. This actually is the process of contacting the server to continuously update
the text area.
This was the applet's method}
start().
thread.start() calls the thread's run() method,
which continuously calls contactServer(), described below.
public void run() {
Forever (while true, that is),
update the text area by appending the text obtained from the server (if any).while (true)
That means that we block if nothing is coming (and see below).text.appendText(contactServer());
This is the "get next message" method.}
It returns a String, as sent to the server.
It has no arguments, it doesn't need any.String contactServer()
{
A local variable will serve as the place where the message will be assembled.
It'sString nextMessage = null;
null when the method starts.
while (nextMessage == null) {
And for as long as there is no message
try {
get a URL to the servlet (offered by the same web server)
URL servlet = new URL( getCodeBase(),
"/examples/servlet/ChatServlet");
And based on that url build an HttpMessage kind of object.
Once we have it, we can get anHttpMessage msg = new HttpMessage(servlet);
InputStream to read from the server
bu invokingInputStream in = msg.sendGetMessage();
sendGetMessage() on the HttpMessage object.
DataInputStream data = new DataInputStream(
new BufferedInputStream(
So we start reading lines of text from it (after we turn it into a buffered reader).in));
Notice that the process blocks untilnextMessage = data.readLine();
readLine() returns something
} catch (Exception e) {
If an exception is thrown (something goes wrong),
try { Thread.sleep(5000); }
we sleep 5 seconds and
catch (InterruptedException ignored) { }
start again (we don't do anything when the thread wakes up).
This is the end of the outer catch.}
The end of the}
while (nextMessage == null) ...
If the loop gets broken and a message can be collected it will be returned with a newline at the end.return nextMessage + "\n";
And that's the end of}
getNextMessage().
Every applet can have a stop() method.
public void stop() {
This one stops the current thread.
thread.stop();
thread = null;
But we didn't include this in our applet code.}
Here's how the message gets sent to the server.
void broadcastMessage(String message) {
If we have a String that we call message,
then we prefix it with the user's namemessage = user + ": " + message;
try {
and attempt the transmission (with POST) over the network.
URL url = new URL( getCodeBase(),
"/examples/servlet/ChatServlet");
This is where we want to send it: (I used a shortcut for) the http:// address of
the server where the applet came from, followed by the location of the
ChatServlet servlet. Once we have this connection we store
it in a url object. At this point it would be quite useful
to check the documentation on the URL objects in the
java.net package.
Then the method creates aHttpMessage msg = new HttpMessage(url);
HttpMessage object to communicate with that url. Notice
that objects of that kind get constructed around URLs, which the constructor receives as a parameter. This
object does all the "dirty" work involved in making the connection and sending the data.
We create a string of pairs, names and values separated byProperties props = new Properties();
= and joined by &'s.
In this case we only create one such pair where the name is message and the value is the actual
message.
props.put("message", message);
That's what we do when we create this entry in the Properties list.
Now we invoke themsg.sendPostMessage(props);
sendPostMessage() method on msg and it will send its
argument (props) to the URL around which it has been built.
} catch (Exception ignored) { }
Let's ignore the errors in this stage.
And we're done describing how the applet handles broadcasting. It's all in the}
HttpMessage
object that has all the required functionality.
public boolean handleEvent(Event event) {
This is the part of the applet that notices user input.
switch (event.id) {
Check the type of the event the user has produced.
If it's an action event (as opposed to, for example, mouse event).case Event.ACTION_EVENT:
if (event.target == input) {
If it comes from the input text field.
Get the text from the field and broadcast it.broadcastMessage(input.getText() + "\n");
input.setText("");
Then clear the text.
And let the event propagate through the hierarchy.return true;
That's AWT 1.0 but you can use it with no problem.}
Most browsers would support it.}
For all other events stop the processing of the event here.return false;
Using AWT 1.1 or even Swing (and JFC) requires newer browsers.}
This, therefore, was the applet.}
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 if you want to use AWT 1.1 you know how to do it).
We'll take a look now to the HttpMessage class. One way in which
it could work would be to establish a raw socket connection to the server and
proceed to speak HTTP. This approach would certainly work but it isn't at all
necessary. The higher-level
java.net.URL and
java.net.URLConnection
Let's do a quick walk-through of HttpMessage. HttpMessage
is designed to communicate with just one URL, the URL given in its constructor.
It can send multiple GET and/or POST requests to that URL, but it always communicates with just one URL, which is passed as an argument to its constructor.
We start by importing a few packages.
This is for the reading and writing we will need to do.import java.io.*;
This is for the networking of it.import java.net.*;
This is forimport java.util.*;
Properties.
public class HttpMessage {
This is where the class starts. It does not extend anything.
It defines an instance variable of typeURL servlet = null;
URL where it will keep the URL of
the servlet that it is supposed to communicate with. Read about objects of this type here:
A URL is essentially a string that describes how to locate a resource on the Internet. Thejava.net.URL
URL class represents URLs and provides
methods to construct and obtain components of the URL (its protocol, host
name, port number, etc.) In addition it provides methods that, after a
URL has been created, uses the URL to retrieve the resource identified
by the URL. It also supports lower-level methods, such as opening a
connection or imput stream to the server that is managing the resource
identified by the URL.
We also have another instance variable for the arguments that we will be passing to the servlet with which we communicate through the URL.String args = null;
This is the constructor, it receives a URL as a parameter.public HttpMessage(URL servlet)
{
And stores it, nothing unusual here.
It's like we store the name of the object.this.servlet = servlet;
Next two methods overload the same method name by defining a function with one argument and one with no arguments that uses the one with one argument where the passed argument is}
null.
This one here is the function with no arguments.public InputStream sendGetMessage() throws IOException
{
It simply calls its namesake with a parameter of null.
It's only a convention.return sendGetMessage(null);
This other one is expecting a}
Properties argument.
You can read about Properties objects here:
Thejava.util.Properties
Properties class is used to represent a properties list. Each
item on the list is called a property and consists of a property name
and a property value. Each porperty name and property value is a Unicode string.
If the property list is to be loaded or stored from IO streams, then syntactic rules
apply that the property names and values must follow.
Notice that this method returnes anpublic InputStream sendGetMessage(Properties args) throws IOException
InputStream.
{
This will correspond to a connection to the URL contacted with an url-encoded string.
We'll store that string here.String argString = "";
if (args != null) { argString = "?" + toEncodedString(args); }
If we have data for the servlet pass it on the URL followed by the ? sign. We've seen this in homework 2, and later, and in the notes of Tuesday.
Notice also how the arguments are encoded by our toEncodedString() function detailed further below.
So after we create a URL-encoded query string from the passed-in arguments, we append this query string to the savedURL url = new URL(servlet.toExternalForm() + argString);
URL, creating a new URL object. At this point, it
could elect to use this new URL (named url) to communicate with the servlet. A call
to would return anurl.openStream()
InputStream
that contains the response. But, unfortunately for our purposes, by default all connections made
using a URL object are cached. We don't want this - we want the most recent answer
that the servlet can provide. So we need to turn caching off. The URL class doesn't
directly support this low-level control, so we need to get the URL object's
URLConnection object's InputStream, which contains the servlet's
response. You can read about URLConnection kind of objects here:
Thejava.net.URLConnection
URLConnection class represents an active connection to the resource
identified by the URL.
We can get such an object by invoking an appropriate instance method onURLConnection con = url.openConnection();
url.
Once we have the url connnection we disable caching.con.setUseCaches(false);
And we obtain and return the input stream associated with that connection.return con.getInputStream();
The code}
HttpMessage uses to send a POST request is similar.
public InputStream sendPostMessage() throws IOException {
First, not sending anything is done by calling the namesake method with an argument of null.
So we only need to worry about this second method.return sendPostMessage(null);
The major difference with how it sends a GET request is that it directly writes the URL-encoded parameter information in the body of the request. This follows the protocol for how POST requests submit their information. The other difference is that it manually sets the request's content type to}
This should be set automatically by Java, but setting it manually works around a bug in some (older) versions of Netscape's browser. (Netscape is good though)."application/x-www-form-urlencoded"
So that's the signature of the method.public InputStream sendPostMessage(Properties args) throws IOException
{
And we start the body here.
This variable holds the data we want to send.String argString = "";
if (args != null) {
If it's not an invocation started from the method with no arguments.
We encode the string we want to send.argString = toEncodedString(args);
Then we open a connection.}
And once we have it we get ready to use it.URLConnection con = servlet.openConnection();
We say we want to read from it.con.setDoInput(true);
And we want to write to it.con.setDoOutput(true);
And no caching.con.setUseCaches(false);
con.setRequestProperty(
"Content-Type", "application/x-www-form-urlencoded"
Now we declare what we're going to send.);
Use the rightDataOutputStream out = new DataOutputStream(con.getOutputStream());
I/O object.
Write the characters one by one.out.writeBytes(argString);
Make sure they're all sent.out.flush();
Close the pipe.out.close();
Return it for the response to be read (if needed).return con.getInputStream();
That's how we POST.}
Encoding is done by likeprivate String toEncodedString(Properties args)
ReadParse of our first CGI module is expecting it.
{
We start with a string buffer.
And get ready to look through the names of the properties in a sequence.StringBuffer buf = new StringBuffer();
For each one, in turn.Enumeration names = args.propertyNames();
while (names.hasMoreElements()) {
Get it in this variable.
Get the value also.String name = (String) names.nextElement();
Put them together with anString value = args.getProperty(name);
= sign between them.
And if there's more make sure these strings are separated by ampersands (buf.append(URLEncoder.encode(name) + "=" + URLEncoder.encode(value));
&'s) as expected.
if (names.hasMoreElements()) buf.append("&");
And when we're done we're done.
So we return the buffer as a}
String.
And that's the end of the client side, really.return buf.toString();
We should mention however that}
HttpMessage is a general-purpose class
for HTTP communication. It doesn't have to be used by applets, and it doesn't have
to connect to servlets. It's usable by any Java client that needs to connect to an
HTTP resource.
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.
The servlet is really quite simple.
We're always importing something.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();
This class is an Observable and is used to collect broadcasted messages.
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
res.setContentType("text/plain");
PrintWriter out = res.getWriter();
out.println(getNextMessage());
This method (that processes a GET request) is calling another message below that is
simply creating a new message sink, and sets it waiting for the message source. Once
the source has the data the sink returns the message and that is returned by the
method invoked above and then sent over through out back to the client
that issued the GET request in the first place.
}
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
String message = req.getParameter("message");
if (message != null) broadcastMessage(message);
When something gets POST-ed it's a String, a message that's
broadcasted by one client. This is placed in the MessageSource
object, for all the waiting sinks to pick it up for their clients.
Setting the response status code to SC_NO_CONTENT indicates that
there is no content in the response.
res.setStatus(res.SC_NO_CONTENT);
}
public String getNextMessage()
{
This only creates a new sink and passes the buck to it.
return new MessageSink().getNextMessage(source);
}
public void broadcastMessage(String message)
{
This delegates the work to the MessageSource. There's only one source but as many sinks as clients.
Read aboutsource.sendMessage(message); } } class MessageSource extends Observable
Observables here:
Anjava.util.Observable
Observable is an object that holds some data. An {
public void sendMessage(String message)
{
setChanged();
When a change is made to this observable object, the set of observers are notified by the change.
notifyObservers(message);
}
}
class MessageSink
implements Observer
The observable object must be a subclass of the Observable class.
Each observer needs to implement the Observable interface. You can
read about it here:
In our program there are many observers but only one and the same observable.java.util.Observer
{
String message = null;
synchronized public void update(Observable o, Object arg)
{
message = (String)arg;
notify();
}
This is the first thing a sink has to do after creation. It's synchronized with
the update method through the monitor (lock) that each Object in Java
has. Once it starts it adds itself as an observer to the source and then waits to
be notified.
Notification comes from the synchronized method update(), above, and
it is called by the observed object (the source) when it has the data.
synchronized public String getNextMessage(MessageSource source)
{
source.addObserver(this);
while (message == null) {
try {
wait();
} catch (Exception ignored) { }
}
Once the notification comes we delete ourselves as an observer and
return the message to the client whose request created us in the first
place. That client will display the returned string and issue a new
request, that will immediately create a new, fresh sink.
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 $myServlets directories and get ready to run the
application. For more info on source to sinks communication see
last example of Thursday notes. 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.
An alternative would be to use a dispatcher servlet or CGI script.