A Web application

Thursday Mar 1st 2001
Share:

Now let’s consider creating an application to run on the Web, which will show Java in all its glory. Part of this application will be a Java program running on the Web server, and the other part will be an applet that’s downloaded to the browser. The applet collects information from the user and sends it back to the application running on the Web server. The task of the program will be simple: the applet will ask for the email address of the user, and after verifying that this address is reasonably legitimate (it doesn’t contain spaces, and it does contain an ‘@’ symbol) the applet will send the email address to the Web server. The application running on the server will capture the data and check a data file in which all of the email addresses are kept. If that address is already in the file, it will send back a message to that effect, which is displayed by the applet. If the address isn’t in the file, it is placed in the list and the applet is informed that the address was added successfully.

Bruce Eckel's Thinking in Java Contents | Prev | Next

Now let’s consider creating an application to run on the Web, which will show Java in all its glory. Part of this application will be a Java program running on the Web server, and the other part will be an applet that’s downloaded to the browser. The applet collects information from the user and sends it back to the application running on the Web server. The task of the program will be simple: the applet will ask for the email address of the user, and after verifying that this address is reasonably legitimate (it doesn’t contain spaces, and it does contain an ‘@’ symbol) the applet will send the email address to the Web server. The application running on the server will capture the data and check a data file in which all of the email addresses are kept. If that address is already in the file, it will send back a message to that effect, which is displayed by the applet. If the address isn’t in the file, it is placed in the list and the applet is informed that the address was added successfully.

Traditionally, the way to handle such a problem is to create an HTML page with a text field and a “submit” button. The user can type whatever he or she wants into the text field, and it will be submitted to the server without question. As it submits the data, the Web page also tells the server what to do with the data by mentioning the Common Gateway Interface (CGI) program that the server should run after receiving this data. This CGI program is typically written in either Perl or C (and sometimes C++, if the server supports it), and it must handle everything. First it looks at the data and decides whether it’s in the correct format. If not, the CGI program must create an HTML page to describe the problem; this page is handed to the server, which sends it back to the user. The user must then back up a page and try again. If the data is correct, the CGI program opens the data file and either adds the email address to the file or discovers that the address is already in the file. In both cases it must format an appropriate HTML page for the server to return to the user.

As Java programmers, this seems like an awkward way for us to solve the problem, and naturally, we’d like to do the whole thing in Java. First, we’ll use a Java applet to take care of data validation at the client site, without all that tedious Web traffic and page formatting. Then let’s skip the Perl CGI script in favor of a Java application running on the server. In fact, let’s skip the Web server altogether and simply make our own network connection from the applet to the Java application on the server!

As you’ll see, there are a number of issues that make this a more complicated problem than it seems. It would be ideal to write the applet using Java 1.1 but that’s hardly practical. At this writing, the number of users running Java 1.1-enabled browsers is small, and although such browsers are now commonly available, you’ll probably need to take into account that a significant number of users will be slow to upgrade. So to be on the safe side, the applet will be programmed using only Java 1.0 code. With this in mind, there will be no JAR files to combine .class files in the applet, so the applet should be designed to create as few .class files as possible to minimize download time.

Well, it turns out the Web server (the one available to me when I wrote the example) does have Java in it, but only Java 1.0! So the server application must also be written using Java 1.0.

The server application

Now consider the server application, which will be called NameCollector. What happens if more than one user at a time tries to submit their email addresses? If NameCollector uses TCP/IP sockets, then it must use the multithreading approach shown earlier to handle more than one client at a time. But all of these threads will try to write to a single file where all the email addresses will be kept. This would require a locking mechanism to make sure that more than one thread doesn’t access the file at once. A semaphore will do the trick, but perhaps there’s a simpler way.

If we use datagrams instead, multithreading is unnecessary. A single datagram socket will listen for incoming datagrams, and when one appears the program will process the message and send the reply as a datagram back to whomever sent the request. If the datagram gets lost, then the user will notice that no reply comes and can then re-submit the request.

