CH13

Lemur zaprasza

Chapter 13
Multipart Forms and Maintaining State




CONTENTS


Strategies

Environment Variables
Hidden HTML Form Fields
Session Files

Multipart Forms
Tic-Tac-Toe

General Functions
Passing State Using URLs
Passing State Using Forms

Shopping Carts

Online Course Catalog

Summary



In Chapter 8, "Client/Server Issues,"
you learn that the Web protocol is stateless. This is inconvenient
for the CGI programmer because many applications require knowledge
of previous states. One common application that needs to maintain
state information is a multipart form. Suppose you have several
HTML forms that collect information. After the last form is filled
out, you want all of the information filled out from all of the
forms to be submitted at once to the CGI program. In order to
do this, each current form must remember the input typed on previous
forms.

Another common application that requires maintaining state is
a shopping cart application. Many online stores enable you to
browse a catalog and select an item you want to order. This item
is added to your "shopping cart"; you can then browse
through the other items in the catalog and add desirable items
to your cart. After you are finished browsing, you can view your
cart and purchase its contents. Every state of this application
must remember what is in the shopping cart.

This chapter covers the basic strategies for maintaining state.
You see some examples of multipart forms, develop a CGI tic-tac-toe
game, and develop a type of shopping cart application.
Strategies


You can take advantage of CGI and HTML to maintain state over
the Web. Although none of the strategies presented here are perfect,
they are satisfactory for most applications.

What sort of data do you want to maintain across states? In a
multipart form, you might want to store all the previously entered
form input in the current state. After you fill out the last form,
you want to submit all of the information you entered in previous
forms as well. You might need an identifying state, something
to tell your CGI application or the Web browser which form to
use, which data to use, or what action to take next.

There are three different methods of maintaining state using CGI:
passing the information through environment variables, via hidden
HTML form fields, or by storing the information in a file. All
three methods have different advantages and disadvantages.


Note


A few browsers-including Netscape and Internet Explorer-have one additional method of maintaining state: HTTP cookies. This is discussed in detail in Chapter 14, "Proprietary Extensions."







Maintaining State: Server or Client's Job?

The strategies presented here rely mostly on the server for maintaining state. The server processes the state information, stores it in some form (an HTML form, a URL, or a session file) and passes this information back to the client. The

next time the client talks to the server, it returns this state information, which the server then proceeds to process again. Even HTTP cookies (discussed in Chapter 14) use a combination of server and client communication to
maintain state.

All of these methods are less than ideal, each with its own limitations. Programming a relatively simple multiform application requires writing a fairly complex CGI program. This seems a waste, because all a multiform application is doing is collecting
information from several different pages. Programming a multiform application shouldn't be more difficult than programming a single-form application, but it is. Consequently, many applications that require maintenance of state are not well-suited for the
Web.
You can use new client-side application technology (such as Java) to overcome these limitations. For example, you could program your own multipage, non-stateless applet in Java that loads from only one HTTP connection. After the user is finished entering
information in this applet, he or she can submit all of the information at once. Because this multipage applet requires only one HTTP connection, you can use the same CGI application that processes a single form application to process this multiple-part
application.
Although new technology promises to present improvements and strategies in tackling conventional CGI problems such as maintaining state, the strategies presented here are far from archaic. Choosing how you maintain state depends on several factors,
including development time and the desired simplicity (or complexity) of the application. Maintaining state using the CGI strategies presented here will be useful for a long time to come.




Environment Variables

The easiest way to maintain state is by passing the information
to the URL. For example, suppose you need to save an ID number
across several different pages. You could append the ID number
to the URL, either following a question mark (the QUERY_STRING
variable) or following a slash (the PATH_INFO
variable).

http://myserver.org/cgi-bin/form?12345

http://myserver.org/cgi-bin/form/12345


If you need to store two different variables (for example, name
and ID number), you could use both.

http://myserver.org/cgi-bin/form/Bob?12345


In this case, Bob is stored
in PATH_INFO and 12345
is in QUERY_STRING.

Passing information using URLs is useful because it doesn't require
forms. You maintain state by appending the state information to
all the URLs within your document. For example, given one of the
previous URLs, all you need to do to pass the state on to the
next page is to reference the page as the following or something
similar:

print "<a href=\"/cgi-bin/form?$ENV{'QUERY_STRING'}\">Next
page</a>\n";


However, using environment variables to store state has the same
disadvantage as using the GET
method for form submission. There is an upper size limit for both
the length of the URL and the storage size of the environment
variable. For large amounts of data, using environment variables
is unsatisfactory.
Hidden HTML Form Fields


You can overcome the size limitations of environment variables
by maintaining state using the HTML forms tag <input
type=hidden>. The concept is similar to environment
variables. Instead of appending the environment variable to references
to the URL, you embed the state information in the form using
the <input type=hidden>
information.

For example, suppose you have a two-part form. When you click
the Submit button on the second form, you want to submit information
from both forms. Remember, because these forms are inside CGI
scripts, no action attribute is needed. Your first form might
look like this:

<form method=POST>
<p>Enter your name: <input name="name"><br>

Enter your age: <input name="age"></p>

<p><input type=submit></p>
</form>


When you click Submit, the values for "name"
and "age" are passed
to the CGI program. The CGI program should return the second form
with the information from the first form embedded as <input
type=hidden> tags.

<form method=POST>
<!-- state information from previous form -->
<input type=hidden name="name" value="Corwyn">

<input type=hidden name="age" value="21">


<!-- second form -->
<p>What kind of cola do you like? <input name="cola"><br>

Do you like soccer? <input type=radio name="soccer"
value="yes"> Yes
    <input type=radio name="soccer"
value="no"> No<br>
Have you ever been to Highland Park, NJ?
    <input type=radio name="NJ"
value="yes"> Yes
    <input type=radio name="NJ"
value="no"> No</p>

<p><input type=submit></p>
</form>


When you submit this second form, both the information from the
first and second forms are submitted.

