18.5 Creating a Guestbook ProgramIf you have followed the examples above, you can now get some simple CGI programs going. But what about harder ones? A common request is to create a CGI program to manage a guestbook, so that visitors to your web site can record their own messages.[ 9 ]
Actually, the form for this kind of thing is quite easy - easier in fact than some of our ice cream forms. Other matters get trickier. But don't worry, we'll explain everything as we go. You probably want guestbook messages to survive a user's visit to your site, so you need a file to store them in. The CGI program (probably) runs under a different user, not as you; therefore, it won't normally have permission to update a file of yours. So, first, create a file (make sure it has read-write permissions for whatever user your program runs as). You can either use a text editor to create an empty file, or do something like: > echo. > c:\temp\chatfile
Okay, but how will you accommodate several folks using the guestbook program simultaneously? The operating system doesn't block simultaneous access to files, so if you're not careful, you could get a jumbled file as everyone writes to it at the same time. To avoid this, we'll use Perl's
# Perl 5.004
use Fcntl qw(:flock); # imports LOCK_EX, LOCK_SH, LOCK_NB
....
flock(CHANDLE, LOCK_EX) || bail("cannot flock $CHATNAME: $!");
# ActiveState distribution
$LOCK_EX = 2; # hard coded value of standard LOCK_EX
....
The
18.5.1 Object-Oriented Programming in PerlFinally, and most importantly, you must learn how to use objects and classes. Although building your own object module is beyond the scope of this book, you don't have to know about that in order to use existing, object-oriented library modules. For in-depth information about using and creating object modules, see Chapter 5 of Programming Perl and the perltoot documentation (Perl 5.004 distribution and beyond). We won't go into the theory behind objects here, but you can just treat them as packages (which they are!) of wonderful and marvelous things that you invoke indirectly. Objects provide subroutines that do anything you need to do with the object.
For instance, suppose the
CGI.pm
module returns an object called $query->param("answer");
This says, "Run the
If you want to retrieve the return value of the $he_said = $query->param("answer");
Objects look like scalars; you store them in scalar variables (like
The type of a particular object is known as its class . The class name is normally just the module name - without the .pm suffix - and often the words class and module are used interchangeably. So we can speak of the CGI module and also the CGI class. Objects of a particular class are created and managed by the module implementing that class. You access classes by loading in a module, which looks just like any other module except that object-oriented ones don't usually export anything. You can think of the class as a factory that cranks out brand-new objects. To get the class to produce one of these new objects, you invoke special methods called constructors . Here's an example: $query = CGI->new(); # call method new() in class "CGI"
What you have there is the invocation of a
class method
. A class method looks just like an Sometimes you'll see that same thing written this way: $query = new CGI; # same thing The second form is identical in behavior to the first. It's got less punctuation, so is sometimes preferred. But it's less convenient to use as part of a larger expression, so we'll use the first form exclusively in this book. From the standpoint of the designer of object modules, an object is a reference to a user-defined data structure, often an anonymous hash. Inside this structure is stored all manner of interesting information. But the well-behaved user of an object is expected to get at this information (to inspect or change it), not by treating the object as a reference and going straight for the data it points to, but by employing only the available object and class methods. Changing the object's data by other means amounts to hanky-panky that is bound to get you talked about. To learn what those methods are and how they work, just read the object module's documentation, usually included as embedded pods. 18.5.2 Objects in CGI.pmThe CGI module is unusual in that it can be treated either as a traditional module with exported functions or as an object module. Some kinds of programs are more easily written using the object interface to CGI.pm rather than the procedural one. A guestbook program is one of these. We access the input that the user supplied to the form via a CGI object, and we can, if we want, use this same object to generate new HTML code for sending back to the user.
First, however, we need to create the object explicitly.
For CGI.pm
, as for so many other classes, the method that generates objects is the class method named
This method constructs and returns a new CGI object corresponding to a filled-out form. The object contains all the user's form input. Without arguments, We'll show you the program and explain its details in a moment. Let's assume that the program is named guestbook.plx and is in the cgi-bin directory. While this program does not look like one of the two-part scripts shown earlier (where one part outputs an HTML form, and the other part reads and responds to form input from a user), you will see that it nevertheless does handle both functions. So you do not need a separate HTML document containing a guestbook form. The user might first trigger our program simply by clicking on a link like this:
Please sign our
The program then downloads an HTML form to the browser, and for good measure also downloads any previous guest messages (up to a stated limit) for the user to review. The user then fills out the form, submits it, and the program reads what is submitted. This information is added to the list of previous messages (saved in a file), which is then output to the browser again, along with a fresh form. The user can continue reading the current set of messages and submitting new messages via the supplied forms as long as he wishes. Here's the program. You might want to scan it quickly before we step you through it.
use strict; # enforce declarations and quoting
use CGI qw(:standard); # import shortcuts
sub bail { # function to handle errors gracefully
my $error = "@_";
print h1("Unexpected Error"), p($error), end_html;
die $error;
}
my(
$CHATNAME, # name of guestbook file
$MAXSAVE, # how many to keep
$TITLE, # page title and header
$cur, # new entry in the guestbook
@entries, # all cur entries
$entry, # one particular entry
$LOCK_EX, # hardcoded value for flock
);
$LOCK_EX = 2; # hardcoded value for flock
$TITLE = "Simple Guestbook";
$CHATNAME = "c:/temp/chatfile"; # wherever makes sense on your system
$MAXSAVE = 10;
print header, start_html($TITLE), h1($TITLE);
$cur = CGI->new(); # current request
if ($cur->param("message")) { # good, we got a message
$cur->param("date", scalar localtime); # current time
@entries = ($cur); # save message to array
}
# open the file for read-write (preserving old contents)
open(CHANDLE, "+< $CHATNAME") ||
bail("cannot open $CHATNAME: $!");
# get exclusive lock on the guestbook
# ($LOCK_EX == exclusive lock)
flock(CHANDLE, $LOCK_EX) || bail("cannot flock $CHATNAME: $!");
# grab up to $MAXSAVE old entries, newest first
while (!eof(CHANDLE) && @entries < $MAXSAVE) {
# pass the filehandle by reference
$entry = CGI->new(\*CHANDLE);
push @entries, $entry;
}
seek(CHANDLE, 0, 0) || bail("cannot rewind $CHATNAME: $!");
foreach $entry (@entries) {
$entry->save(\*CHANDLE); # pass the filehandle by reference
}
truncate(CHANDLE, tell(CHANDLE)) ||
bail("cannot truncate $CHATNAME: $!");
close(CHANDLE) || bail("cannot close $CHATNAME: $!");
print hr, start_form; # hr() emits html horizontal rule: <HR>
print p("Name:", $cur->textfield(
-NAME => "name"));
print p("Message:", $cur->textfield(
-NAME => "message",
-OVERRIDE => 1, # clears previous message
-SIZE => 50));
print p(submit("send"), reset("clear"));
print end_form, hr;
print h2("Prior Messages");
foreach $entry (@entries) {
printf("%s [%s]: %s",
$entry->param("date"),
$entry->param("name"),
$entry->param("message"));
print br();
}
Figure 18.5 shows a sample screen dump after running the program a few times. Figure 18.5: Sample screen dumpBecause every execution of the program results in the return of an HTML form to the particular browser that sought us out, the program begins by getting a start on the HTML code:
The program then creates a new CGI object:
$cur = CGI->new(); # current request
if ($cur->param("message")) { # good, we got a message
# set to the current time
$cur->param("date", scalar localtime);
@entries = ($cur); # save message to array
If we are being called via submission of a form, then the
If we are not being called via submission of a form, but rather because the user has clicked on "Please sign our guestbook," then the query object we create here will be empty. The
In either case, we proceed to check for any entries previously saved in our savefile. We will read those into the
This opens the file in nondestructive read-write mode. Alternatively, we could have used
# need to import two "constants" from Fcntl module for sysopen
use Fcntl qw( O_RDWR O_CREAT );
sysopen(CHANDLE, $CHATNAME, O_RDWR|O_CREAT, 0666)
Then we lock the file, as described earlier, and proceed to read up to a total of
flock(CHANDLE, $LOCK_EX) || bail("cannot flock $CHATNAME: $!");
while (!eof(CHANDLE) && @entries < $MAXSAVE) {
# pass the filehandle by reference
$entry = CGI->new(\*CHANDLE);
push @entries, $entry;
seek(CHANDLE, 0, 0) || bail("cannot rewind $CHATNAME: $!");
foreach $entry (@entries) {
$entry->save(\*CHANDLE); # pass the filehandle by reference
}
truncate(CHANDLE, tell(CHANDLE)) ||
bail("cannot truncate $CHATNAME: $!");
The
The format of a savefile entry looks like this, where the entry is terminated by
NAME1=VALUE1
NAME2=VALUE2
NAME3=VALUE3
Now it's time to return a fresh form to the browser and its user. (This form will be, of course, the first form he is seeing if he has just clicked on "Please sign our guestbook.") First, consider some preliminaries:
As already mentioned, CGI.pm allows us to use either straight function calls or method calls via a CGI object. Here, for basic HTML code, we've reverted to the simple function calls. But for generation of form input fields, we continue to employ object methods:
print p("Name:", $cur->textfield(
-NAME => "name"));
print p("Message:", $cur->textfield(
-NAME => "message",
-OVERRIDE => 1, # clears previous message
-SIZE => 50));
print p(submit("send"), reset("clear"));
The
Widgets created by
CGI.pm
are by default sticky - they retain their values between calls. (This statement is true only during a single session with a form, beginning when the user clicks on "Please sign our guestbook.") Consequently, the
The second invocation of Finally, we output for the user's delectation the current set of saved messages, including, of course, any he has just submitted:
print h2("Prior Messages");
foreach $entry (@entries) {
printf("%s [%s]: %s",
$entry->param("date"),
$entry->param("name"),
$entry->param("message"));
print br();
}
As you will doubtless realize, the Users can sit with the guestbook form, continually typing messages and pressing the submit button. This method simulates an electronic bulletin-board system, letting users see each other's new messages each time they send off their own. When they do this, they call the same CGI program repeatedly, which means that the previous widget values are automatically retained between invocations. This result is particularly convenient when creating multistage forms, such as those used in so-called "shopping cart" applications. |
|