When the server application receives a datagram and unpacks it, it must extract the email address and check the file to see if that address is there already (and if it isn’t, add it). And now we run into another problem. It turns out that Java 1.0 doesn’t quite have the horsepower to easily manipulate the file containing the email addresses (Java 1.1 does). However, the problem can be solved in C quite readily, and this will provide an excuse to show you the easiest way to connect a non-Java program to a Java program. A Runtime object for a program has a method called exec( ) that will start up a separate program on the machine and return a Process object. You can get an OutputStream that connects to standard input for this separate program and an InputStream that connects to standard output. All you need to do is write a program using any language that takes its input from standard input and writes the output to standard output. This is a convenient trick when you run into a problem that can’t be solved easily or quickly enough in Java (or when you have legacy code you don’t want to rewrite). You can also use Java’s native methods (see Appendix A) but those are much more involved.

The C program
The job of this non-Java application (written in C because Java wasn’t appropriate for CGI programming; if nothing else, the startup time is prohibitive) is to manage the list of email addresses. Standard input will accept an email address and the program will look up the name in the list to see if it’s already there. If not, it will add it and report success, but if the name is already there then it will report that. Don’t worry if you don’t completely understand what the following code means; it’s just one example of how you can write a program in another language and use it from Java. The particular programming language doesn’t really matter as long as it can read from standard input and write to standard output.

//: Listmgr.c
// Used by NameCollector.java to manage 
// the email list file on the server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BSIZE 250
 
int alreadyInList(FILE* list, char* name) {
  char lbuf[BSIZE];
  // Go to the beginning of the list:
  fseek(list, 0, SEEK_SET);
  // Read each line in the list:
  while(fgets(lbuf, BSIZE, list)) {
    // Strip off the newline:
    char * newline = strchr(lbuf, '\n');
    if(newline != 0) 
      *newline = '\0';
    if(strcmp(lbuf, name) == 0)
      return 1;
  }
  return 0;
}
 
int main() {
  char buf[BSIZE];
  FILE* list = fopen("emlist.txt", "a+t");
  if(list == 0) {
    perror("could not open emlist.txt");
    exit(1);
  }
  while(1) {
    gets(buf); /* From stdin */
    if(alreadyInList(list, buf)) {
      printf("Already in list: %s", buf);
      fflush(stdout);
    }
    else {
      fseek(list, 0, SEEK_END);
      fprintf(list, "%s\n", buf);
      fflush(list);
      printf("%s added to list", buf);
      fflush(stdout);
    }
  }
} ///:~ 

This assumes that the C compiler accepts ‘//’ style comments. (Many do, and you can also compile this program with a C++ compiler.) If yours doesn’t, simply delete those comments.

The first function in the file checks to see whether the name you hand it as a second argument (a pointer to a char) is in the file. Here, the file is passed as a FILE pointer to an already-opened file (the file is opened inside main( )). The function fseek( ) moves around in the file; here it is used to move to the top of the file. fgets( ) reads a line from the file list into the buffer lbuf, not exceeding the buffer size BSIZE. This is inside a while loop so that each line in the file is read. Next, strchr( ) is used to locate the newline character so that it can be stripped off. Finally, strcmp( ) is used to compare the name you’ve passed into the function to the current line int the file. strcmp( ) returns zero if it finds a match. In this case the function exits and a one is returned to indicate that yes, the name was already in the list. (Note that the function returns as soon as it discovers the match, so it doesn’t waste time looking at the rest of the list.) If you get all the way through the list without a match, the function returns zero.

In main( ), the file is opened using fopen( ). The first argument is the file name and the second is the way to open the file; a+ means “Append, and open (or create if the file does not exist) for update at the end of the file.” The fopen( ) function returns a FILE pointer which, if it’s zero, means that the open was unsuccessful. This is dealt with by printing an error message with perror( ) and terminating the program with exit( ).