Although this method overcomes the limitation of environment variables,
it causes a new limitation: You can use <input
type=hidden> only if your application uses forms.
However, not all CGI applications that need state information
use forms. Sometimes using forms is undesirable because it adds
unnecessary complications to the CGI programming. An example of
this is presented later in the "Passing State Using Forms"
section of this chapter.
Session Files

Both of the previous methods for maintaining state are unable
to maintain state over different sessions. For example, suppose
you are in the middle of filling out a long multipart form when
you have to leave for an appointment. If you quit your Web browser,
you lose all of the information you have entered. Similarly, if
your session crashes while you are typing information, you must
retype everything starting from the first form.

Using session files to maintain state enables you to overcome
this difficulty and to maintain state over different sessions.
Instead of storing state information in environment variables
or forms, store it in a file on the server. An application can
access previous state information simply by accessing this file.
The format of the file is flexible; it could be a text file, a
directory containing files with the state information, or a database.

With session files, you still need to pass some identifying attribute
of the session file from state to state of the application by
using one of the previous methods. However, if for any reason
you need to quit your Web session, you can recover the state information
by remembering your session ID.


Tip


A good method for creating a unique session ID is to use the time function. For example, in Perl the code is


$sessionID = time;


This is almost certain to be unique. For added randomness on a UNIX machine, you can append the process ID.

$sessionID = time."-".$$;







Using a session file to maintain state can be risky because, theoretically,
one user can guess the session ID of another user and capture
his or her state information. You can take several steps to make
sure only the proper user has access to his or her own state information.
Remember, it is important to include session information with
files, even when you are going to delete them after the CGI program
has terminated.

For example, make the first page of your application a login application
that asks for a username and a password. When the user enters
this information, the application adds the username and password
to a password file and then generates an associated session ID.
In order to retrieve your session ID or to access your session
file, you need to re-authenticate using your username and password.

One other limitation is that it leaves extraneous files on your
server. After a user starts filling out a form, the CGI application
has no way of knowing if the user decided not to finish filling
out the form or simply left for a two-hour coffee break. Either
way, the file is left on the server. You can address this problem
by writing a program that periodically cleans out the directory
for files that haven't been touched for more than a day.
Multipart
Forms

To demonstrate a simple multipart form, I'll design a voting booth
CGI program. The specifications are very simple:

Two pages. The first page asks for name,
state, and party affiliation. The second page presents a list
of candidates for whom you can vote according to your party affiliation.
The candidates are hard-coded in the application.
After you fill out and submit both forms,
a confirmation message is sent that tells you your name, state
of residence, and for whom you voted.


A good way to develop applications like this is to create all
of your forms first (without the state information). The basic
forms for this application are in Listings 13.1 and 13.2. Both
forms contain the hidden input field "nextpage".
This value informs the CGI program which page to send next.


Listing 13.1. The first form.



<html>
<head>
<title>Voting Booth</title>
</head>

<body>
<h1>Voting Booth</h1>

<form method=POST>
<input type=hidden name="nextpage" value="two">

<p>Name: <input name="name"><br>
State: <input name="state" size=3></p>

<p><select name="party">
<option>Democrat
<option>Republican
</select></p>

<p><input type=submit value="Vote!"></p>


</form>
</body>
</html>



Listing 13.2 is the second form, which is used if the voter is
a Democrat. The only difference between this form and the one
for Republicans (Listing 13.1) is the candidates.


Listing 13.2. The second form.



<html>
<head>
<title>Vote for Whom?</title>
</head>

<body>
<h1>Vote for Whom?</h1>

<form method=POST>
<input type=hidden name="nextpage" value="three">


<select name="candidate">
<option>Bill Clinton
<option>Donkey
</select>

<p><input type=submit value=\"Vote!\"></p>

</form>
</body>
</html>



Because I'm using forms, I use the <input
type=hidden> tag to maintain state. The second
form is the only one that needs to maintain state. Before sending
this form, the program embeds hidden fields that contain the value
of the information submitted from the first page. When this second
page is submitted, the application prints the confirmation message.

The entire application, called vote, is shown in Listing 13.3.
When you first run this application, you see the form in Listing
13.1. Suppose you type the information depicted in Figure 13.1.
Because you entered Republican
as your party affiliation, vote returns a Republican ballot, as
shown in Figure 13.2. After you make
your selection, a confirmation message like the one in Figure 13.3
appears.

Figure 13.1 : First form from vote.


Figure 13.2 : The second form: a Republican ballot.


Figure 13.3 : The confirmation mesage.



Listing 13.3. The vote application.



#!/usr/local/bin/perl

require 'cgi-lib.pl';

&ReadParse(*input);
if ($input{'nextpage'} eq "two") {
      print &PrintHeader,&HtmlTop("Vote
for Whom?");
      print "<form method=POST>\n";

      print "<input type=hidden
name=\"nextpage\" value=\"three\">\n";

      print "<input type=hidden
name=\"name\" value=\"$input{'name'}\">\n";

      print "<input type=hidden
name=\"state\" value=\"$input{'state'}\">\n";

      print "<input type=hidden
name=\"party\" value=\"$input{'party'}\">\n";

      print "<select name=\"candidate\">\n";

      if ($input{'party'} eq "Democrat")
{
             print
"<option>Bill Clinton\n";
             print
"<option>Donkey\n";
      }
      else {
             print
"<option>Bob Dole\n";
             print
"<option>Elephant\n";
      }
      print "</select>\n";

      print "<p><input
type=submit value=\"Vote!\"></p>\n";
print "</form>\n";
      print &HtmlBot;
}
elsif ($input{'nextpage'} eq "three") {
      print &PrintHeader,&HtmlTop("Thanks
for Voting!");
      print <<EOM;
<p>Thank you for voting, $input{'name'} from $input{'state'}.
You voted for:
<b>$input{'candidate'}</b>. Thank you for participating
in our
democracy!</p>
EOM
      print &HtmlBot;
}
else {
      print &PrintHeader,&HtmlTop("Voting
Booth");
      print <<EOM;
<form method=POST>
<input type=hidden name="nextpage" value="two">

<p>Name: <input name="name"><br>
State: <input name="state" size=3></p>

<p><select name="party">
<option>Democrat
<option>Republican
</select></p>

<p><input type=submit value="Vote!"></p>


</form>
EOM
      print &HtmlBot;
}


Tic-Tac-Toe


In order to demonstrate another application of state, I wrote
a tic-tac-toe game. Tic-tac-toe is a simple game, and yet it is
not trivial to implement as a CGI program. It presents several
challenges, among them, presenting a board, accepting the player's
move, and maintaining state.

My tic-tac-toe game works like this:

It provides a blank board. The user makes
the first move.
The CGI stores this move and then checks
to see if the user has won. If the user has won or if there's
a stalemate, the game is over; otherwise, the computer moves.
Once again, the CGI checks for a winner or a stalemate.
If no one has won, the CGI generates a
new board based on the state of the previous board.


The program needs some method of passing the state of the previous
board to the next board. The state of the board is easily expressed
as a nine-character string with the first three characters representing
the top row, the second three characters representing the middle
row, and the last three characters representing the final row
(see Figure 13.4). The program only needs
this one string, and it is not likely that a user will need to
maintain state over different sessions. Therefore, using a session
file to pass state is overkill.

Figure 13.4 : Tic-tac-toc state information.


Passing state via environment variables or via a hidden input
field are both good options for this game. Determining which method
you use depends on how you present the information and accept
user input. You can either display the board using a form or by
using a text board with several references. I programmed tic-tac-toe
using both methods, comparing and contrasting the advantages and
disadvantages of each.
General Functions

Common to programs using both methods are the functions that actually
play the game. I use a two-dimensional three-by-three array of
integers to store the board value. The player plays X and the
computer plays O. An X represents a 1 on the array, an O a -1,
and an empty spot a zero. The function set_board()
in Listing 13.4 converts a state string into values on this two-dimensional
array. Likewise, board2string()
(also in Listing 13.4) converts the array into a state string.


Listing 13.4. The set_board()
and board2string()
functions.



void set_board(int board[3][3],char *state)

{
  int i;

  for (i = 0; i<9; i++) {
      if (state[i] == 'x')
          board[i%3][i/3]
= 1;
      else if (state[i] == 'o')

          board[i%3][i/3]
= -1;
      else
          board[i%3][i/3]
= 0;
  }
}

char *board2string(int board[3][3])
{
  char *str = malloc(10);
  int i,j;

  for (j=0; j<3; j++)
      for (i=0; i<3; i++) {
          if
(board[i][j] == 1)
          str[i+j*3]
= 'x';
          else
if (board[i][j] == -1)
          str[i+j*3]
= 'o';
          else

          str[i+j*3]
= '_';
      }
  str[9] = '\0';
  return str;
}



In order to determine whether there is a winner on the board,
you sum up the rows, columns, and two diagonals. The sum will
be a number ranging from -3 to 3. If the value of any sum is 3,
then the human (X) wins. If the value of any sum is -3, then the
computer (O) wins; otherwise, there is no winner. The function
check_winner() (shown in
Listing 13.5) sums each row, column, and diagonal and returns
a 1 if the human wins, a -1 if the computer wins, a 2 if there
is no winner and the board is full (a stalemate), or a 0 otherwise.


Listing 13.5. The check_winner()
function.



int check_winner(int board[3][3])
{
  int i,j;
  short FOUND = 0;

  /* 0 = go on, 1 = human wins, -1 = computer wins,
2 = stalemate */

  /* sum up horizontals */
  for (j = 0; j<3; j++) {
      if (board[0][j]+board[1][j]+board[2][j]
== 3)
          return
1;
      else if (board[0][j]+board[1][j]+board[2][j]
== -3)
          return
-1;
  }
  /* try verticals */
  for (i = 0; i<3; i++) {
      if (board[i][0]+board[i][1]+board[i][2]
== 3)
          return
1;
      else if (board[i][0]+board[i][1]+board[i][2]
== -3)
          return
-1;
  }
  /* now test diagonals */
  i = board[0][0]+board[1][1]+board[2][2];
  j = board[2][0]+board[1][1]+board[0][2];
  if ( (i==3) || (j==3) )
      return 1;
  else if ( (i==-3) || (j==-3) )
      return -1;
  for (j = 0; j<3; j++)
      for (i = 0; i<3; i++)
          if
(board[i][j] == 0)
        FOUND = 1;
  if (FOUND)
      return 0;
  else
      return 2;
}



Finally, a function is needed that determines the computer's move.
I wanted the computer to play a "stupid" game of tic-tac-toe
so the human could easily win, so I have the computer pick a random
empty spot on the board. The function computer_move()
(shown in Listing 13.6) makes the computer's move.


Listing 13.6. The computer_move()
function.



void computer_move(int board[3][3])
{
  int positions[9];
  int i,j,move;
  int num = 0;

  /* we can assume there are empty positions; otherwise,
this function
        would not have
been called */
  /* find empty positions */
  for (j=0; j<3; j++)
      for (i=0; i<3; i++)
          if
(board[i][j] == 0) {
          positions[num]
= i+j*3;
          num++;

          }

  /* pick random number from 0 to num-1 */
  move = (int) ((double) num*rand()/(RAND_MAX+1.0));

  board[positions[move]%3][positions[move]/3] = -1;

}


Passing State Using URLs


I first describe the game by passing state via URLs. A typical
board is shown in Figure 13.5 and the
HTML that created it is shown in Listing 13.7. Each empty spot
is represented by a question mark and is surrounded by an <a
href> tag. Within each <a
href> tag is the state of the board if the user
decides to move on that spot. In order to generate all of these
states, the program needs an algorithm to determine all possible
states according to the current board state and the empty positions
on the board.

Figure 13.5: A board created by text-tictactoe



Listing 13.7. The HTML that created the board shown in Figure 13.5.




<html> <head>
<title>Your Move</title>
</head>

<body>
<h1>Your Move</h1>
<pre>
X | <a href="/cgi-bin/text-tictactoe?xxo______">?</a>
| O
<a href="/cgi-bin/text-tictactoe?x_ox_____">?</a>
| <a href=
Â"/cgi-bin/text-tictactoe?x_o_x____">?</a>
| <a href=
Â"/cgi-bin/text-tictactoe?x_o__x___">?</a>

<a href="/cgi-bin/text-tictactoe?x_o___x__">?</a>
| <a href=
Â"/cgi-bin/text-tictactoe?x_o____x_">?</a>
| <a href=
Â"/cgi-bin/text-tictactoe?x_o_____x">?</a>

</pre>

<p><a href="/cgi-bin/text-tictactoe">Start
new game</a></p>
</body> </html>




Note


The text board is not visually pleasing. You could draw three images, a square with an X, a square with an O, and an empty square, and use these images inline instead of the text X, O, and ? to improve the look of this program.







Clicking any of the question marks on the board calls text-tictactoe
again and tells it how the board should look. The program then
determines whether the human has won; if he or she hasn't won,
then the computer moves. The complete code for text-tictactoe
is shown in Listing 13.8.


Listing 13.8. The text-tictactoe.c program.



#include <stdio.h>
#include <math.h>
#include "cgi-lib.h"
#include "html-lib.h"
#include "string-lib.h"

void set_board(int board[3][3],char *state)
{
  int i;

  for (i = 0; i<9; i++) {
      if (state[i] == 'x')
          board[i%3][i/3]
= 1;
      else if (state[i] == 'o')

          board[i%3][i/3]
= -1;
      else
          board[i%3][i/3]
= 0;
  }
}

char *board2string(int board[3][3])
{
  char *str = malloc(10);
  int i,j;

  for (j=0; j<3; j++)
      for (i=0; i<3; i++) {
          if
(board[i][j] == 1)
          str[i+j*3]
= 'x';
          else
if (board[i][j] == -1)
          str[i+j*3]
= 'o';
          else

          str[i+j*3]
= '_';
      }
  str[9] = '\0';
  return str;
}

int check_winner(int board[3][3])
{
  int i,j;
  short FOUND = 0;

  /* 0 = go on, 1 = human wins, -1 = computer wins,
2 = stalemate */

  /* sum up horizontals */
  for (j = 0; j<3; j++) {
      if (board[0][j]+board[1][j]+board[2][j]
== 3)
          return
1;
      else if (board[0][j]+board[1][j]+board[2][j]
== -3)
          return
-1;
  }
  /* try verticals */
  for (i = 0; i<3; i++) {
      if (board[i][0]+board[i][1]+board[i][2]
== 3)
          return
1;
      else if (board[i][0]+board[i][1]+board[i][2]
== -3)
          return
-1;
  }
  /* now test diagonals */
  i = board[0][0]+board[1][1]+board[2][2];
  j = board[2][0]+board[1][1]+board[0][2];
  if ( (i==3) || (j==3) )
      return 1;
  else if ( (i==-3) || (j==-3) )
      return -1;
  for (j = 0; j<3; j++)
      for (i = 0; i<3; i++)
          if
(board[i][j] == 0)
          FOUND
= 1;
  if (FOUND)
      return 0;
  else
      return 2;
}

void computer_move(int board[3][3])
{
  int positions[9];
  int i,j,move;
  int num = 0;

  /* we can assume there are empty positions; otherwise,
this function
        would not have
been called */
  /* find empty positions */
  for (j=0; j<3; j++)
      for (i=0; i<3; i++)
          if
(board[i][j] == 0) {
          positions[num]
= i+j*3;
          num++;

          }

  /* pick random number from 0 to num-1 */
  move = (int) ((double) num*rand()/(RAND_MAX+1.0));

  board[positions[move]%3][positions[move]/3] = -1;

}

void print_board(char *msg,char *state)
{
  int i,j;
  char pstate[9];

  html_begin(msg);
  h1(msg);
  printf("<pre>\n");
  for (j=0; j<3; j++) {
      for (i=0; i<3; i++) {
          switch
(state[i + j*3]) {
          case
'_':
        strcpy(pstate,state);

        pstate[i + j*3]
= 'x';
        printf("<a
href=\"/cgi-bin/text-tictactoe?%s\">",pstate);

        printf("?</a>");

        break;
          case
'x':
        printf("X");

        break;
          case
'o':
        printf("O");

        break;
          }

          switch(i)
{
          case
0: case 1:
        printf("
| ");
        break;
          case
2:
        printf("\n");

        break;
          }

      }
  }
  printf("</pre>\n");
  printf("<p><a href=\"/cgi-bin/text-tictactoe\">");

  printf("Start new game</a></p>\n");

}

void next_move(int board[3][3])
{
  int winner;
  char state[9];

  html_header();
  strcpy(state,board2string(board));
  if (strcmp(state,"_________")) {
      winner = check_winner(board);

      if (winner == 1) /* human
wins */
          print_board("You
Win!",state);
      else if (winner == 2)
          print_board("Stalemate",state);

      else if (winner == 0) { /*
computer's turn */
          computer_move(board);

          strcpy(state,board2string(board));

          winner
= check_winner(board);
          if
(winner == -1)
        print_board("Computer
Wins!",state);
          else
if (winner == 2)
        print_board("Stalemate",state);

          else

        print_board("Your
Move",state);
      }
  }
  else
      print_board("Your Move",state);

  html_end();
}

int main()
{
  int board[3][3];

  if (QUERY_STRING == NULL)
      set_board(board,"_________");

  else
      set_board(board,QUERY_STRING);

  next_move(board);
}


Passing State Using Forms


The code for text-tictactoe is fairly clean and straightforward.
There doesn't seem to be a good reason at this point to reimplement
tic-tac-toe using <input type=hidden>
tags to pass the state, especially since it requires forms. However,
you can create a nice-looking form that enables the user to click
an imagemap of the board, determines where on the board the user
clicked, and plays the game from there.

I create a version of the tic-tac-toe program that does this in
Chapter 15, "Imagemaps." Before
writing a forms- and image-based CGI game, it would be nice to
write a purely textual form-based application and use it as the
foundation for the more advanced, better looking game.

The challenge for creating a textual, forms-based implementation
of tic-tac-toe (form-tictactoe) is presenting the board and accepting
user input. I use a form with a submit button for each empty spot
on the board (as shown in Figure 13.6).
Each submit button is named "location" and contains
its coordinates as its value. Listing 13.9 contains the HTML for
one board scenario. Notice that the current state of the board
is stored in a hidden field. When the user selects a submit button,
the current state and the user's move is submitted to the CGI
program form-tictactoe.

Figure 13.6 : A form-based tic-tac-toe game.



Listing 13.9. The HTML form for one board scenario.



<html> <head>
<title>Your Move</title>
</head>

<body>
<h1>Your Move</h1>
<form action="/cgi-bin/form-tictactoe" method=POST>

<input type=hidden name="board" value="ooxox___x">


<p>
O
O
X
<br>
O
X
<input type=submit name="location" value="21">

<br>
<input type=submit name="location" value="02">

<input type=submit name="location" value="12">

X
</p>

</form>
</body> </html>



Form-tictactoe differs slightly from text-tictactoe in that it
receives the previous board configuration and the user's move
rather than the new board configuration containing the user's
move. This is only a slight change, and the revised code is shown
in Listing 13.10.


Listing 13.10. form-tictactoe.c.



#include <stdio.h>
#include <math.h>
#include "cgi-lib.h"
#include "html-lib.h"
#include "string-lib.h"

void set_board(int board[3][3],char *state)
{
  int i;

  for (i = 0; i<9; i++) {
      if (state[i] == 'x')
          board[i%3][i/3]
= 1;
      else if (state[i] == 'o')

          board[i%3][i/3]
= -1;
      else
          board[i%3][i/3]
= 0;
  }
}

char *board2string(int board[3][3])
{
  char *str = malloc(10);
  int i,j;

  for (j=0; j<3; j++)
      for (i=0; i<3; i++) {
          if
(board[i][j] == 1)
        str[i+j*3] = 'x';

          else
if (board[i][j] == -1)
        str[i+j*3] = 'o';

          else

        str[i+j*3] = '_';

      }
  str[9] = '\0';
  return str;
}

int check_winner(int board[3][3])
{
  int i,j;
  short FOUND = 0;

  /* 0 = go on, 1 = human wins, -1 = computer wins,
2 = stalemate */

  /* sum up horizontals */
  for (j = 0; j<3; j++) {
      if (board[0][j]+board[1][j]+board[2][j]
== 3)
          return
1;
      else if (board[0][j]+board[1][j]+board[2][j]
== -3)
          return
-1;
  }
  /* try verticals */
  for (i = 0; i<3; i++) {
      if (board[i][0]+board[i][1]+board[i][2]
== 3)
          return
1;
      else if (board[i][0]+board[i][1]+board[i][2]
== -3)
          return
-1;
  }
  /* now test diagonals */
  i = board[0][0]+board[1][1]+board[2][2];
  j = board[2][0]+board[1][1]+board[0][2];
  if ( (i==3) || (j==3) )
      return 1;
  else if ( (i==-3) || (j==-3) )
      return -1;
  for (j = 0; j<3; j++)
      for (i = 0; i<3; i++)
          if
(board[i][j] == 0)
        FOUND = 1;
  if (FOUND)
      return 0;
  else
      return 2;
}

void computer_move(int board[3][3])
{
  int positions[9];
  int i,j,move;
  int num = 0;

  /* we can assume there are empty positions; otherwise,
this function
        would not have
been called */
  /* find empty positions */
  for (j=0; j<3; j++)
      for (i=0; i<3; i++)
          if
(board[i][j] == 0) {
        positions[num]
= i+j*3;
        num++;
          }

  /* pick random number from 0 to num-1 */
  move = (int) ((double) num*rand()/(RAND_MAX+1.0));

  board[positions[move]%3][positions[move]/3] = -1;

}

void print_play(char *msg,char *state)
{
  int i,j;

  html_begin(msg);
  h1(msg);
  printf("<pre>\n");
  for (j=0; j<3; j++) {
      for (i=0; i<3; i++) {
          switch
(state[i + j*3]) {
          case
'_':
        printf("
");
        break;
          case
'x':
        printf("X");

        break;
          case
'o':
        printf("O");

        break;
          }

          switch(i)
{
          case
0: case 1:
        printf("
| ");
        break;
          case
2:
        printf("\n");

               break;

          }

      }
  }
  printf("</pre>\n");
  printf("<p><a href=\"/cgi-bin/form-tictactoe\">");

  printf("Play again?</a></p>\n");

}

void print_move(char *msg,char *state)
{
  int i,j;
  char xy[3];

  html_begin(msg);
  h1(msg);
  printf("<form action=\"/cgi-bin/form-tictactoe\"
method=POST>\n");
  printf("<input type=hidden name=\"board\"
value=\"%s\">\n",state);
  printf("<p>\n");
  for (j=0; j<3; j++) {
      for (i=0; i<3; i++) {
          switch
(state[i + j*3]) {
          case
'_':
        sprintf(xy,"%d%d",i,j);

        printf("<input
type=submit name=\"location\" value=\"%s\">\n",xy);

        break;
          case
'x':
        printf("X\n");

        break;
          case
'o':
        printf("O\n");

        break;
          }

      }
      printf("<br>\n");

  }
  printf("</form>\n");
}

void print_board(int board[3][3], int x, int y)
{
  int winner;
  char state[9];

  html_header();
  strcpy(state,board2string(board));
  if (x != -1) { /* win? if not, computer moves. */

      board[x][y] = 1;
      strcpy(state,board2string(board));

      winner = check_winner(board);

      if (winner == 1) /* human
wins */
          print_play("You
Win!",state);
      else if (winner == 2)
          print_play("Stalemate",state);

      else if (winner == 0) { /*
computer's turn */
          computer_move(board);

          strcpy(state,board2string(board));

          winner
= check_winner(board);
          if
(winner == -1)
        print_play("Computer
Wins!",state);
          else
if (winner == 2)
        print_play("Stalemate",state);

          else

        print_move("Your
Move",state);
      }
  }
  else
      print_move("Your Move",state);

  html_end();
}

int main()
{
  int board[3][3];
  char xy[3];
  int x,y;
  llist coordinates;

  if (read_cgi_input(&coordinates)) {
      set_board(board,cgi_val(coordinates,"board"));

      strcpy(xy,cgi_val(coordinates,"location"));

      x = atoi(substr(xy,0,1));

      y = atoi(substr(xy,1,1));

  }
  else {
      set_board(board,"_________");

      x = -1;
      y = -1;
  }
  print_board(board,x,y);
  list_clear(&coordinates);
}



As you can see, form-tictactoe is slightly more complex than text-tictactoe;
therefore, passing state through the URL seems to be the better
strategy in this case. However, you can make form-tictactoe present
the board in an interesting and visually pleasing way using imagemaps,
which you can't do by passing the information using URLs, as I
show in Chapter 15. If presentation is
important for you, then the form-based approach works best.
Shopping
Carts

One of the most common applications on the World Wide Web is the
shopping cart application. The idea is that the user is provided
with a shopping cart. The user can browse through different pages
and descriptions on a Web site and add items to his or her shopping
cart. The user should be able to examine the shopping cart and
remove or add items. When the user is finished selecting items,
he or she can order the items.

As with other CGI applications that require state, you can effectively
use all three methods of maintaining state with shopping cart
applications. Normally, however, because of the space restriction
of the URL method, you are limited to either hidden form fields
or a session file. A session file is often ideal in this scenario
because users might want a chance to leave the site temporarily,
possibly to look at other sites or maybe just to take a little
break. A session file allows the user to save state across different
sessions, a useful feature in a shopping cart application. However,
the potential for other users to capture your shopping cart is
a dangerous one, and you should take the proper steps to avoid
this possibility.

One important part of all shopping cart applications is a database
on the back end. You need a way to store descriptions and information
about all the items in your store. For a detailed discussion of
databases and CGI, see Chapter 12, "Databases."
Online Course Catalog

The term shopping cart application
is a general one. Not all shopping cart applications are actually
an online ordering system. As an example of a shopping cart application,
I have designed an online course catalog.

Several schools have made their course catalogs available on the
World Wide Web. Some have added a search feature that makes it
easy to pick courses. Another nice feature would be to allow students
to select courses while browsing the catalog online. This is equivalent
to adding the courses to a shopping cart. After the student is
finished browsing through the catalog, he or she could look at
the shopping cart, compare the classes, resolve classes meeting
at conflicting times, and then easily prioritize his or her class
choices. After the student is finished organizing the shopping
cart, he or she could "order" the classes (submit the
course list for registration).

I develop here a very basic online course catalog system upon
which you can easily add more features. Here are the requirements
for this system:

All of the courses and course description
and information must be stored in some sort of database.
The CGI program lists courses by department.
While browsing through the courses, you can add courses to your
basket. A course cannot be added to your basket more than once.
When you are finished choosing your courses,
you can display your choices. The display should show conflicting
times.


I assume each course has the following information:

Department
Course number
Course title
Days it meets
Time it meets
Course description


For simplicity's sake, I make certain assumptions about the courses.
Courses either meet on Monday-Wednesday-Friday or on Tuesday-Thursday.
Each course description is one paragraph. None of the fields will
contain double pipes (||).

I use a file system database similar to the one used in the video
library example in Chapter 12. Each department
is represented by a top-level directory. Within each directory
are files containing the course information in the following format:

TITLE={}
DAYS={} # MWF || TT
TIME={} # 24 hour format
DESC={}


where the information is contained within the braces. I allow
for the possibility of braces within the fields in a hexadecimal
encoded form. The name of the file is the course number. The DAYS
field contains either MWF
for Monday-Wednesday-Friday or TT
for Tuesday-Thursday. The Time
is a number between 0 and 24; all of the classes meet on the hour.
A sample database tree is shown in Figure 13.7
and a sample course file is shown in Listing 13.11.

Figure 13.7 : Sample database directory tree.



Listing 13.11. A sample course file called 101, located in
the english/ directory.



TITLE={Intro to English}
DAYS={MWF}
TIME={10}
DESC={An introduction to the wonders and nuances of the written
English
language. Learn how to read, write, and speak proper English!}



In Chapter 12, I wrote some Perl code
that parses this kind of file. This code is recycled as the function
&parse_file, shown in
Listing 13.12. &parse_file
returns a list containing the course title, the days and time
the course meets, and the course description.


Listing 13.12. The &parse_file
function.



sub parse_file {
      local($DEPT,$CN) = @_;
      local($filename) = $dbasedir.$DEPT."/".$CN;

      local($TITLE,$DAYS,$TIME,$DESC);

      local($field);

      if (-e $filename) {
        # read fields
of each record
        open(RECORD,$filename)

              ||
&CgiDie("Error","Couldn't Open Record");

        $/ = '}';
        while ($field
= <RECORD>) {
              $field
=~ s/^[\r\n]//;
              if
($field =~ /^TITLE=\{/) {
               ($TITLE
= $field) =~ s/^TITLE=\{//;
               $TITLE
=~ s/\}//;
               $TITLE
= &decode($TITLE);
              }

              elsif
($field =~ /^DAYS=\{/) {
               ($DAYS
= $field) =~ s/^DAYS=\{//;
               $DAYS
=~ s/\}//;
               $DAYS
= &decode($DAYS);
              }

              elsif
($field =~ /^TIME=\{/) {
               ($TIME
= $field) =~ s/^TIME=\{//;
               $TIME
=~ s/\}//;
               $TIME
= &decode($TIME);
              }

              elsif
($field =~ /^DESC=\{/) {
               ($DESC
= $field) =~ s/^DESC=\{//;
               $DESC
=~ s/\}//;
               $DESC
= &decode($DESC);
              }

        }
        $/ = '\n';
        close(RECORD);

        return ($TITLE,$DAYS,$TIME,$DESC);

      }
      else {
        &CgiDie("Error","Record
does not exist");
      }
}

sub decode {
      local($data) = @_;

      $data =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;

      return $data;
}



Using this database as a back end, my program, called courses,
does the following:

Given no input, it lists the departments.
The student can select the department whose courses he or she
wants to browse.
After the student chooses a department,
an HTML form with a detailed description of each course is displayed.
At the end of each description is a submit button that allows
the user to choose a course. There are two links at the bottom
of the page: one to go back to the departmental list and the other
to view the shopping cart's current contents.
When the student views the shopping cart,
it displays the courses chosen, sorted by time. Courses that meet
at the same time are displayed under the same time heading.


All of the information is dynamically created from the database
and from previous user input and is heavily dependent on forms.
Therefore, it only seems natural to use hidden input fields as
the way to pass state.


Note


As stated earlier, using a session file might actually be more practical for this shopping cart application. Implementing a shopping cart using a session file is left as an exercise to the reader.






As advised earlier, I begin designing this application by first
displaying all of the basic forms. There are three possible forms
shown in Listings 13.13, 13.14, and 13.15. The first form compiles
a list of departments from the database and displays that list.
It contains a state variable "form" that says which
form to display next. In this case, it says to display the "list"
form-the second form, which displays a detailed list of the courses
in the selected department.


Listing 13.13. The first form: a list of departments.



<html>
<head>
<title>Which Department?</title>
</head>

<body>
<h1>Which Department?</h1>

<form method=POST>
<input type=hidden name="form" value="list">


<select name="department">
<option>english
<option>government
<option>history
<option>math
<option>physics
</select>

<p><input type=submit value="Choose Department"></p>

</form>

</body>
</html>



This second form contains both the name of the department selected
and the state "form" in hidden fields. Note that the
value of "form" is once again "list," or the
second form. If a student selects a course by pressing the submit
button, courses redisplay the same list, except without the option
of selecting that course again. At the bottom of this form there
are two options: either to go back to the list of departments
or to view the courses selected (the third form).


Listing 13.14. Second form: a detailed list of courses.



<html>
<head>
<title>english</title>
</head>

<body>
<h1>english</h1>
<hr>

<form method=POST>
<input type=hidden name="department" value="english">

<input type=hidden name="form" value="list">


<h2>Intro to English</h2>
<h3>english 101</h3>

<p><i>MWF</i>, <b>10</b></p>


<p>An introduction to the wonders and nuances of the written
English
language. Learn how to read, write, and speak proper English!</p>


<p><input type=submit name="courses" value="english
101"></p>
<hr>

<h2>English as a Second Language</h2>
<h3>english 105</h3>

<p><i>MWF</i>, <b>10</b></p>


<p>Learn how to speak English fluently in 21 days, or your
money back!</p>

<p><input type=submit name="courses" value="english
105"></p>
<hr>

<h2>Shakespeare</h2>
<h3>english 210</h3>

<p><i>MWF</i>, <b>11</b></p>


<p>In this course, we read and discuss the greatest of Shakespeare's

works, including Macbeth, Romeo and Juliet, and A Midsummer's
Night Dream.</p>

<p><input type=submit name="courses" value="english
210"></p>
<hr>


<p><input type=submit name="do" value="Show
Schedule"><br>
<input type=submit name="do" value="Choose Department"></p>

</form>

</body>
</html>



The third form does not really need to be a form at all for this
implementation of courses, because there are no options on this
form. However, since you might want to develop this program further,
I display this HTML document as a form that contains the courses
you have chosen as hidden fields. You can add such functionality
as removing courses or submitting the courses for registration.


Listing 13.15. The third form: the student's chosen courses.




<html>
<head>
<title>Classes Chosen</title>
</head>

<body>
<h1>Classes Chosen</h1>

<form action=POST>
<input type=hidden name="courses" value="english
101">
<input type=hidden name="courses" value="english
105">
<input type=hidden name="courses" value="english
210">

<h2>Monday, Wednesday, Friday</h2>
<hr>

<h3>10</h3>

<h4>english 101: Intro to English</h4>
<p>An introduction to the wonders and nuances of the written
English
language. Learn how to read, write, and speak proper English!</p>

<hr>

<h4>english 105: English as a Second Language</h4>

<p>Learn how to speak English fluently in 21 days, or your
money back!</p>
<hr>

<h3>11</h3>

<h4>english 210: Shakespeare</h4>
<p>In this course, we read and discuss the greatest of Shakespeare's

works, including Macbeth, Romeo and Juliet, and A Midsummer's
Night Dream.</p>
<hr>

<h2>Tuesday, Thursday</h2>
<hr>

</form>

</body>
</html>



There are two form fields I use to maintain state. The first is
field "form," which tells courses which form to display
next. The second is field "courses," which contains
the courses you have selected in the form "Department CourseNumber."

The complete source code for courses is shown in Listing 13.16.



Listing 13.16. The courses application.



#!/usr/local/bin/perl

require 'cgi-lib.pl';

$dbasedir = '/home/eekim/Web/CGI/courses/';
$indexfile = 'index';

# $QUERY_STRING = $ENV{'QUERY_STRING'};
$num_input = &ReadParse(*input);

if (!$input{'form'} || ($input{'do'} eq "Choose Department"))
{
      @departments = &get_dir($dbasedir);

      print &PrintHeader,&HtmlTop("Which
Department?");
      print "<form method=POST>\n";

      &next_form("list");

      &print_state;
      print "<select name=\"department\">\n";

      foreach $dept (@departments)
{
      print "<option>$dept\n";

      }
      print "</select>\n";

      print "<p><input
type=submit value=\"Choose Department\"></p>\n";

print "</form>\n";
      print &HtmlBot;
}
elsif ($input{'do'} eq "Show Schedule") {
      print &PrintHeader,&HtmlTop("Classes
Chosen");
      print "<form action=POST>\n";

      @chosen = &print_state;

      # damn, wish Perl4 had struct

      # %mwf|%tt{$TIME} = $DEPT||$CN||$TITLE||$DESC{}$DEPT||$CN||$TITLE||$DESC

      undef %mwf;
      undef %tt;
      foreach $class (@chosen) {

        ($DEPT,$CN) =
split(/ /,$class);
        ($TITLE,$DAYS,$TIME,$DESC)
= parse_file($DEPT,$CN);
        if ($DAYS eq "MWF")
{
              $mwf{$TIME}
.= "{}" unless (!$mwf{$TIME});
              $mwf{$TIME}
.= "$DEPT||$CN||$TITLE||$DESC";
        }
        else {
              $tt{$TIME}
.= "{}" unless (!$mwf{$TIME});
              $tt{$TIME}
.= "$DEPT||$CN||$TITLE||$DESC";
        }
      }
      print "<h2>Monday,
Wednesday, Friday</h2>\n";
      print "<hr>\n";

      foreach $time (sort keys(%mwf))
{
        print "<h3>$time</h3>\n";

        foreach $course
(split("{}",$mwf{$time})) {
              ($DEPT,$CN,$TITLE,$DESC)
= split(/\|\|/,$course);
              print
"<h4>$DEPT $CN: $TITLE</h4>\n";
              print
"<p>$DESC</p>\n";
print "<hr>\n";
        }
      }
      print "<h2>Tuesday,
Thursday</h2>\n";
      print "<hr>\n";

      foreach $time (sort keys(%tt))
{
        print "<h3>$time</h3>\n";

        foreach $course
(split("{}",$tt{$time})) {
              ($DEPT,$CN,$TITLE,$DESC)
= split(/\|\|/,$course);
              print
"<h4>$DEPT $CN: $TITLE</h4>\n";
              print
"<p>$DESC</p>\n";
print "<hr>\n";
        }
      }
      print "</form>\n";

      print &HtmlBot;
}
elsif ($input{'form'} eq "list") {
      $dept = $input{'department'};

      @courses = &get_dir($dbasedir.$dept);

      print &PrintHeader,&HtmlTop($dept);

      print "<hr>\n";

      print "<form method=POST>\n";

      print "<input type=hidden
name=\"department\" value=\"$dept\">\n"

        unless (!$dept);

      &next_form("list");
# this needs to be changed
      @chosen = &print_state;

      foreach $cn (@courses) {
        &print_file($dept,$cn,@chosen);

      }
      print "<p><input
type=submit name=\"do\" value=\"Show Schedule\"><br>\n";

      print "<input type=submit
name=\"do\" value=\"Choose Department\"></p>\n";

print "</form>\n";
      print &HtmlBot;
}

sub get_dir {
      local($dir) = @_;
      local(@directories);

      opendir(DIR,$dir) || &CgiDie("Error","Can't
read directory");
      @directories = grep(!/^\./,readdir(DIR));

      closedir(DIR);
      return sort(@directories);

}

sub next_form {
      local($form) = @_;

      print "<input type=hidden
name=\"form\" value=\"$form\">\n\n";

}

sub print_state {
      local($course,@chosen);

      foreach $course (split("\0",$input{'courses'}))
{
        print "<input
type=hidden name=\"courses\" value=\"$course\">\n";

        push(@chosen,$course);

      }
      print "\n";
      return @chosen;
}

sub print_file {
      local($DEPT,$CN,@chosen) =
@_;
      local($TITLE,$DAYS,$TIME,$DESC)
= &parse_file($DEPT,$CN);

      print "<h2>$TITLE</h2>\n";

      print "<h3>$DEPT
$CN</h3>\n";
      print "<p><i>$DAYS</i>,
<b>$TIME</b></p>\n";
      print "<p>$DESC</p>\n";

      print "<p><input
type=submit name=\"courses\" value=\"$DEPT $CN\"></p>\n"

unless (grep(/^$DEPT $CN$/,@chosen));
      print "<hr>\n";

}

sub parse_file {
      local($DEPT,$CN) = @_;
      local($filename) = $dbasedir.$DEPT."/".$CN;

      local($TITLE,$DAYS,$TIME,$DESC);

      local($field);

      if (-e $filename) {
        # read fields
of each record
        open(RECORD,$filename)

              ||
&CgiDie("Error","Couldn't Open Record");

        $/ = '}';
        while ($field
= <RECORD>) {
              $field
=~ s/^[\r\n]//;
              if
($field =~ /^TITLE=\{/) {
               ($TITLE
= $field) =~ s/^TITLE=\{//;
               $TITLE
=~ s/\}//;
               $TITLE
= &decode($TITLE);
              }

              elsif
($field =~ /^DAYS=\{/) {
               ($DAYS
= $field) =~ s/^DAYS=\{//;
               $DAYS
=~ s/\}//;
               $DAYS
= &decode($DAYS);
              }

              elsif
($field =~ /^TIME=\{/) {
               ($TIME
= $field) =~ s/^TIME=\{//;
               $TIME
=~ s/\}//;
               $TIME
= &decode($TIME);
              }

              elsif
($field =~ /^DESC=\{/) {
               ($DESC
= $field) =~ s/^DESC=\{//;
               $DESC
=~ s/\}//;
               $DESC
= &decode($DESC);
              }

        }
        $/ = '\n';
        close(RECORD);

        return ($TITLE,$DAYS,$TIME,$DESC);

      }
      else {
        &CgiDie("Error","Record
does not exist");
      }
}

sub decode {
      local($data) = @_;

      $data =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;

      return $data;
}


Summary


There are three methods of maintaining state using CGI: using
environment variables, hidden HTML form fields, and session files.
Which method you use depends largely on need, including how much
information you need to maintain across state and how you want
to present the information.

Two important types of applications that require knowledge of
previous states are multipart forms and shopping cart applications.
Both have a variety of applications, and both can use all three
methods of maintaining state to their advantage.
  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • teen-mushing.xlx.pl
  • Wątki
    Powered by wordpress | Theme: simpletex | © Lemur zaprasza