![]() |
CSCI A348/548
|
We aim to present the web based chat application here, line by line.
Thanks to Jaz for making the suggestion that we go through it step by step.
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.
We name the class (and since we make itpublic class ChatApplet extends Applet implements Runnable {
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);
and a text field whose goal is to collect the messages from clients:label = new Label("Type here: ");
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());
and the new panel at the bottom (in the south)add("Center", text);
Theadd("South", panel);
panel
contains two widgets: a label on its left
and the text field on its rightpanel.add("West", label);
We're done with the GUI now. The rest of the interface is event-handling.panel.add("Center", input);
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.
Forever (while true, that is),public void run() {
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.
And for as long as there is no messagewhile (nextMessage == null) {
get a URL to the servlet (offered by the same web server)try {
And based on thatURL servlet = new URL( getCodeBase(), "/examples/servlet/ChatServlet");
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
If an exception is thrown (something goes wrong),} catch (Exception e) {
we sleep 5 seconds andtry { Thread.sleep(5000); }
start again (we don't do anything when the thread wakes up).catch (InterruptedException ignored) { }
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.
This one stops the current thread.public void stop() {
thread.stop();
thread = null;
But we didn't include this in our applet code.}
Here's how the message gets sent to the server.
If we have avoid broadcastMessage(String message) {
String
that we call message
,
then we prefix it with the user's namemessage = user + ": " + message;
and attempt the transmission (withtry {
POST
) over the network.
This is where we want to send it: (I used a shortcut for) theURL url = new URL( getCodeBase(), "/examples/servlet/ChatServlet");
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
.
That's what we do when we create this entry in theprops.put("message", message);
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.
Let's ignore the errors in this stage.} catch (Exception ignored) { }
And we're done describing how the applet handles broadcasting. It's all in the}
HttpMessage
object that has all the required functionality.
This is the part of the applet that notices user input.public boolean handleEvent(Event event) {
Check the type of the event the user has produced.switch (event.id) {
If it's an action event (as opposed to, for example, mouse event).case Event.ACTION_EVENT:
If it comes from theif (event.target == input) {
input
text field.
Get the text from the field and broadcast it.broadcastMessage(input.getText() + "\n");
Then clear the text.input.setText("");
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
.
This is where the class starts. It does not extend anything.public class HttpMessage {
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 we have data for the servlet pass it on the URL followed by theif (args != null) { argString = "?" + toEncodedString(args); }
?
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.
First, not sending anything is done by calling the namesake method with an argument ofpublic InputStream sendPostMessage() throws IOException {
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 it's not an invocation started from the method with no arguments.if (args != null) {
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();
Get it in this variable.while (names.hasMoreElements()) {
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.
And when we're done we're done.if (names.hasMoreElements()) buf.append("&");
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.*;
This class is anpublic class ChatServer extends HttpServlet { MessageSource source = new MessageSource();
Observable
and is used to collect broadcasted messages.
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 throughpublic void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/plain"); PrintWriter out = res.getWriter(); out.println(getNextMessage());
out
back to the client
that issued the GET request in the first place.
}
When something gets POST-ed it's apublic void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String message = req.getParameter("message"); if (message != null) broadcastMessage(message);
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.
This only creates a new sink and passes the buck to it.res.setStatus(res.SC_NO_CONTENT); } public String getNextMessage() {
This delegates the work to thereturn new MessageSink().getNextMessage(source); } public void broadcastMessage(String message) {
MessageSource
. There's only one source but as many sinks as clients.
Read aboutsource.sendMessage(message); } } class MessageSource extends Observable
Observable
s here:
Anjava.util.Observable
Observable
is an object that holds some data. An When a change is made to this observable object, the set of observers are notified by the change.{ public void sendMessage(String message) { setChanged();
The observable object must be a subclass of thenotifyObservers(message); } } class MessageSink implements Observer
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
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{ String message = null; synchronized public void update(Observable o, Object arg) { message = (String)arg; notify(); }
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.
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.synchronized public String getNextMessage(MessageSource source) { source.addObserver(this); while (message == null) { try { wait(); } catch (Exception ignored) { } }
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 thesource.deleteObserver(this); String messageCopy = message; message = null; return messageCopy; } }
$myServlets
directories and get ready to run the
application. For more info on source to sinks communication see
last example of Tuesday 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.
A348/A548