Assuming that the file was opened successfully, the program enters an infinite loop. The function call gets(buf) gets a line from standard input (which will be connected to the Java program, remember) and places it in the buffer buf. This is simply passed to the alreadyInList( ) function, and if it’s already in the list, printf( ) sends that message to standard output (where the Java program is listening). fflush( ) is a way to flush the output buffer.

If the name is not already in the list, fseek( ) is used to move to the end of the list and fprintf( ) “prints” the name to the end of the list. Then printf( ) is used to indicate that the name was added to the list (again flushing standard output) and the infinite loop goes back to waiting for a new name.

Remember that you usually cannot compile this program on your computer and load it onto the Web server machine, since that machine might use a different processor and operating system. For example, my Web server runs on an Intel processor but it uses Linux, so I must download the source code and compile using remote commands (via telnet) with the C compiler that comes with the Linux distribution.

The Java program
This program will first start the C program above and make the necessary connections to talk to it. Then it will create a datagram socket that will be used to listen for datagram packets from the applet.

//: NameCollector.java
// Extracts email names from datagrams and stores
// them inside a file, using Java 1.02.
import java.net.*;
import java.io.*;
import java.util.*;
 
public class NameCollector {
  final static int COLLECTOR_PORT = 8080;
  final static int BUFFER_SIZE = 1000;
  byte[] buf = new byte[BUFFER_SIZE];
  DatagramPacket dp = 
    new DatagramPacket(buf, buf.length);
  // Can listen & send on the same socket:
  DatagramSocket socket;
  Process listmgr;
  PrintStream nameList;
  DataInputStream addResult;
  public NameCollector() {
    try {
      listmgr =
        Runtime.getRuntime().exec("listmgr.exe");
      nameList = new PrintStream(
        new BufferedOutputStream(
          listmgr.getOutputStream()));
      addResult = new DataInputStream(
        new BufferedInputStream(
          listmgr.getInputStream()));
 
    } catch(IOException e) {
      System.err.println(
        "Cannot start listmgr.exe");
      System.exit(1);
    }
    try {
      socket =
        new DatagramSocket(COLLECTOR_PORT);
      System.out.println(
        "NameCollector Server started");
      while(true) {
        // Block until a datagram appears:
        socket.receive(dp);
        String rcvd = new String(dp.getData(),
            0, 0, dp.getLength());
        // Send to listmgr.exe standard input:
        nameList.println(rcvd.trim());
        nameList.flush();
        byte[] resultBuf = new byte[BUFFER_SIZE];
        int byteCount = 
          addResult.read(resultBuf);
        if(byteCount != -1) {
          String result = 
            new String(resultBuf, 0).trim();
          // Extract the address and port from 
          // the received datagram to find out 
          // where to send the reply:
          InetAddress senderAddress =
            dp.getAddress();
          int senderPort = dp.getPort();
          byte[] echoBuf = new byte[BUFFER_SIZE];
          result.getBytes(
            0, byteCount, echoBuf, 0);
          DatagramPacket echo =
            new DatagramPacket(
              echoBuf, echoBuf.length,
              senderAddress, senderPort);
          socket.send(echo);
        }
        else
          System.out.println(
            "Unexpected lack of result from " +
            "listmgr.exe");
      }
    } catch(SocketException e) {
      System.err.println("Can't open socket");
      System.exit(1);
    } catch(IOException e) {
      System.err.println("Communication error");
      e.printStackTrace();
    }
  }
  public static void main(String[] args) {
    new NameCollector();
  }
} ///:~ 

The first definitions in NameCollector should look familiar: the port is chosen, a datagram packet is created, and there’s a handle to a DatagramSocket. The next three definitions concern the connection to the C program: a Process object is what comes back when the C program is fired up by the Java program, and that Process object produces the InputStream and OutputStream objects representing, respectively, the standard output and standard input of the C program. These must of course be “wrapped” as is usual with Java IO, so we end up with a PrintStream and DataInputStream.

All the work for this program happens inside the constructor. To start up the C program, the current Runtime object is procured. This is used to call exec( ), which returns the Process object. You can see that there are simple calls to produce the streams from the Process object: getOutputStream( ) and getInputStream( ). From this point on, all you need to consider is sending data to the stream nameList and getting the results from addResult.

As before, a DatagramSocket is connected to a port. Inside the infinite while loop, the program calls receive( ), which blocks until a datagram shows up. When the datagram appears, its contents are extracted into the String rcvd . This is trimmed to remove white space at each end and sent to the C program in the line:

nameList.println(rcvd.trim());

This is only possible because Java’s exec( ) provides access to any executable that reads from standard input and writes to standard output. There are other ways to talk to non-Java code, which are discussed in Appendix A.

Capturing the result from the C program is slightly more complicated. You must call read( ) and provide a buffer where the results will be placed. The return value for read( ) is the number of bytes that came from the C program, and if this value is -1 it means that something is wrong. Otherwise, the resultBuf is turned into a String and the spaces are trimmed off. This string is then placed into a DatagramPacket as before and shipped back to the same address that sent the request in the first place. Note that the sender’s address is part of the DatagramPacket we received.

Remember that although the C program must be compiled on the Web server, the Java program can be compiled anywhere since the resulting byte codes will be the same regardless of the platform on which the program will be running.

The NameSender applet

As mentioned earlier, the applet must be written with Java 1.0 so that it will run on the largest number of browsers, so it’s best if the number of classes produced is minimized. Thus, instead of using the Dgram class developed earlier, all of the datagram manipulations will be placed in line. In addition, the applet needs a thread to listen for the reply from the server, and instead of making this a separate thread it’s integrated into the applet by implementing the Runnable interface. This isn’t as easy to read, but it produces a one-class (and one-server-hit) applet:

//: NameSender.java
// An applet that sends an email address
// as a datagram, using Java 1.02.
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;
 
public class NameSender extends Applet 
    implements Runnable {
  private Thread pl = null;
  private Button send = new Button(
    "Add email address to mailing list");
  private TextField t = new TextField(
    "type your email address here", 40);
  private String str = new String();
  private Label 
    l = new Label(), l2 = new Label();
  private DatagramSocket s; 
  private InetAddress hostAddress;
  private byte[] buf = 
    new byte[NameCollector.BUFFER_SIZE];
  private DatagramPacket dp =
    new DatagramPacket(buf, buf.length);
  private int vcount = 0;
  public void init() {
    setLayout(new BorderLayout());
    Panel p = new Panel();
    p.setLayout(new GridLayout(2, 1));
    p.add(t);
    p.add(send);
    add("North", p);
    Panel labels = new Panel();
    labels.setLayout(new GridLayout(2, 1));
    labels.add(l);
    labels.add(l2);
    add("Center", labels);
    try {
      // Auto-assign port number:
      s = new DatagramSocket();
      hostAddress = InetAddress.getByName(
        getCodeBase().getHost());
    } catch(UnknownHostException e) {
      l.setText("Cannot find host");
    } catch(SocketException e) {
      l.setText("Can't open socket");
    } 
    l.setText("Ready to send your email address");
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(send)) {
      if(pl != null) {
        // pl.stop(); Deprecated in Java 1.2
        Thread remove = pl;
        pl = null;
        remove.interrupt();
      }
      l2.setText("");
      // Check for errors in email name:
      str = t.getText().toLowerCase().trim();
      if(str.indexOf(' ') != -1) {
        l.setText("Spaces not allowed in name");
        return true;
      }
      if(str.indexOf(',') != -1) {
        l.setText("Commas not allowed in name");
        return true;
      }
      if(str.indexOf('@') == -1) {
        l.setText("Name must include '@'");
        l2.setText("");
        return true;
      }
      if(str.indexOf('@') == 0) {
        l.setText("Name must preceed '@'");
        l2.setText("");
        return true;
      }
      String end = 
        str.substring(str.indexOf('@'));
      if(end.indexOf('.') == -1) {
        l.setText("Portion after '@' must " +
          "have an extension, such as '.com'");
        l2.setText("");
        return true;
      }
      // Everything's OK, so send the name. Get a
      // fresh buffer, so it's zeroed. For some 
      // reason you must use a fixed size rather
      // than calculating the size dynamically:
      byte[] sbuf = 
        new byte[NameCollector.BUFFER_SIZE];
      str.getBytes(0, str.length(), sbuf, 0);
      DatagramPacket toSend =
        new DatagramPacket(
          sbuf, 100, hostAddress,
          NameCollector.COLLECTOR_PORT);
      try {
        s.send(toSend);
      } catch(Exception e) {
        l.setText("Couldn't send datagram");
        return true;
      }
      l.setText("Sent: " + str);
      send.setLabel("Re-send");
      pl = new Thread(this);
      pl.start();
      l2.setText(
        "Waiting for verification " + ++vcount);
    }
    else return super.action(evt, arg);
    return true;
  }
  // The thread portion of the applet watches for
  // the reply to come back from the server:
  public void run() {
    try {
      s.receive(dp);
    } catch(Exception e) {
      l2.setText("Couldn't receive datagram");
      return;
    }
    l2.setText(new String(dp.getData(),
      0, 0, dp.getLength()));
  }
} ///:~ 

