The Web MasterUSENIX

 

Counters for Your CGI Programs

taylor, dave

by Dave Taylor
<[email protected]>

Dave Taylor is president of the interface design firm Intuitive Systems and a long-time USENIX member



Usually in this column I demonstrate stand-alone CGI programs that you can drop onto your own UNIX-based (of course) Web site, but this time I'm going to offer you a helpful snippet of code instead ­ one that addresses a common desire on Web sites: counting visitors.

There are a lot of GIF-based counters available, including my favorite, "wwwcount," which can do just about everything from wash your sink to polish your car (well, almost). But they don't help you add your own counter to existing CGI programs; they're standalone applications that you have to install separately.

You can also use server-side include directives to get page counters if your system is set up correctly. These SSI snippets look like

<!--#counter file=".count"-->

or similar in your HTML source. (But because they're replaced with the actual numeric output of the counter in the page before it's delivered to the browser, you can't see this with a "view source" on the page. Try it: visit my company home page and view the source to see the counter on the bottom: <www.intuitive.com>).

This works well for static pages, but the output of the CGI program isn't parsed by the Web server prior to its being sent to the client browser, so short of rewiring your server, it's not a solution to this particular dilemma.

And so the solution is to have a general-purpose counter subroutine that you can drop into your CGI programs as they're developed.

Version One: Classic UNIX File Locking

The challenge with a counter, of course, is that you need to compensate for possible race conditions where two instantiations of the program might step on each other during the open-file/read-contents/increment/save-new-contents loop. The traditional strategy is to use a separate .lock file and that's what this first version of the subroutine does:

int
visitcount ()
{
 /** How many times has this routine been called? Use temp file COUNTER to keep track and LOCKFILE as a lock file.
 **/

 FILE *fd;
 char buffer[40];
 int current_value, lockid, loopcount = 0;

 while ((lockid = open(LOCKFILE, O_CREAT | O_EXCL, 0777)) < 0) {
  usleep(10000);
  if (loopcount++ > MAXWAITS) {
   return(DEFAULT_VALUE);
  }
 }

 if ((fd = fopen(COUNTER, "r")) == NULL)
  current_value = 0;
 else {
  fscanf(fd, "%d", &current_value);
  (void) fclose(fd);
 }

 current_value++; /* increment! **/
 if ((fd = fopen(COUNTER, "w")) != NULL) {
  fprintf(fd, "%d\n", current_value);
  (void) fclose(fd);
}
 (void) close(lockid);
 (void) unlink(LOCKFILE);

 return(current_value);
}

Here are the relevant definitions. LOCKFILE should be set to the name of a lock file, usually on the same file device as the counter file. COUNTER is the name of the file within which the subroutine keeps track of visitor count. MAXWAITS indicates how many times the program can go into the usleep() sleep wait loop (you'll want to keep this low if it's a CGI program). DEFAULT_VALUE is the value to return if we can't get the lock file.

When run on Linux 2.0.30, this counter sporadically failed and lost track of the counter value, which was highly annoying. On other versions of UNIX it was more reliable but that didn't solve the problem within my code!

Version Two: Flock

The solution was to modify the code to use the flock() file-locking mechanism, which begat some modifications to the program:

In addition to being a smaller and more elegant solution, it's also more reliable because the requirement for the atomic-level (uninterruptible) check-and-lock event is done within OS code, rather than my hoping I code it correctly in my own procedure.

You can see both of these procedures in use: the lock file strategy is demonstrated at <www.intuitive.com/origins>, and the flock version is shown at <www.trivial.net>.

A logical extension to this would be to allow multiple counters in the same CGI (indeed, that's exactly what Trivial.Net does; there's a "times started" counter and a "times completed" counter). No problem, make the filename a parameter to the procedure itself.

The other addition would be to allow it to output a graphical representation of the number rather than just text. This turns out to be surprisingly easy if you remember that the CGI itself is sending HTML to standard output. Instead of having the output "12," for example, it could output:

<img src=digit1.gif><img src=digit2.gif>

As long as the digits are in a well-known location (perhaps on their own server), spitting out a stream of individual digits would work fine. There is a small performance penalty you could incur getting a number of tiny graphic files rather than a single, slightly larger, multidigit graphic.

 

?Need help? Use our Contacts page.
First posted: 13th April 1998 efc
Last changed: 13th April 1998 efc
Issue index
;login: index
USENIX home