Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 215 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
215
Dung lượng
1,18 MB
Nội dung
10.5 The Table API Apache provides a general API for creating and maintaining lookup tables Apache tables are ubiquitous, used for everything from storing the current request's outgoing HTTP headers to maintaining the list of environment variables passed to subprocesses Tables are similar to Perl hashes in that they are lists of key/value pairs However, unlike a Perl hash, keys are case-insensitive, and a single key may correspond to a list of several values.[2] In addition, Apache table keys and values are always strings; arbitrary data types cannot be used [2] Despite the differences between Perl hashes and Apache tables, the Perl API allows programmers to access tables via tied Perl hashes See Section 9.2.5 10.5.1 The table and table_entry Data Types Currently, a table is an Apache array containing array elements of the table_entry data type (defined in include/alloc.h): typedef struct { char *key; /* the key */ char *val; /* the value */ } table_entry; When fetching or setting the value of a key, Apache searches for the key using a simple linear search Since most tables are short, this usually doesn't impose a significant overhead You will usually not want to access the table_entry directly, but use API function calls to manipulate the keys and values for you If you do read directly from the table_entry, a note in the include file indicates that you should check the key for null This is because the table_entry may be made part of a more sophisticated hash table in the future The table structure itself is a private data type intended to be accessed via an opaque table * If you want to peek at its definition, you can find it in include/alloc.c It is equally straightforward: struct table { array_header a; #ifdef MAKE_TABLE_PROFILE void *creator; #endif }; The MAKE_TABLE_PROFILE define is part of Apache's debugging code and is usually undefined, so table is really just an array header 10.5.2 Creating and Copying Tables If you need a table of key/value pairs that is private to your own module, you can use these API routines to create it You can either create a new empty table or start with one that is already defined and make a copy of it These functions are defined in the include/alloc.h file, which is automatically included when you bring in include/httpd.h table *ap_make_table (pool *p, int nelts) ap_make_table() creates a new empty table, given a resource pool pointer and an estimate of the number of elements you expect to add If the nelts argument is nonzero, that number of table_entry tables will be pre-allocated for efficiency Regardless of its initial size, the table grows as necessary to accommodate new entries and table merging operations Accessitable *my_table = ap_make_table(p, 25); table * ap_copy_table (pool *p, const table *t) This function takes a resource pool and an existing table and makes a replica of the table, returning a pointer to the copy You can then change the contents of the copy without modifying the original In this example, we make a copy of the headers_in table: table *my_headers = ap_copy_table(r->pool, r->headers_in); 10.5.3 Getting and Setting Table Values These routines allow you to add new entries to the table, to change existing ones, and to retrieve entries const char *ap_table_get (const table *t, const char *key) Given a table pointer and a key, ap_table_get() returns the value of the entry at that key as a char * If there are multiple values for that particular key, the function will only return the first one it finds, which will be the first entry added In this example, we recover the string value of the incoming Useragent header: const char *ua = ap_table_get(r->headers_in, "User-agent"); To iterate through the multiple values for the same key, use the ap_table_do() function described later in this section void ap_table_set (table *t, const char *key, const char *val) ap_table_set() sets the entry named by key to the character string in val If an entry with the same key already exists, its value is replaced Otherwise, a new entry is created If more than one entry has the same key, the extraneous ones are deleted, making the key single-valued Internally, Apache calls ap_pstrdup() on the key and the value and stores copies of them in the table This means that you are able to change or dispose of the original variables without worrying about disrupting the table Here's an example of using this function to set the outgoing headers field Location to the string http://www.modperl.com/ Because Location is a single-valued field, ap_table_set() is the correct call to use: ap_table_set(r->headers_out, "Location", "http://www.modperl void ap_table_setn (table *t, const char *key, const char *val) This function behaves the same as ap_table_set(), but the character strings for key and val are not copied with ap_pstrdup() You must ensure that the strings remain valid for the lifetime of the table The previous example is a good candidate for ap_table_setn(), as it uses static strings for both the key and value void ap_table_add (table *t, const char *key, const char *val) This function is similar to ap_table_set(), but existing entries with the same key are not replaced Instead, the new entry is added to the end of the list, making the key multivalued Internally, Apache calls ap_pstrdup() on the key and the value, allowing you to change or dispose of the original variables without worrying about disrupting the table This example adds several Set-cookie fields to the outgoing HTTP headers table: for(i=0; cookies[i]; i++) { ap_table_add(r->headers_out, "Set-cookie", cookies[i]); } void ap_table_addn (table *t, const char *key, const char *val) This function behaves like ap_table_add(), but key and val are not duplicated before storing them into the table This function saves a little time and memory if you are working with static strings void ap_table_merge (table *t, const char *key, const char *val) ap_table_merge() merges a new key value into the existing entry by appending it to what's already there This is used for commadelimited header fields such as Content-language For example, this series of calls will result in a value of en, fr, sp in the Contentlanguage field: ap_table_merge(r->headers_out, "Content-language", "en"); ap_table_merge(r->headers_out, "Content-language", "fr"); ap_table_merge(r->headers_out, "Content-language", "sp"); Like ap_table_set(), the key and value are copied using ap_pstrdup() before moving them into the table void ap_table_mergen (table *t, const char *key, const char *val) This function is the same as ap_table_merge, but the key and val arguments are not copied with ap_pstrdup() before entering them into the table void ap_table_unset (table *t, const char *key) ap_table_unset() deletes all entries having the indicated key This example removes the Referer field from the incoming headers, possibly in preparation for making an anonymous proxy request (see Chapter 7): ap_table_unset(r->headers_in, "Referer"); void ap_table_do (int (*comp)(void *, const char *, const char *), void *rec, const table *t, ); ap_table_get() and ap_table_getn() work well for single-valued keys, but there are a few instances in which keys are not unique To access all the values of these keys, you will have to use ap_table_do() to iterate over the table As its prototype indicates, this function is more complicated than the ones we've seen before The function's first argument is a pointer to a callback function that will be called during the iteration process The second argument is a void * that can be used to pass some arbitrary information to the callback The third argument is the table * itself This is followed by a variable number of char * key arguments, terminated by a null ap_table_do() will iterate over the table, invoking the callback routine only when a table entries' key matches a key in the given list If no keys are given, the function will invoke the callback routine for all of the table entries The callback function should have this function prototype: int callback(void *rec, const char *key, const char *value); The first argument corresponds to the void * argument passed to ap_table_do(), and the second and third arguments are the key and value of the current table entry The callback should do whatever work it needs to do (for example, copying the value into an Apache array), and return a true value The callback can return in order to abort ap_table_do() prematurely Here's a callback that simply prints out the key name and value without performing further processing: static int header_trace(void *data, const char *key, const c { request_rec *r = (request_rec *)data; ap_rprintf(r, "Header Field `%s' == `%s'\n", key, val); return TRUE; } Here's how the callback can be used to print out the contents of the outgoing headers table: ap_table_do(header_trace, r, r->headers_out, NULL); And in this example, the callback is only invoked for the Content-type and Content-length fields: ap_table_do(header_trace, (void*)r, r->headers_out, "Content-type", "Content-length", NULL); 10.5.4 Other Table Functions Here are a few miscellaneous table functions that don't fit into the previous categories: table *ap_overlay_tables (pool *p, const table *overlay, const table *base) This function takes the contents of the table at overlay and adds it to the table at base Entries in overlay that don't exist in base are added to base Entries that already exist in base are overwritten You can use ap_overlay_tables() to perform a bulk update of a table This example overlays the fields listed in my_headers onto the table of outgoing headers: table *new_table = _ap_overlay_tables(r->pool, my_headers, r array_header *ap_table_elts (table *t) If you wish to access the contents of the table directly, you can call the ap_table_elts() function (it's a preprocessor macro, actually) It will return an array_header*, which you can then iterate through, casting each element to a table_entry array_header *arr = ap_table_elts(my_table); int ap_is_empty_table (table *t) This function (it's a preprocessor macro, actually) returns true if there are no entries in the given table, or false otherwise if(!ap_is_empty_table(my_table)) { /* this table has one or more elements */ } void ap_clear_table (table *t) The ap_clear_table() function clears all entries from the table Example: ap_clear_table(my_table); 9.2 Other Core Perl API Classes The vast bulk of the functionality of the Perl API is contained in the Apache object However, a number of auxiliary classes, including Apache::Table, Apache::Connection, and Apache::Server, provide additional methods for accessing and manipulating the state of the server This section discusses these classes 9.2.1 The Apache TIEHANDLE Interface In the CGI environment, the standard input and standard output file descriptors are redirected so that data read and written is passed through Apache for processing In the Apache module API, handlers ordinarily use the Apache read() and print() methods to communicate with the client However, as a convenience, mod_perl ties the STDIN and STDOUT filehandles to the Apache class prior to invoking Perl API modules This allows handlers to read from standard input and write to standard output exactly as if they were in the CGI environment The Apache class supports the full TIEHANDLE interface, as described in perltie(1) STDIN and STDOUT are already tied to Apache by the time your handler is called If you wish to tie your own input or output filehandle, you may do so by calling tie() with the request object as the function's third parameter: tie *BROWSER, 'Apache', $r; print BROWSER 'Come out, come out, wherever you are!'; Of course, it is better not to hardcode the Apache class name, as $r might be blessed into a subclass: tie *BROWSER, ref $r, $r; 9.2.2 The Apache::SubRequest Class The Apache methods lookup_uri( ) and lookup_file( ) return a request record object blessed into the Apache::SubRequest class The Apache::SubRequest class is a subclass of Apache and inherits most of its methods from there Here are two examples of fetching subrequest objects: my $subr = $r->lookup_file($filename); my $subr = $r->lookup_uri($uri); The Apache::SubRequest class adds a single new method, run() run() When a subrequest is created, the URI translation, access checks, and MIME checking phases are run, but unlike a real request, the content handler for the response phase is not actually run If you would like to invoke the content handler, the run( ) method will do it: my $status = $subr->run; When you invoke the subrequest's response handler in this way, it will do everything a response handler is supposed to, including sending the HTTP headers and the document body run( ) returns the content handler's status code as its function result If you are invoking the subrequest run( ) method from within your own content handler, you must not send the HTTP header and document body yourself, as this would be appended to the bottom of the information that has already been sent Most handlers that invoke run() will immediately return its status code, pretending to Apache that they handled the request themselves: my $status = $subr->run; return $status; 9.2.3 The Apache::Server Class The Apache::Server class provides the Perl interface to the C API server_rec data structure, which contains lots of low-level information about the server configuration Within a handler, the current Apache::Server object can be obtained by calling the Apache request object's server( ) method At Perl startup time (such as within a startup script or a module loaded with PerlModule), you can fetch the server object by invoking Apache->server directly By convention, we use the variable $s for server objects #at request time sub handler { my $r = shift; my $s = $r->server; } #at server startup time, e.g., PerlModule or PerlRequire my $s = Apache->server; This section discusses the various methods that are available to you via the server object They correspond closely to the fields of the server_rec structure, which we revisit in Chapter 10 is_virtual() This method returns true if the current request is being applied to a virtual server This is a read-only method my $is_virtual = $s->is_virtual; log() The log( ) method retrieves an object blessed into the Apache::Log class You can then use this object to access the full-featured logging API See Section 9.1.6.2" for details use Apache::Log (); my $log = $s->log; The Apache::Server::log() method is identical in most respects to the Apache::log() method discussed earlier The difference is that messages logged with Apache::log() will include the IP address of the browser and add the messages to the notes table under a key named error-notes See the description of notes() under Section For variety, this version of hangman stores its session ID in a client-side cookie rather than in the URI Because this is the final and most feature-rich version of hangman, we give the code in its entirety in Example 5.8 A variety of things have changed Let's start with the table definition: CREATE TABLE hangman ( session_id char(8) primary key, username char(40) default 'anonymous', WORD char(30), GUESSED char(26), GAMENO int, WON int, GUESSES_LEFT int, TOTAL int, modified timestamp, KEY(modified) ) In addition to the state variables that we've used before, we've added a new column named username with a default value of anonymous When a new user starts playing the game, he is initially anonymous Whenever he wins a game, he gets the right to enter his name or handle into the database Subsequently, his name is displayed on the hangman page in nice bold red letters, and it also appears on the top winners list, provided the user can score high enough to get there Even though the table definition has changed, the get_state() and set_state() subroutines used in the previous version of the game are sufficiently generic that they don't need alteration The other change is that the session ID is now stored in a cookie, rather than in a URI The code required to store the session ID in a cookie is similar to what we used earlier for the shared memory example (Example 5.5): sub get_session_id { my(@result); expire_old_sessions(); my $id = cookie('sessionID'); return @result if defined($id) and $id =~ m/^([a-h0-9]{$ID_LENGTH})$/o and @result = check_id($id); # If we get here, there's not already a valid cookie my $session_id = generate_id(); die "Couldn't make a new session id" unless $session_id; return $session_id; } get_session_id() attempts to retrieve a cookie named sessionID If it finds such a cookie, it first checks that the session ID looks right and then passes it to check_id() to confirm that the session is in the database If there's no session cookie, it calls generate_id() to create a new ID and return it Later when we generate the HTTP header we will incorporate this session ID into a cookie that is sent to the client The biggest change relative to the previous version of the script is the addition of a new subroutine called show_scores(), which displays an HTML table of the top 15 winners, the number of games they've won and lost, the average number of letters guessed per word, and an aggregate score This subroutine is called at the end of each game by show_restart_form(), and is also called whenever the user presses the new "Show High Scores" button (CGI parameter show_scores ) The top of the show_scores() routine looks like this: sub show_scores { my($current_session, $count) = @_; my $tries = TRIES; my $sth = $DBH->prepare( 1 and TOTAL+GUESSES_LEFT > $tries and WON > 0 ORDER BY SCORE DESC LIMIT $count END The core of show_scores() is a big SQL SELECT statement that retrieves the top- scoring players based on a formula that divides the percentage of games won by the average number of guesses per game The SQL statement sorts the returned list in descending order by score, then skims off the top records and returns them The remainder of the routine calls execute() followed by a fetchrow_array() loop Each retrieved record is turned into a row of an HTML table and printed out The code is straightforward; see the listing for the details Another significant change is in the show_guess_form() routine: sub show_guess_form { my $state = shift; print start_form(-name => 'gf'), "Your guess: ", textfield(-name => 'guess', -value => '', -override = submit(-value => 'Guess'), br({-clear => 'ALL'}), submit(-name => 'show_scores', -value => 'Show High S submit(-Style => 'color: red', -name => 'abort', -value => print end_form; } This version of show_guess_form() adds a new button labeled "Give Up," which allows the user to give up and move on to the next word process_guess() is modified to recognize this condition and treat it as an incorrect attempt to guess the whole word Other changes to the hangman script allow the user to enter and edit his name show_restart_form() has been modified to include an HTML text field that prompts the user to type in his name The routine now looks like this: sub show_restart_form { my($state, $status, $session_id) = @_; print start_form; print p("Enter your name for posterity: ", textfield(-name => 'change_name', -value => $state-> if $status eq 'won'; print p("Do you want to play again?", submit(-name => 'restart', -value => 'Another game'), checkbox(-name => 'clear', -label => 'Clear my score') print end_form; show_scores($session_id, TOP_COUNT); } When the restart form is submitted, the script checks for the change_name parameter and calls a new subroutine named set_username() if present: set_username($session_id, param('change_name')) if param('chang set_username(), in turn, issues the appropriate SQL UPDATE command to insert the user's name into the database: sub set_username { my($session, $newname) = @_; $newname = $DBH->quote($newname); $DBH->do("UPDATE $DB_TABLE SET username=$newname WHERE session_id='$session'") || die "update: ", $DBH->errstr; } This subroutine uses a trick that we haven't seen before Because the username is typed in by the user, there's no guarantee that it doesn't contain funny characters, such as quotation marks, which will throw off the SQL parser To avoid this, we pass the username through the DBI quote() function This escapes funny characters and puts quotes around the string, making it safe to use in SQL The final frill on this script is an odd little subroutine defined at the bottom of the code named Apache::DBI:db::ping() : sub Apache::DBI::db::ping { my $dbh = shift; return $dbh->do('select 1'); } MySQL, like some other networked databases, will time out if a client has been idle for some period of time If this happens, the hangman script will fail with a fatal database error the next time it tries to make a query To avoid this eventuality, the Apache::DBI module attempts to reconnect to the database if it notices that the database has gone silent However, Apache::DBI does this checking by calling the database driver's ping() method, and the MySQL DBI driver doesn't implement ping() (at least, not at the time that this was written) To avoid the embarrassment of having our hangman game get hung, we define our own version of ping() It simply calls a SQL SELECT statement that's guaranteed to be true If the database is still up, the call succeeds If the database has timed out, the subroutine returns false and Apache::DBI reestablishes the connection behind the scenes Example 5.8 Hangman with All the Trimmings # file: hangman7.cgi # hangman game with all the trimmings use IO::File (); use CGI qw(:standard); use DBI (); use MD5 (); use strict; use vars qw($DBH $DB_TABLE $ID_LENGTH); use constant WORDS => '/usr/games/lib/hangman-words'; use constant ICONS => '/icons/hangman'; use constant TRIES => 6; use constant TOP_COUNT => 15; # how many top scores to show # session settings use constant EXPIRE => 60*60*24*30; # allow 30 days before exp use constant DB => 'dbi:mysql:www'; use constant DBAUTH => 'nobody:'; use constant SECRET => "something obscure"; use constant COOKIE_NAME => 'hangman7'; use constant MAX_TRIES => 10; $DB_TABLE = "hangman7"; $ID_LENGTH = 8; # Open the database $DBH = DBI->connect(DB, split(':', DBAUTH, 2), {PrintError => 0 || die "Couldn't open database: ", $DBI::errstr; # get the current session ID, or make one my($session_id, $note) = get_session_id(); # retrieve the state my $state = get_state($session_id) unless param('clear'); # reinitialize if we need to we need to check for "change_na # because it's possible for the user to hit return in the chang $state = initialize($state) if !$state or param('restart') or param('change_name'); # process the current guess, if any set_username($session_id, param('change_name')) if param('chang my($message, $status) = process_guess(param('guess') || '', $st unless param('show_scores'); # start the page print header(-Cookie => cookie(-name => COOKIE_NAME, -value => $session_id, -expires => '+' EXPIRE 'd') ), start_html(-Title => 'Hangman 7', -bgcolor => 'white', -onLoad => 'if (document.gf) document.gf.guess.f h1('Hangman 7: DBI Sessions in Cookies'); if (param() and !cookie(COOKIE_NAME)) { print h2(font({-color => 'red'}, footer(); exit 0; } print h2(font({-color => 'red'}, "Player: $state->{username}")) $state->{username} and $state->{username} ne 'anonymous'; print p(font({-color => 'red'}, $note)) if $note; # save the modified state save_state($state, $session_id); # draw the statistics show_status($state); # Prompt the user to restart or for his next guess if (param('show_scores')) { show_scores($session_id, TOP_COUNT); print start_form, submit(-name => 'play', -value => 'Play') } else { # draw the picture show_picture($state); show_word($state); print h2(font({-color => 'red'}, $message)) if $message; if ($status =~ /^(won|lost)$/) { show_restart_form($state, $status, $session_id); } else { show_guess_form($state); } } footer(); $DBH->disconnect; ########### subroutines ############## # This is called to process the user's guess sub process_guess { my($guess, $state) = @_; # lose immediately if user has no more guesses left return ('', 'lost') unless $state->{GUESSES_LEFT} > 0; # lose immediately if user aborted if (param('abort')) { $state->{TOTAL} += $state->{GUESSES_LEFT}; $state->{GUESSES_LEFT} = 0; return (qq{Chicken! The word was "$state->{WORD}."}, 'lo } # break the word and guess into individual letters my %guessed = map { $_ => 1 } $state->{GUESSED} =~ /(.)/g; my %letters = map { $_ => 1 } $state->{WORD} =~ /(.)/g; # return immediately if user has already guessed the word return ('', 'won') unless grep(!$guessed{$_}, keys %letters # do nothing more if no guess return ('', 'continue') unless $guess; # This section processes individual letter guesses $guess = lc $guess; return ("Not a valid letter or word!", 'error') unless $guess =~ /^[a-z]+$/; return ("You already guessed that letter!", 'error') if $guessed{$guess}; # This section is called when the user guesses the whole wo if (length($guess) > 1 and $guess ne $state->{WORD}) { $state->{TOTAL} += $state->{GUESSES_LEFT}; $state->{GUESSES_LEFT} = 0; return (qq{You lose The word was "$state->{WORD}."}, ' } # update the list of guesses foreach ($guess =~ /(.)/g) { $guessed{$_}++; } $state->{GUESSED} = join '', sort keys %guessed; # correct guess word completely filled in unless (grep(!$guessed{$_}, keys %letters)) { $state->{WON}++; return (qq{You got it! The word was "$state->{WORD}."}, } # incorrect guess if (!$letters{$guess}) { $state->{TOTAL}++; $state->{GUESSES_LEFT} ; # user out of turns return (qq{The jig is up The word was "$state->{WORD}" if $state->{GUESSES_LEFT} {GUESSES_LEFT}; my $picture = sprintf("%s/h%d.gif", ICONS, TRIES-$tries_lef print img({-src => $picture, -align => 'LEFT', -alt => "[$tries_left tries left]"}); } # print the status sub show_status { my $state = shift; my $current_average = $state->{TOTAL}/$state->{GAMENO}; my $overall_average = $state->{GAMENO}>1 ? ($state->{TOTAL}-(TRIES-$state->{GUESSES_LEFT}))/($state my $score = $overall_average > 0 ? (100*$state->{WON}/($state->{GAMENO}*$overall_average)) # print the word with underscores replacing unguessed lette print table(TR({-width => '90%'}, td(b('Word #:'), $state->{GAMENO}), td(b('Won:'), $state->{WON}), td(b('Guessed:'), $state->{GUESSED}), ), TR( td(b('Current average:'), sprintf("%2.3f", $c td(b('Overall average:'), sprintf("%2.3f", $o td(b('Score:'), sprintf("%3.0f", $score)) ) ); } sub show_word { my $state = shift; my %guessed = map { $_ => 1 } $state->{GUESSED} =~ /(.)/g; print h2("Word:", map {$guessed{$_} ? $_ : '_'} $state->{WORD} =~ /(.)/g); } # print the fill-out form for requesting input sub show_guess_form { my $state = shift; print start_form(-name => 'gf'), "Your guess: ", textfield(-name => 'guess', -value => '', -override = submit(-value => 'Guess'), br({-clear => 'ALL'}), submit(-name => 'show_scores', -value => 'Show High S submit(-Style => 'color: red', -name => 'abort', -value => print end_form; } # ask the user if he wants to start over sub show_restart_form { my($state, $status, $session_id) = @_; print start_form; print p("Enter your name for posterity: ", textfield(-name => 'change_name', -value => $state-> if $status eq 'won'; print p("Do you want to play again?", submit(-name => 'restart', -value => 'Another game'), checkbox(-name => 'clear', -label => 'Clear my score') print end_form; show_scores($session_id, TOP_COUNT); } # pick a word, any word sub pick_random_word { my $list = IO::File->new(WORDS) || die "Couldn't open ${\WORDS}: $!\n"; my $word; rand($.) < 1 && ($word = $_) while ; chomp $word; $word; } ################### state maintenance ############### # This is called to initialize a whole new state object # or to create a new game sub initialize { my $state = shift; $state = {} unless $state; $state->{WORD} = pick_random_word(); $state->{GUESSES_LEFT} = TRIES; $state->{TOTAL} += 0; $state->{GUESSED} = ''; $state->{GAMENO} += 1; $state->{WON} += 0; $state->{username} = param('change_name') if param('change_ return $state; } # Retrieve the session ID from the path info If it's not # already there, add it to the path info with a redirect sub get_session_id { my(@result); expire_old_sessions(); my $id = cookie(COOKIE_NAME); return @result if defined($id) and $id =~ m/^([a-h0-9]{$ID_LENGTH})$/o and @result = check_id($id); # If we get here, there's not already a valid cookie my $session_id = generate_id(); die "Couldn't make a new session id" unless $session_id; return $session_id; } # Find a new unique ID and insert it into the database sub generate_id { # Create a new session id my $tries = 0; my $id = hash(SECRET rand()); while ($tries++ < MAX_TRIES) { last if $DBH->do("INSERT INTO $DB_TABLE (session_id) VAL $id = hash($id); } return undef if $tries >= MAX_TRIES; # we failed return $id; } # check to see that an old ID is valid sub check_id { my $id = shift; return ($id, '') if $DBH->do("SELECT 1 FROM $DB_TABLE WHERE session_id='$ return ($id, 'The record of your game may have expired Re if $DBH->do("INSERT INTO $DB_TABLE (session_id) VALUES ( return (); } # generate a hash value sub hash { my $value = shift; return substr(MD5->hexhash($value), 0, $ID_LENGTH); } sub expire_old_sessions { $DBH->do(prepare("SELECT * FROM $DB_TABLE WHERE sess AND WORDNULL") || die "Prepare: ", $DBH->errstr; $sth->execute || die "Execute: ", $sth->errstr; my $state = $sth->fetchrow_hashref; $sth->finish; return $state; } # save the state in the database sub save_state { my($state, $id) = @_; my $sth = $DBH->prepare(execute(@{$state}{qw(WORD GUESSED GAMENO WON TOTAL GU || die "execute: ", $DBH->errstr; $sth->finish; } # Return true if the current session is one of the top ten # Overall score is the percentage of games won weighted by the # number of guesses taken sub show_scores { my($current_session, $count) = @_; my $tries = TRIES; my $sth = $DBH->prepare( 1 and TOTAL+GUESSES_LEFT > $tries and WON > 0 ORDER BY SCORE DESC LIMIT $count END ; $sth->execute || die "execute: ", $sth->errstr; my @rows = th([qw(Name Games Won Average Score)]); while (my(@rec) = $sth->fetchrow_array) { my $id = shift @rec; push @rows, $id eq $current_session ? th({-align => 'LEFT'}, \@rec) : td(\@rec); } print br({-clear => 'ALL'}), table({-border => 'undef', -width => '75%'}, caption(b("Top $count Winners")), TR(\@rows)); $sth->finish; } # change the username in the database sub set_username { my($session, $newname) = @_; $newname = $DBH->quote($newname); $DBH->do("UPDATE $DB_TABLE SET username=$newname WHERE session_id='$session'") || die "update: ", $DBH->errstr; } # fix the absence of ping() in the mysql interface sub Apache::DBI::db::ping { my $dbh = shift; return $dbh->do('select 1'); } # print bottom of page sub footer { print hr, a({-href => '/'}, "Home"), p(cite({-Style => "fontsize: 10pt"}, 'graphics courtesy And end_html(); } ... PerlFixupHandler PerlChildInitHandler PerlHandler PerlChildExitHandler PerlLogHandler PerlPostReadRequestHandler PerlInitHandler PerlTransHandler PerlCleanupHandler PerlHeaderParserHandler PerlStackedHandlers... PerlStackedHandlers PerlAccessHandler PerlMethodHandlers PerlAuthenHandler PerlDirectiveHandlers PerlAuthzHandler PerlSections PerlTypeHandler PerlSSI hook() The hook( ) function can be used at runtime to determine whether... The vast bulk of the functionality of the Perl API is contained in the Apache object However, a number of auxiliary classes, including Apache: :Table, Apache: :Connection, and Apache: :Server, provide additional methods for accessing and manipulating the state of the