One
of the problems with the current HTTP protocol
is its inability to maintain state. In other words, the protocol
provides no way to access data from previous requests.
Imagine an ordering (or "shopping cart") system on the Web.
You present the user with several forms listing the numerous products
that can be ordered. The system keeps track of what the user ordered.
Finally, it displays all of the user's selections. This type of
system needs to somehow store the information--or "state"--so that
it can be accessed at a later time.
For example, suppose you ask the user for his or her address
in the first form. If you need this information in a later form,
you don't want to ask all over again. Instead, you want to find
a way for that address to be accessible to a later form, but transparent
to the user. This is the most basic problem of using multiple forms--maintaining
"state" from one form to another--and thus deserves special attention
in this book.
There are several different strategies we'll explore for maintaining
state. They include:
As mentioned in Chapter 4, Forms and CGI, hidden fields allow you to store "hidden" information
within a form. These fields are not displayed by the client. However,
if the user selects the "View Source" option in the browser, the
entire form is visible, including the hidden fields. Hidden fields
are therefore not meant for security (since anyone can see them),
but just for passing information to and from forms transparently.
Here is an example of two hidden fields that store author
information within a form:
<FORM ACTION="/cgi-bin/test.pl" METHOD="POST">
.
.
<INPUT TYPE="hidden" NAME="author" VALUE="Larry Bird">
<INPUT TYPE="hidden" NAME="company" VALUE="Boston Celtics">
.
.
</FORM>
When the form is submitted, the information within the hidden
fields is encoded, as the client passes all the fields to the server
in the same exact manner. As far as the CGI program is concerned,
there is no difference between hidden fields and regular, visible
fields.
One thing to note is that certain browsers may not be able
to handle hidden fields correctly.
AOA simple way to use hidden fields for maintaining state involves
writing the information from a form as hidden field information
into its successive form. Here is a simple first form:
<FORM ACTION="/cgi-bin/test.pl" METHOD="POST">
Name: <INPUT TYPE="text" NAME="01 Full Name" SIZE=40>
<BR>
EMail: <INPUT TYPE="text" NAME="02 EMail" SIZE=40>
<BR>
<INPUT TYPE="submit" VALUE="Submit the survey">
<INPUT TYPE="reset" VALUE="Clear all fields">
</FORM>
When this form is submitted, the program retrieves the information
and creates a dynamic second form, based on the first form, like
this:
<FORM ACTION="/cgi-bin/test.pl" METHOD="POST">
<INPUT TYPE="hidden" NAME="01 Full Name" VALUE="Shishir Gundavaram">
<INPUT TYPE="hidden" NAME="02 EMail" VALUE="[email protected]">
What is your favorite WWW browser?
<BR>
Browser: <INPUT TYPE="text" NAME="03 Browser" SIZE=40>
<BR>
<INPUT TYPE="submit" VALUE="Submit the survey">
<INPUT TYPE="reset" VALUE="Clear all fields">
</FORM>
As you can see, the two fields, along with the user information,
are inserted into the second form. The main advantage of such a
process is that there is no need for magic cookies and temporary
files. On the other hand, the disadvantage is that the form information
is appended repeatedly to successive forms, creating large forms.
This could result in possible
performance problems.
Let's look at an example using this technique. Here is the
first form:
<HTML>
<HEAD><TITLE>Welcome to the CGI Shopping Cart</TITLE></HEAD>
<BODY>
<H1>CGI Shopping Cart</H1>
Welcome! Thanks for stopping by the CGI Shopping Cart. Here is a list
of some of our products. We hope you like them, and please visit again.
<FORM ACTION="/cgi-bin/shopping.pl/catalog.html" METHOD="POST">
<HR>
What is your full name: <BR>
<INPUT TYPE="text" NAME="01 Full Name" SIZE=40>
<P>
What is your e-mail address: <BR>
<INPUT TYPE="text" NAME="02 Email" SIZE=40>
<P>
<INPUT TYPE="submit" VALUE="Submit and Retrieve Catalog">
<INPUT TYPE="reset" VALUE="Clear all fields">
</FORM>
</BODY></HTML>
The most important thing to note here is the
extra path
information passed to the program. This filename represents the
next form to be displayed. The two fields in this form will be "hidden"
in /catalog.html. Now, here is the second form:
<HTML>
<HEAD><TITLE>Welcome to the CGI Shopping Cart</TITLE></HEAD>
<BODY>
<H1>CGI Shopping Cart</H1>
Thanks for visiting our server. Here is a catalog of some of our books.
Make your selections and press the submit buttons. Note: multiple
selections are allowed.
<HR>
<FORM ACTION="/cgi-bin/shopping.pl" METHOD="POST">
<H2>Books on Networking</H2>
<SELECT NAME="03 Networking Books" SIZE=3 MULTIPLE>
<OPTION SELECTED>Managing Internet Information Services
<OPTION>TCP/IP Network Administration
<OPTION>Linux Network Administrator's Guide
<OPTION>Managing UUCP and Usenet
<OPTION>The USENET Handbook
</SELECT>
<HR>
<H2>UNIX related Books</H2>
<SELECT NAME="04 UNIX Books" SIZE=3 MULTIPLE>
<OPTION SELECTED>Learning the UNIX Operating System
<OPTION>Learning the Korn Shell
<OPTION>UNIX Power Tools
<OPTION>Learning Perl
<OPTION>Programming Perl
<OPTION>Learning the GNU Emacs
</SELECT>
<INPUT TYPE="submit" VALUE="Submit the selection">
<INPUT TYPE="reset" VALUE="Clear all fields">
</FORM>
</BODY></HTML>
The ACTION attribute does not contain
extra path information. This represents the last form in the "shopping
cart." Also note the fact that there is a scrolled list that allows
multiple selections. The program displays any form element that
has multiple selection in a unique way.
The program begins as follows:
#!/usr/local/bin/perl
$webmaster = "shishir\@bu\.edu";
$document_root = "/home/shishir/httpd_1.4.2/public";
$request_method = $ENV{'REQUEST_METHOD'};
$form_file = $ENV{'PATH_INFO'};
$full_path = $document_root . $form_file;
$exclusive_lock = 2;
$unlock = 8;
if ($request_method eq "GET") {
if ($form_file) {
&display_file ();
} else {
&return_error (500, "CGI Shopping Cart Error",
"An initial form must be specified.");
}
If the program was requested with the GET
protocol and extra path information, the display_file
subroutine is called to output the form. The program should be accessed
with the following URL:
https://your.machine/cgi-bin/shopping.pl/start.html
where /start.html represents the first
form. If no path information is specified, an error message is returned.
} elsif ($request_method eq "POST") {
&parse_form_data (*STATE);
if ($form_file) {
&parse_file ();
} else {
&thank_you ();
}
If extra path information is passed to this program with the
POST method, the parse_file
subroutine is invoked.
This subroutine inserts the information
from the previous form(s) into the current form as hidden fields.
Remember, the form information is stored in the STATE
associative array. On the other hand, if no path information is
specified, it is the end of the data collection process. The
thank_you subroutine displays the information from
all the forms.
} else {
&return_error (500, "Server Error",
"Server uses unsupported method");
}
exit (0);
The display_file subroutine simply outputs
the first form to standard output.
sub display_file
{
open (FILE, "<" . $full_path) ||
&return_error (500, "CGI Shopping Cart Error",
"Cannot read from the form file [$full_path].");
flock (FILE, $exclusive_lock);
print "Content-type: text/html", "\n\n";
while (<FILE>) {
print;
}
flock (FILE, $unlock);
close (FILE);
}
The parse_file subroutine inserts information
from previous forms into the current form, as hidden fields.
sub parse_file
{
local ($key, $value);
open (FILE, "<" . $full_path) ||
&return_error (500, "CGI Shopping Cart Error",
"Cannot read from the form file [$full_path].");
flock (FILE, $exclusive_lock);
print "Content-type: text/html", "\n\n";
while (<FILE>) {
if (/<\s*form\s*.*>/i) {
print;
foreach $key (sort (keys %STATE)) {
$value = $STATE{$key};
print <<End_of_Hidden;
<INPUT TYPE="hidden" NAME="$key" VALUE="$value">
End_of_Hidden
}
The file specified by PATH_INFO is opened.
The while loop iterates through the file one
line at a time. The regular expression checks for the <FORM>
tag within the document. If it is found, the line containing the
tag is displayed. Also, the foreach construct
iterates through all of the key-value form pairs, and outputs a
hidden field for each one.
If the <FORM> tag is not found, the
line from the file is output verbatim.
flock (FILE, $unlock);
close (FILE);
}
The thank_you subroutine thanks the user
and displays the data he or she selected.
sub thank_you
{
local ($key, $value, @all_values);
print <<Thanks;
Content-type: text/html
<HTML>
<HEAD><TITLE>Thank You!</TITLE></HEAD>
<BODY>
<H1>Thank You!</H1>
Thank you again for using our service. Here are the items
that you selected:
<HR>
<P>
Thanks
This subroutine formats and displays the information stored
in the STATE associative array, which represents
the combined data from all the forms.
foreach $key (sort (keys %STATE)) {
$value = $STATE{$key};
$key =~ s/^\d+\s//;
if ($value =~ /\0/) {
print "<B>", $key, "</B>", "<BR>", "\n";
$value =~ s/\0/<BR>\n/g;
print $value, "<BR>", "\n";
If a particular value contains a null string, it is replaced
with "
<BR>" followed by a newline
character. As a result, the multiple values are displayed properly.
} else {
print $key, ": ", $value, "<BR>", "\n";
}
}
print "<HR>", "\n";
print "</BODY></HTML>", "\n";
}
The parse_form_data subroutine is similar
to the one used in the "survey" program above, except it does not
handle any query information.
sub parse_form_data
{
local (*FORM_DATA) = @_;
local ($query_string, @key_value_pairs, $key_value, $key, $value);
read (STDIN, $query_string, $ENV{'CONTENT_LENGTH'});
@key_value_pairs = split (/&/, $query_string);
foreach $key_value (@key_value_pairs) {
($key, $value) = split (/=/, $key_value);
$key =~ tr/+/ /;
$value =~ tr/+/ /;
$key =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
$value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
if (defined($FORM_DATA{$key})) {
$FORM_DATA{$key} = join ("\0", $FORM_DATA{$key}, $value);
} else {
$FORM_DATA{$key} = $value;
}
}
}