Connecting Java to CGI

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

Encoding data for CGI

In this version, the name and the email address will be collected and stored in the file in the form:

First Last <email@domain.com>;

&lt;Form method="GET" ACTION="/cgi-bin/Listmgr2.exe"&gt;
&lt;P&gt;Name: &lt;INPUT TYPE = "text" NAME = "name" 
VALUE = "" size = "40"&gt;&lt;/p&gt;
&lt;P&gt;Email Address: &lt;INPUT TYPE = "text" 
NAME = "email" VALUE = "" size = "40"&gt;&lt;/p&gt;
&lt;p&gt;&lt;input type = "submit" name = "submit" &gt; &lt;/p&gt;
&lt;/Form&gt;

This creates two data entry fields called name and email, along with a submit button that collects the data and sends it to a CGI program. Listmgr2.exe is the name of the executable program that resides in the directory that’s typically called “cgi-bin” on your Web server. [65] (If the named program is not in the cgi-bin directory, you won’t see any results.) If you fill out this form and press the “submit” button, you will see in the URL address window of the browser something like:

http://www.myhome.com/cgi-bin/Listmgr2.exe?

name=First+Last&email=email@domain.com&submit=Submit

(Without the line break, of course). Here you see a little bit of the way that data is encoded to send to CGI. For one thing, spaces are not allowed (since spaces typically separate command-line arguments). Spaces are replaced by ‘ +’ signs. In addition, each field contains the field name (which is determined by the HTML page) followed by an ‘ =’ and the field data, and terminated by a ‘ &’.

At this point, you might wonder about the ‘ +’, ‘ =,’ and ‘ &’. What if those are used in the field, as in “John & Marsha Smith”? This is encoded to:

John+%26+Marsha+Smith

That is, the special character is turned into a ‘%’ followed by its ASCII value in hex.

Fortunately, Java has a tool to perform this encoding for you. It’s a static method of the class URLEncoder called encode( ). You can experiment with this method using the following program:

//: EncodeDemo.java
// Demonstration of URLEncoder.encode()
import java.net.*;
 
public class EncodeDemo {
  public static void main(String[] args) {
    String s = "";
    for(int i = 0; i &lt; args.length; i++)
      s += args[i] + " ";
    s = URLEncoder.encode(s.trim());
    System.out.println(s);
  }
} ///:~ 

This takes the command-line arguments and combines them into a string of words separated by spaces (the final space is removed using String.trim( )). These are then encoded and printed.

The applet

//: NameSender2.java
// An applet that sends an email address
// via a CGI GET, using Java 1.02.
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;
 
public class NameSender2 extends Applet {
  final String CGIProgram = "Listmgr2.exe";
  Button send = new Button(
    "Add email address to mailing list");
  TextField name = new TextField(
    "type your name here", 40),
    email = new TextField(
    "type your email address here", 40);
  String str = new String();
  Label l = new Label(), l2 = new Label();
  int vcount = 0;
  public void init() {
    setLayout(new BorderLayout());
    Panel p = new Panel();
    p.setLayout(new GridLayout(3, 1));
    p.add(name);
    p.add(email);
    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);
    l.setText("Ready to send email address");
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(send)) {
      l2.setText("");
      // Check for errors in data:
      if(name.getText().trim()
         .indexOf(' ') == -1) {
        l.setText(
          "Please give first and last name");
        l2.setText("");
        return true;
      }
      str = email.getText().trim();
      if(str.indexOf(' ') != -1) {
        l.setText(
          "Spaces not allowed in email name");
        l2.setText("");
        return true;
      }
      if(str.indexOf(',') != -1) {
        l.setText(
          "Commas not allowed in email name");
        return true;
      }
      if(str.indexOf('@') == -1) {
        l.setText("Email name must include '@'");
        l2.setText("");
        return true;
      }
      if(str.indexOf('@') == 0) {
        l.setText(
          "Name must preceed '@' in email name");
        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;
      }
      // Build and encode the email data:
      String emailData = 
        "name=" + URLEncoder.encode(
          name.getText().trim()) +
        "&amp;email=" + URLEncoder.encode(
          email.getText().trim().toLowerCase()) +
        "&amp;submit=Submit";
      // Send the name using CGI's GET process:
      try {
        l.setText("Sending...");
        URL u = new URL(
          getDocumentBase(), "cgi-bin/" +
          CGIProgram + "?" + emailData);
        l.setText("Sent: " + email.getText());
        send.setLabel("Re-send");
        l2.setText(
          "Waiting for reply " + ++vcount);
        DataInputStream server =
          new DataInputStream(u.openStream());
        String line;
        while((line = server.readLine()) != null)
          l2.setText(line);
      } catch(MalformedURLException e) {
        l.setText("Bad URl");
      } catch(IOException e) {
        l.setText("IO Exception");
      } 
    }
    else return super.action(evt, arg);
    return true;
  }
} ///:~ 

The name of the CGI program (which you’ll see later) is Listmgr2.exe. Many Web servers are Unix machines (mine runs Linux) that don’t traditionally use the .exe extension for their executable programs, but you can call the program anything you want under Unix. By using the .exe extension the program can be tested without change under both Unix and Win32.

As before, the applet sets up its user interface (with two fields this time instead of one). The only significant difference occurs inside the action( ) method, which handles the button press. After the name has been checked, you see the lines:

      String emailData = 
        "name=" + URLEncoder.encode(
          name.getText().trim()) +
        "&amp;email=" + URLEncoder.encode(
          email.getText().trim().toLowerCase()) +
        "&amp;submit=Submit";
      // Send the name using CGI's GET process:
      try {
        l.setText("Sending...");
        URL u = new URL(
          getDocumentBase(), "cgi-bin/" +
          CGIProgram + "?" + emailData);
        l.setText("Sent: " + email.getText());
        send.setLabel("Re-send");
        l2.setText(
          "Waiting for reply " + ++vcount);
        DataInputStream server =
          new DataInputStream(u.openStream());
        String line;
        while((line = server.readLine()) != null)
          l2.setText(line); <p><tt>        // ... </tt></p>

Displaying a Web page from within an applet

It’s also possible for the applet to display the result of the CGI program as a Web page, just as if it were running in normal HTML mode. You can do this with the following line:

getAppletContext().showDocument(u);

//: ShowHTML.java
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;
 
public class ShowHTML extends Applet {
  static final String CGIProgram = "MyCGIProgram";
  Button send = new Button("Go");
  Label l = new Label();
  public void init() {
    add(send);
    add(l);
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(send)) {
      try {
        // This could be an HTML page instead of
        // a CGI program. Notice that this CGI 
        // program doesn't use arguments, but 
        // you can add them in the usual way.
        URL u = new URL(
          getDocumentBase(), 
          "cgi-bin/" + CGIProgram);
        // Display the output of the URL using
        // the Web browser, as an ordinary page:
        getAppletContext().showDocument(u);
      } catch(Exception e) {
        l.setText(e.toString());
      } 
    }
    else return super.action(evt, arg);
    return true;
  }
} ///:~ 

The CGI program in C++

At this point you could follow the previous example and write the CGI program for the server using ANSI C. One argument for doing this is that ANSI C can be found virtually everywhere. However, C++ has become quite ubiquitous, especially in the form of the GNU C++ Compiler [66] ( g++) that can be downloaded free from the Internet for virtually any platform (and often comes pre-installed with operating systems such as Linux). As you will see, this means that you can get the benefit of object-oriented programming in a CGI program.

To avoid throwing too many new concepts at you all at once, this program will not be a “pure” C++ program; some code will be written in plain C even though C++ alternatives exist. This isn’t a significant issue because the biggest benefit in using C++ for this program is the ability to create classes. Since what we’re concerned with when parsing the CGI information is the field name-value pairs, one class ( Pair) will be used to represent a single name-value pair and a second class ( CGI_vector) will automatically parse the CGI string into Pair objects that it will hold (as a vector) so you can fetch each Pair out at your leisure.

One of the reasons for using C++ here is the convenience of the C++ Standard Template Library . Among other things, the STL contains a vector class. This is a C++ template, which means that it will be configured at compile time so it will hold objects of only a particular type (in this case, Pair objects). Unlike the Java Vector, which will accept anything, the C++ vector template will cause a compile-time error message if you try to put anything but a Pair object into the vector, and when you get something out of the vector it will automatically be a Pair object, without casting. Thus, the checking happens at compile time and produces a more robust program. In addition, the program can run faster since you don’t have to perform run-time casts. The vector also overloads the operator[] so you have a convenient syntax for extracting Pair objects. The vector template will be used in the creation of CGI_vector, which you’ll see is a fairly short definition considering how powerful it is.

//: CGITools.h
// Automatically extracts and decodes data
// from CGI GETs and POSTs. Tested with GNU C++ 
// (available for most server machines).
#include &lt;string.h&gt;
#include &lt;vector&gt; // STL vector
using namespace std;
 
// A class to hold a single name-value pair from
// a CGI query. CGI_vector holds Pair objects and
// returns them from its operator[].
class Pair {
  char* nm;
  char* val;
public:
  Pair() { nm = val = 0; }
  Pair(char* name, char* value) {
    // Creates new memory:
    nm = decodeURLString(name);
    val = decodeURLString(value);
  }
  const char* name() const { return nm; }
  const char* value() const { return val; }
  // Test for "emptiness"
  bool empty() const {
    return (nm == 0) || (val == 0);
  }
  // Automatic type conversion for boolean test:
  operator bool() const {
    return (nm != 0) &amp;&amp; (val != 0);
  }
  // The following constructors &amp; destructor are
  // necessary for bookkeeping in C++.
  // Copy-constructor:
  Pair(const Pair&amp; p) {
    if(p.nm == 0 || p.val == 0) {
      nm = val = 0;
    } else {
      // Create storage &amp; copy rhs values:
      nm = new char[strlen(p.nm) + 1];
      strcpy(nm, p.nm);
      val = new char[strlen(p.val) + 1];
      strcpy(val, p.val);
    }
  }
  // Assignment operator:
  Pair&amp; operator=(const Pair&amp; p) {
    // Clean up old lvalues:
    delete nm;
    delete val;
    if(p.nm == 0 || p.val == 0) {
      nm = val = 0;
    } else {
      // Create storage &amp; copy rhs values:
      nm = new char[strlen(p.nm) + 1];
      strcpy(nm, p.nm);
      val = new char[strlen(p.val) + 1];
      strcpy(val, p.val);
    }
    return *this;
  } 
  ~Pair() { // Destructor
    delete nm; // 0 value OK
    delete val;
  }
  // If you use this method outide this class, 
  // you're responsible for calling 'delete' on
  // the pointer that's returned:
  static char* 
  decodeURLString(const char* URLstr) {
    int len = strlen(URLstr);
    char* result = new char[len + 1];
    memset(result, len + 1, 0);
    for(int i = 0, j = 0; i &lt;= len; i++, j++) {
      if(URLstr[i] == '+')
        result[j] = ' ';
      else if(URLstr[i] == '%') {
        result[j] =
          translateHex(URLstr[i + 1]) * 16 +
          translateHex(URLstr[i + 2]);
        i += 2; // Move past hex code
      } else // An ordinary character
        result[j] = URLstr[i];
    }
    return result;
  }
  // Translate a single hex character; used by
  // decodeURLString():
  static char translateHex(char hex) {
    if(hex &gt;= 'A')
      return (hex &amp; 0xdf) - 'A' + 10;
    else
      return hex - '0';
  }
};
 
// Parses any CGI query and turns it
// into an STL vector of Pair objects:
class CGI_vector : public vector&lt;Pair&gt; {
  char* qry;
  const char* start; // Save starting position
  // Prevent assignment and copy-construction:
  void operator=(CGI_vector&amp;);
  CGI_vector(CGI_vector&amp;);
public:
  // const fields must be initialized in the C++
  // "Constructor initializer list":
  CGI_vector(char* query) :
      start(new char[strlen(query) + 1]) {
    qry = (char*)start; // Cast to non-const
    strcpy(qry, query);
    Pair p;
    while((p = nextPair()) != 0)
      push_back(p);
  }
  // Destructor:
  ~CGI_vector() { delete start; }
private:
  // Produces name-value pairs from the query 
  // string. Returns an empty Pair when there's 
  // no more query string left:
  Pair nextPair() {
    char* name = qry;
    if(name == 0 || *name == '\0')
      return Pair(); // End, return null Pair
    char* value = strchr(name, '=');
    if(value == 0)
      return Pair(); // Error, return null Pair
    // Null-terminate name, move value to start
    // of its set of characters:
    *value = '\0';
    value++;
    // Look for end of value, marked by '&amp;':
    qry = strchr(value, '&amp;');
    if(qry == 0) qry = ""; // Last pair found
    else {
      *qry = '\0'; // Terminate value string
      qry++; // Move to next pair
    }
    return Pair(name, value);
  }
}; ///:~ 

After the #include statements, you see a line that says:

using namespace std;

Namespaces in C++ solve one of the problems taken care of by the package scheme in Java: hiding library names. The std namespace refers to the Standard C++ library, and vector is in this library so the line is required.

The Pair class starts out looking pretty simple: it just holds two ( private) character pointers, one for the name and one for the value. The default constructor simply sets these pointers to zero, since in C++ an object’s memory isn’t automatically zeroed. The second constructor calls the method decodeURLString( ) that produces a decoded string in newly-allocated heap memory. This memory must be managed and destroyed by the object, as you will see in the destructor. The name( ) and value( ) methods produce read-only pointers to the respective fields. The empty( ) method is a way for you to ask the Pair object whether either of its fields are empty; it returns a bool, which is C++’s built-in primitive Boolean data type. The operator bool( ) uses a special case of C++ operator overloading , which allows you to control automatic type conversion. If you have a Pair object called p and you use it in an expression in which a Boolean result is expected, such as if(p) { //... , then the compiler will recognize that it has a Pair and it needs a Boolean, so it will automatically call operator bool( ) to perform the necessary conversion.

The copy-constructor Pair(const Pair&) is automatically called whenever you pass an object into or out of a function by value . That is, you aren’t passing the address of the object you’re making a copy of the whole object inside the function frame. This isn’t an option in Java since you pass only handles, thus there’s no copy-constructor in Java. (If you want to make a local duplicate, you clone( ) the object – see Chapter 12.) Likewise, if you assign a handle in Java, it’s simply copied. But assignment in C++ means that the entire object is copied. In the copy-constructor, you create new storage and copy the source data, but with the assignment operator you must release the old storage before allocating new storage. What you’re seeing is probably the worst-case complexity scenario for a C++ class, but it’s one of the reasons Java proponents can argue that Java is a lot simpler than C++. In Java you pass handles and there’s a garbage collector, so you don’t have to do this kind of thing.

class Pair {
  string nm;
  string val;
public:
  Pair() { }
  Pair(char* name, char* value) {
    // Creates new memory:
    nm = decodeURLString(name);
    val = decodeURLString(value);
  }
  const char* name() const { return nm.c_str(); }
  const char* value() const { return val.c_str(); }
  // Test for "emptiness"
  bool empty() const {
    return (nm.length() == 0) || (val.length() == 0);
  }
  // Automatic type conversion for boolean test:
  operator bool() const {
    return (nm.length() != 0) &amp;&amp; (val.length() != 0);
  }

(Also, for this case decodeURLString( ) returns a string instead of a char*.) You don’t need to define a copy-constructor, operator=, or destructor because the compiler does that for you, and does it correctly. But even if it sometimes works automatically, C++ programmers must still know the details of copy-construction and assignment.

The remainder of the Pair class consists of the two methods decodeURLString( ) and a helper method translateHex( ), which is used by decodeURLString( ). (Note that translateHex( ) does not guard against bad user input such as “%1H.”) After allocating adequate storage (which must be released by the destructor), decodeURLString( ) moves through and replaces each ‘ +’ with a space and each hex code (beginning with a ‘ %’) with the appropriate character.

CGI_vector parses and holds an entire CGI GET command. It is inherited from the STL vector, which is instantiated to hold Pairs. Inheritance in C++ is denoted by using a colon at the point you’d say extends in Java. In addition, inheritance defaults to private so you’ll almost always need to use the public keyword as was done here. You can also see that CGI_vector has a copy-constructor and an operator=, but they’re both declared as private. This is to prevent the compiler from synthesizing the two functions (which it will do if you don’t declare them yourself), but it also prevents the client programmer from passing a CGI_vector by value or from using assignment.

CGI_vector’s job is to take the QUERY_STRING and parse it into name-value pairs, which it will do with the aid of Pair. First it copies the string into locally-allocated memory and keeps track of the starting address with the constant pointer start. (This is later used in the destructor to release the memory.) Then it uses its method nextPair( ) to parse the string into raw name-value pairs, delimited by ‘ =’ and &’ signs. These are handed by nextPair( ) to the Pair constructor so nextPair( ) can return the Pair object, which is then added to the vector with push_back( ). When nextPair( ) runs out of QUERY_STRING, it returns zero.

Now that the basic tools are defined, they can easily be used in a CGI program, like this:

//: Listmgr2.cpp
// CGI version of Listmgr.c in C++, which 
// extracts its input via the GET submission 
// from the associated applet. Also works as
// an ordinary CGI program with HTML forms.
#include &lt;stdio.h&gt;
#include "CGITools.h"
const char* dataFile = "list2.txt";
const char* notify = "Bruce@EckelObjects.com";
#undef DEBUG
 
// Similar code as before, except that it looks
// for the email name inside of '&lt;&gt;':
int inList(FILE* list, const char* emailName) {
  const int BSIZE = 255;
  char lbuf[BSIZE];
  char emname[BSIZE];
  // Put the email name in '&lt;&gt;' so there's no
  // possibility of a match within another name:
  sprintf(emname, "&lt;%s&gt;", emailName);
  // 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(strstr(lbuf, emname) != 0)
      return 1;
  }
  return 0;
}
 
void main() {
  // You MUST print this out, otherwise the 
  // server will not send the response:
  printf("Content-type: text/plain\n\n");
  FILE* list = fopen(dataFile, "a+t");
  if(list == 0) {
    printf("error: could not open database. ");
    printf("Notify %s", notify);
    return;
  }
  // For a CGI "GET," the server puts the data
  // in the environment variable QUERY_STRING:
  CGI_vector query(getenv("QUERY_STRING"));
  #if defined(DEBUG)
  // Test: dump all names and values
  for(int i = 0; i &lt; query.size(); i++) {
    printf("query[%d].name() = [%s], ", 
      i, query[i].name());
    printf("query[%d].value() = [%s]\n", 
      i, query[i].value());
  }
  #endif(DEBUG)
  Pair name = query[0];
  Pair email = query[1];
  if(name.empty() || email.empty()) {
    printf("error: null name or email");
    return;
  } 
  if(inList(list, email.value())) {
    printf("Already in list: %s", email.value());
    return;
  }
  // It's not in the list, add it:
  fseek(list, 0, SEEK_END);
  fprintf(list, "%s &lt;%s&gt;;\n", 
    name.value(), email.value());
  fflush(list);
  fclose(list);
  printf("%s &lt;%s&gt; added to list\n", 
    name.value(), email.value());
} ///:~ 

The alreadyInList( ) function is almost identical to the previous version, except that it assumes all email names are inside ‘ <>’.

When you use the GET approach (which is normally done in the HTML METHOD tag of the FORM directive, but which is controlled here by the way the data is sent), the Web server grabs everything after the ‘?’ and puts in into the environment variable QUERY_STRING. So to read that information you have to get the value of QUERY_STRING, which you do using the standard C library function getenv( ). In main( ), notice how simple the act of parsing the QUERY_STRING is: you just hand it to the constructor for the CGI_vector object called query and all the work is done for you. From then on you can pull out the names and values from query as if it were an array. (This is because the operator[] is overloaded in vector.) You can see how this works in the debug code, which is surrounded by the preprocessor directives #if defined(DEBUG) and #endif(DEBUG).

What about POST?

//: POSTtest.cpp
// CGI_vector works as easily with POST as it
// does with GET. Written in "pure" C++.
#include &lt;iostream.h&gt;
#include "CGITools.h"
 
void main() {
  cout &lt;&lt; "Content-type: text/plain\n" &lt;&lt; endl;
  // For a CGI "POST," the server puts the length
  // of the content string in the environment 
  // variable CONTENT_LENGTH:
  char* clen = getenv("CONTENT_LENGTH");
  if(clen == 0) {
    cout &lt;&lt; "Zero CONTENT_LENGTH" &lt;&lt; endl;
    return;
  }
  int len = atoi(clen);
  char* query_str = new char[len + 1];
  cin.read(query_str, len);
  query_str[len] = '\0';
  CGI_vector query(query_str);
  // Test: dump all names and values
  for(int i = 0; i &lt; query.size(); i++)
    cout &lt;&lt; "query[" &lt;&lt; i &lt;&lt; "].name() = [" &lt;&lt;
      query[i].name() &lt;&lt; "], " &lt;&lt;
      "query[" &lt;&lt; i &lt;&lt; "].value() = [" &lt;&lt;
      query[i].value() &lt;&lt; "]" &lt;&lt; endl;
  delete query_str; // Release storage
} ///:~ 

The getenv( ) function returns a pointer to a character string representing the content length. If this pointer is zero, the CONTENT_LENGTH environment variable has not been set, so something is wrong. Otherwise, the character string must be converted to an integer using the ANSI C library function atoi( ). The length is used with new to allocate enough storage to hold the query string (plus its null terminator), and then read( ) is called for cin. The read( ) function takes a pointer to the destination buffer and the number of bytes to read. The query_str is then null-terminated to indicate the end of the character string.

At this point, the query string is no different from a GET query string, so it is handed to the constructor for CGI_vector. The different fields in the vector are then available just as in the previous example.

To test this program, you must compile it in the cgi-bin directory of your host Web server. Then you can perform a simple test by writing an HTML page like this:

&lt;HTML&gt;
&lt;HEAD&gt;
&lt;META CONTENT="text/html"&gt;
&lt;TITLE&gt;A test of standard HTML POST&lt;/TITLE&gt;
&lt;/HEAD&gt;
Test, uses standard html POST
&lt;Form method="POST" ACTION="/cgi-bin/POSTtest"&gt;
&lt;P&gt;Field1: &lt;INPUT TYPE = "text" NAME = "Field1" 
VALUE = "" size = "40"&gt;&lt;/p&gt;
&lt;P&gt;Field2: &lt;INPUT TYPE = "text" NAME = "Field2" 
VALUE = "" size = "40"&gt;&lt;/p&gt;
&lt;P&gt;Field3: &lt;INPUT TYPE = "text" NAME = "Field3" 
VALUE = "" size = "40"&gt;&lt;/p&gt;
&lt;P&gt;Field4: &lt;INPUT TYPE = "text" NAME = "Field4" 
VALUE = "" size = "40"&gt;&lt;/p&gt;
&lt;P&gt;Field5: &lt;INPUT TYPE = "text" NAME = "Field5" 
VALUE = "" size = "40"&gt;&lt;/p&gt;
&lt;P&gt;Field6: &lt;INPUT TYPE = "text" NAME = "Field6" 
VALUE = "" size = "40"&gt;&lt;/p&gt;
&lt;p&gt;&lt;input type = "submit" name = "submit" &gt; &lt;/p&gt;
&lt;/Form&gt;
&lt;/HTML&gt;

When you fill this out and submit it, you’ll get back a simple text page containing the parsed results, so you can see that the CGI program works correctly.

//: POSTtest.java
// An applet that sends its data via a CGI POST
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;
 
public class POSTtest extends Applet {
  final static int SIZE = 10;
  Button submit = new Button("Submit");
  TextField[] t = new TextField[SIZE];
  String query = "";
  Label l = new Label();
  TextArea ta = new TextArea(15, 60);
  public void init() {
    Panel p = new Panel();
    p.setLayout(new GridLayout(t.length + 2, 2));
    for(int i = 0; i &lt; t.length; i++) {
      p.add(new Label(
        "Field " + i + "  ", Label.RIGHT));
      p.add(t[i] = new TextField(30));
    }
    p.add(l);
    p.add(submit);
    add("North", p);
    add("South", ta);
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(submit)) {
      query = "";
      ta.setText("");
      // Encode the query from the field data:
      for(int i = 0; i &lt; t.length; i++)
         query += "Field" + i + "=" +
           URLEncoder.encode(
             t[i].getText().trim()) +
           "&amp;";
      query += "submit=Submit";
      // Send the name using CGI's POST process:
      try {
        URL u = new URL(
          getDocumentBase(), "cgi-bin/POSTtest");
        URLConnection urlc = u.openConnection();
        urlc.setDoOutput(true);
        urlc.setDoInput(true);
        urlc.setAllowUserInteraction(false);
        DataOutputStream server = 
          new DataOutputStream(
            urlc.getOutputStream());
        // Send the data
        server.writeBytes(query);
        server.close();
        // Read and display the response. You
        // cannot use 
        // getAppletContext().showDocument(u);
        // to display the results as a Web page!
        DataInputStream in = 
          new DataInputStream(
            urlc.getInputStream());
        String s;
        while((s = in.readLine()) != null) {
          ta.appendText(s + "\n");
        }
        in.close();
      }
      catch (Exception e) {
        l.setText(e.toString());
      }
    }
    else return super.action(evt, arg);
    return true;
  }
} ///:~ 

Once the information is sent to the server, you can call getInputStream( ) and wrap the return value in a DataInputStream so that you can read the results. One thing you’ll notice is that the results are displayed as lines of text in a TextArea. Why not simply use getAppletContext().showDocument(u)? Well, this is one of those mysteries. The code above works fine, but if you try to use showDocument( ) instead, everything stops working – almost. That is, showDocument( ) does work, but what you get back from POSTtest is “Zero CONTENT_LENGTH.” So somehow, showDocument( ) prevents the POST query from being passed on to the CGI program. It’s difficult to know whether this is a bug that will be fixed, or some lack of understanding on my part (the books I looked at were equally abstruse). In any event, if you can stand to limit yourself to looking at the text that comes back from the CGI program, the above applet works fine.


[65] You can test this under Windows32 using the Microsoft Personal Web Server that comes with Microsoft Office 97 and some of their other products. This is a nice way to experiment since you can perform local tests (and it's also fast). If you're on a different platform or if you don't have Office 97, you might be able to find a freeware Web server for testing by searching the Internet.

[66] GNU stands for “Gnu’s Not Unix.” The project, created by the Free Software Foundation, was originally intended to replace the Unix operating system with a free version of that OS. Linux appears to have replaced this initiative, but the GNU tools have played an integral part in the development of Linux, which comes packaged with many GNU components.

[67] My book Thinking in C++ (Prentice-Hall, 1995) devotes an entire chapter to this subject. Refer to this if you need further information on the subject.

[68] I can’t say I really understand what’s going on here, but I managed to get it working by studying Java Network Programming by Elliotte Rusty Harold (O’Reilly 1997). He alludes to a number of confusing bugs in the Java networking libraries, so this is an area in which you can’t just write code and have it work right away. Be warned.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

  • Agile methodologies give development and test teams the ability to build software at a faster rate than ever before. Combining DevOps with hybrid cloud architectures give teams not just the principles, but also the technology necessary to achieve their goals. By combining hybrid cloud and DevOps: IT departments maintain control, visibility, and security Dev/test teams remain agile and collaborative Organizational barriers are broken down Innovation and automation can thrive Download this white paper to …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds