75 Creating a Text-Based Web Page Counter


#75 Creating a Text-Based Web Page Counter

One popular element of many web pages is a page counter that increments each time a request for the page in question is served . A quick glance at the counter value then lets you see how popular your pages are and whether they're seeing lots of visitors . While counters aren't typically written as shell scripts, that doesn't mean it can't be done, and we'll throw caution to the wind and build it ourselves !

The fundamental challenge with this particular script is that there's a possible race condition , a situation in which two people visit the page simultaneously and each of the counter scripts steps on the other when writing to the data file. You can try to solve the race condition within the script itself, but that's surprisingly tricky. Consider the following few lines of code:

 while [ -e $lockfile ] ; do   sleep 1 done touch $lockfile 

It seems as though this should work, only allowing the script to escape the while loop when the lock file doesn't exist and then immediately creating the lock file to keep everyone else out. But it doesn't work. Remember that two copies can be running essentially simultaneously, so what happens if one ascertains that there's no lock file, gets through the while loop, and then is swapped out by the CPU before creating the new lock file? Meanwhile, the second script tests, also finds there's no lock file, and creates one, convinced it now has exclusive access to the data. Then the first script swaps back in, it touches the lock file (which already exists, though it doesn't know that), and mayhem ensues.

The solution is to use a utility written for the job, to ensure that you don't encounter a race condition in the middle of your locking sequence. If you're lucky, your Unix has the helpful lockf command, which executes a specific command while holding an exclusive file lock. If not, many Unixes have the lockfile utility as an alternative. To be portable, this script works with both, depending on what it can find. Script #10 discusses this issue in greater depth too.

The Code

 #!/bin/sh # counter - A simple text-based page counter, with appropriate locking. myhome="/home/taylor/web/wicked/examples" counter="$myhome/counter.dat" lockfile="$myhome/counter.lck" updatecounter="$myhome/updatecounter" # Note that this script is not intended to be called directly from # a web browser so it doesn't use the otherwise obligatory # content-type header material. # Ascertain whether we have lockf or lockfile system apps if [ -z $(which lockf) ] ; then   if [ -z $(which lockfile) ] ; then     echo "(counter: no locking utility available)<br>"     exit 0   else # proceed with the lockfile command     if [ ! -f $counter ] ; then       echo "0" # it'll be created shortly     else       cat $counter     fi      trap "/bin/rm -f $lockfile" 0     lockfile -1 -l 10 -s 2 $lockfile     if [ $? -ne 0 ] ; then       echo "(counter: couldn't create lockfile in time)"        exit 0     fi     $updatecounter $counter   fi else   if [ ! -f $counter ] ; then     echo "0" # it'll be created shortly   else     cat $counter   fi   lockf -s -t 10 $lockfile $updatecounter $counter   if [ $? -ne 0 ] ; then     echo "(counter: couldn't create lockfile in time)"   fi fi exit 0 

The counter script calls $updatecounter , a second, smaller script that's used to actually increment the counter. It ignores any file-locking issues, assuming that they're dealt with elsewhere:

 #!/bin/sh # updatecounter - A tiny script that updates the counter file to #   the value specified. Assumes that locking is done elsewhere. if [ $# -ne 1 ] ; then   echo "Usage: 
 #!/bin/sh # updatecounter - A tiny script that updates the counter file to # the value specified. Assumes that locking is done elsewhere. if [ $# -ne 1 ] ; then echo "Usage: $0 countfile" >&2 exit 1 fi count="$(cat $1)" newcount="$((${count:-0} + 1))" echo "$newcount" > $1 chmod a+rw $1 exit 0 
countfile" >&2 exit 1 fi count="$(cat )" newcount="$((${count:-0} + 1))" echo "$newcount" > chmod a+rw exit 0

How It Works

The counter and updatecounter scripts do something quite simple: Together they open up a file; grab the number therein; increment it; save the new, larger value; and display that value. All the complexity in these scripts is associated with locking files to ensure that there's no collision when updating the counter value.

The basis of the main conditional ascertains whether the system has lockf (the preferred choice), lockfile (an acceptable alternative), or nothing:

 if [ -z $(which lockf) ] ; then   if [ -z $(which lockfile) ] ; then      echo "(counter: no locking utility available)<br>" 

The which command looks for a specific command in the current PATH ; if it can't find it, it returns zero. If neither lockf nor lockfile exists, the script just refuses to run and quits, but if either locking system can be found, it uses that and proceeds.

The search path for scripts running within the CGI environment is often shorter than the path for interactive scripts, so if you know that the system has lockf or lockfile and the script can't find it, you'll need to do one of two things. Modify the runtime PATH by adding a line of code like the following to the beginning of the script, supplying the directory that contains the program in question:

 PATH="${PATH}:/home/taylor/bin" 

Or replace both $(which lockf) and $(which lockfile) with the full lockfile or lockf path and filename that you want to use in the script.

Running the Script

This script isn't intended to be invoked directly by a user or linked to directly by a web page. It is most easily run as a server-side include (SSI) directive on an SSI-enabled web page, typically denoted by changing the suffix of the enabled page from .html to .shtml so that the web server knows to process it specially.

The .shtml web page would have a line of code embedded in the HTML similar to the following:

 <!--#exec cmd="/wicked/examples/counter.sh"--> 

The Results

A short SSI page that includes a call to the counter.sh script is shown in Figure 8-7. This same HTML page also uses Script #76, Displaying Random Text .

click to expand
Figure 8-7: Server-side includes let us invoke shell scripts from within HTML files

Hacking the Script

If your system doesn't support SSI, another approach to getting a counter, though a bit clunky , would be to have a wrapper script that emulates this simple SSI mechanism. Here's an example in which the string "---countervalue---", embedded in the HTML page to display, will be replaced with the actual numeric counter value for the specified HTML file:

 #!/bin/sh # streamfile - Outputs an HTML file, replacing the sequence #   ---countervalue--- with the current counter value. # This script should be referenced, instead of $infile, from other pages. infile="page-with-counter.html" counter="./counter.sh" echo "Content-type: text/html" echo "" value="$($counter)" sed "s/---countervalue---/$value/g" < $infile exit 0 



Wicked Cool Shell Scripts. 101 Scripts for Linux, Mac OS X, and Unix Systems
Wicked Cool Shell Scripts
ISBN: 1593270127
EAN: 2147483647
Year: 2004
Pages: 150
Authors: Dave Taylor

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net