The UI for the applet is quite simple. There’s a TextField in which you type your email address, and a Button to send the email address to the server. Two Labels are used to report status back to the user.

By now you can recognize the DatagramSocket, InetAddress, buffer, and DatagramPacket as trappings of the network connection. Lastly, you can see the run( ) method that implements the thread portion so the applet can listen for the reply sent back by the server.

The init( ) method sets up the GUI with the familiar layout tools, then creates the DatagramSocket that will be used both for sending and receiving datagrams.

The action( ) method (remember, we’re confined to Java 1.0 now, so we can’t use any slick inner listener classes) watches only to see if you press the “send” button. When the button is pressed, the first action is to check the Thread pl to see if it’s null. If it’s not null, there’s a live thread running. The first time the message is sent a thread is started up to watch for the reply. Thus, if a thread is running, it means this is not the first time the user has tried to send the message. The pl handle is set to null and the old listener is interrupted. (This is the preferred approach, since stop( ) is deprecated in Java 1.2 as explained in the previous chapter.)

Regardless of whether this is the first time the button was pressed, the text in l2 is erased.

The next group of statements checks the email name for errors. The String.indexOf( ) method is used to search for illegal characters, and if one is found it is reported to the user. Note that all of this happens without any network activity, so it’s fast and it doesn’t bog down the Internet.

Once the name is verified, it is packaged into a datagram and sent to the host address and port number in the same way that was described in the earlier datagram example. The first label is changed to show you that the send has occurred, and the button text is changed so that it reads “re-send.” At this point, the thread is started up and the second label informs you that the applet is waiting for a reply from the server.

The run( ) method for the thread uses the DatagramSocket that lives in NameSender to receive( ), which blocks until the datagram packet comes from the server. The resulting packet is placed into NameSender’s DatagramPacket dp . The data is retrieved from the packet and placed into the second label in NameSender. At this point, the thread terminates and becomes dead. If the reply doesn’t come back from the server in a reasonable amount of time, the user might become impatient and press the button again, thus terminating the current thread (and, after re-sending the data, starting a new one). Because a thread is used to listen for the reply, the user still has full use of the UI.

The Web page
Of course, the applet must go inside a Web page. Here is the complete Web page; you can see that it’s intended to be used to automatically collect names for my mailing list:

<HTML>
<HEAD>
<META CONTENT="text/html">
<TITLE>
Add Yourself to Bruce Eckel's Java Mailing List
</TITLE>
</HEAD>
<BODY LINK="#0000ff" VLINK="#800080" BGCOLOR="#ffffff">
<FONT SIZE=6><P>
Add Yourself to Bruce Eckel's Java Mailing List
</P></FONT>
The applet on this page will automatically add your email address to the mailing list, so you will receive update information about changes to the online version of "Thinking in Java," notification when the book is in print, information about upcoming Java seminars, and notification about the “Hands-on Java Seminar” Multimedia CD. Type in your email address and press the button to automatically add yourself to this mailing list. <HR>
<applet code=NameSender width=400 height=100>
</applet>
<HR>
If after several tries, you do not get verification it means that the Java application on the server is having problems. In this case, you can add yourself to the list by sending email to 
<A HREF="mailto:Bruce@EckelObjects.com">
Bruce@EckelObjects.com</A>
</BODY>
</HTML>

The applet tag is quite trivial, no different from the first one presented in Chapter 13.

Problems with this approach

This certainly seems like an elegant approach. There’s no CGI programming and so there are no delays while the server starts up a CGI program. The datagram approach seems to produce a nice quick response. In addition, when Java 1.1 is available everywhere, the server portion can be written entirely in Java. (Although it’s quite interesting to see how easy it is to connect to a non-Java program using standard input and output.)

There are problems, however. One problem is rather subtle: since the Java application is running constantly on the server and it spends most of its time blocked in the Datagram.receive( ) method, there might be some CPU hogging going on. At least, that’s the way it appeared on the server where I was experimenting. On the other hand, there wasn’t much else happening on that server, and starting the program using “nice” (a Unix program to prevent a process from hogging the CPU) or its equivalent could solve the problem if you have a more heavily-loaded server. In any event, it’s worth keeping your eye on an application like this – a blocked receive( ) could hog the CPU.

The second problem is a show stopper. It concerns firewalls. A firewall is a machine that sits between your network and the Internet. It monitors all traffic coming in from the Internet and going out to the Internet, and makes sure that traffic conforms to what it expects.

Firewalls are conservative little beasts. They demand strict conformance to all the rules, and if you’re not conforming they assume that you’re doing something sinful and shut you out (not quite so bad as the Spanish Inquisition, but close). For example, if you are on a network behind a firewall and you start connecting to the Internet using a Web browser, the firewall expects that all your transactions will connect to the server using the accepted http port, which is 80. Now along comes this Java applet NameSender, which is trying to send a datagram to port 8080, which is way outside the range of the “protected” ports 0-1024. The firewall naturally assumes the worst – that someone has a virus – and it doesn’t allow the transaction to happen.

As long as your customers have raw connections to the Internet (for example, using a typical Internet service provider) there’s no problem, but you might have some important customers dwelling behind firewalls, and they won’t be able to use your program.

This is rather disheartening after learning so much Java, because it would seem that you must give up Java on the server and learn how to write CGI scripts in C or Perl. But as it turns out, despair is not in order.

One scenario is part of Sun’s grand scheme. If everything goes as planned, Web servers will be equipped with servlet servers . These will take a request from the client (going through the firewall-accepted port 80) and instead of starting up a CGI program they will start up a Java program called a servlet. This is a little application that’s designed to run only on the server. A servlet server will automatically start up the servlet to handle the client request, which means you can write all your programs in Java (further enabling the “100 percent pure Java initiative”). It is admittedly an appealing idea: once you’re comfortable with Java, you don’t have to switch to a more primitive language to handle requests on the server.

Since it’s only for handling requests on the server, the servlet API has no GUI abilities. This fits quite well with NameCollector.java, which doesn’t have a GUI anyway.

At this writing, a low-cost servlet server was available from java.sun.com. In addition, Sun is encouraging other Web server manufacturers to add servlet capabilities to their servers.

Contents | Prev | Next
Share:
Home
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved