19.8 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 it all 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 with wide-open permissions. If you're on a UNIX system, then you can do this (from your shell) to initialize a file for the guestbook program to use: touch /usr/tmp/chatfile chmod 0666 /usr/tmp/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 use Fcntl qw(:flock); # imports LOCK_EX, LOCK_SH, LOCK_NB .... flock(CHANDLE, LOCK_EX) || bail("cannot flock $CHATNAME: $!");
The
19.8.1 Object-Oriented Programming in PerlFinally, and most important, it's time to teach you 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 (1) manpage. 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 object method (which is what we were talking about a moment ago), except instead of using an object to call the method, you use the name of the class as though it were itself an object. An object method is saying "call the function by this name that is related to this object"; a class method is saying "call the function by this name that is related to this class." 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. 19.8.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 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 there is no need for 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 <A HREF="http://www.SOMEWHERE.org/cgi-bin/guestbook">guestbook</A>. 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 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. #!/usr/bin/perl -w use 5.004; use strict; # enforce declarations and quoting use CGI qw(:standard); # import shortcuts use Fcntl qw(:flock); # imports LOCK_EX, LOCK_SH, LOCK_NB 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 ); $TITLE = "Simple Guestbook"; $CHATNAME = "/usr/tmp/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); # set to the 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) { $entry = CGI->new(\*CHANDLE); # pass the filehandle by reference 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(); } print end_html; Figure 19.5 shows an example screen dump after running the guestbook program. Figure 19.5: A simple guestbook formNote that the program begins with: use 5.004; If you want to run it with an earlier version of Perl 5, you'll need to comment out the line reading: use Fcntl qw (:flock);
and change Since 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: print header, start_html($TITLE), h1($TITLE); It then creates a new CGI object: $cur = CGI->new(); # current request if ($cur->param("message")) { # good, we got a message $cur->param("date", scalar localtime); # set to the current time @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 open(CHANDLE, "+< $CHATNAME") || bail("cannot open $CHATNAME: $!");
This opens the file in nondestructive read-write mode. Alternatively, we could use # 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) || bail "can't open $CHATNAME: $!";
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) { $entry = CGI->new(\*CHANDLE); # pass the filehandle by reference 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: $!");
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 will, of course, be the first form he is seeing if he has just clicked on "Please sign our guestbook.") First, some preliminaries: print hr, start_form; # hr() emits html horizontal rule: <HR> 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")); print end_form, hr;
The
Widgets created by CGI.pm are by default sticky: they retain their values between calls. (But only during a single "session" with a form, beginning when the user clicks on "Please sign our guestbook.") This means that the NAME="name" VALUE="Sam Smith"
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(); } print end_html;
As you will doubtless realize, the Users can sit there with the guestbook form, continually typing messages and hitting the submit button. This simulates an electronic bulletin-board system, letting them see each others' 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 is particularly convenient when creating multistage forms, such as those used in so-called "shopping cart" applications. |
|