A minimalist CGI hit counter

I wanted to count hits on my web site. Obviously I wanted this to be simple and low-overhead, I also wanted it to ignore hits from me, since it gets updated frequently, and therefore at least initially I might be the only user generating hits.

So I didn't want to parse query strings, and instead I used PATH_INFO, also I didn't want to generate a GIF image. Here's a simple C++ CGI program that meets the requirement in a typical Linux/Apache environment:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>

#define MAX_PI 30

int main()
{
   int hits = 0;
   char buf[256];
   FILE *fp = 0;
   timespec ts, tr;
   ts.tv_sec = 0;
   ts.tv_nsec = 100000000;
   const char *pi = getenv("PATH_INFO");
   const char *cookie = getenv("HTTP_COOKIE");
   if (!cookie)
      cookie = "";
   bool isme = !strcmp(cookie, "user=teales");
   for (; pi && *pi == '/';) {
      if (strlen(pi) > MAX_PI+1)
         // no buffer overruns please
         break;
      strcpy(buf, pi+1);
      strcat(buf, ".cnt");
      if (!access(buf, F_OK)) {
         // count file exists
         for (;;) {
            // keep trying until we open it
            fp = fopen(buf, "r+");
            if (fp)
               break;
            nanosleep(&ts, &tr);
         }
         int fd = fileno(fp);
         flock fl;
         fl.l_type = F_WRLCK;
         fl.l_whence = SEEK_SET;
         fl.l_start = 0;
         fl.l_len = 0;              // lock all
         fl.l_pid = 0;
         for (;;) {
            // keep trying until we lock it
            if (fcntl(fd, F_SETLK , &fl) != -1)
               break;
            nanosleep(&ts, &tr);
         }
         fread(&hits, sizeof(int), 1, fp);
         if (!isme) {
            hits++;
            fseek(fp, 0, SEEK_SET);
            fwrite(&hits, sizeof(int), 1, fp);
         }
         fl.l_type = F_UNLCK;
         fcntl(fd, F_SETLK , &fl);  // unlock file
         fclose(fp);
      } else {
         fp = fopen(buf, "w");
         if (fp) {
            if (!isme)
               hits++;
            fwrite(&hits, sizeof(int), 1, fp);
            fclose(fp);
         }
      }
      break;
   }
   int cl = sprintf(buf, "var hits = %d;\n", hits);
   printf("Content-length: %d\nContent-type: text/javascript\n\n", cl);
   printf("%s", buf);

   return 0;
}

Upload the C++ source file to your cgi-bin directory, then using telnet, go to that directory, and type:

gcc -o hits.cgi counter.cpp

With the compiler on my server, this produced an executable about 16k in size, which should load and execute pretty quickly. When you've built it, execute the command

chmod 755 hits.cgi

to make the file executable. The source is written to maintain hit count files for as many pages as you like, discriminating between them using the supplied PATH_INFO. It gets round the need to generate a GIF by generating instead a one-line Javascript fragment. Then on your page you can simply have a script block like the first one here:

<head>
<script src="/cgi-bin/hits.cgi/poodle">
</script>
<script>
   // Your script here
</script>
</head>

Once your page has been loaded, the downloaded script block will contain:

var hits = 42;

or whatever. You can then use this global value in client side scripting to fill in the value of a read-only form field, or to do a document write. That way, wherever the hit count appears (if at all), it will be governed by your stylesheet, and can be as unobtrusive or as blatant as you like.

You'll need to set a cookie on the machine where you do the page development. I used the following HTML, uploaded it to my server, then accessed the page. Since the page I'm doing this for doesn't use cookies, nobody else will have one, so I can filter out my hits.

<html>
<head>
<script>
function SetCookie()
{
   var expires = new Date("December 31, 2050");
   var s = expires.toGMTString();
   document.cookie = "user=myusername; expires=" + s;

}
</script>
</head>

<body onLoad="SetCookie()">
Cookie is hopefully set
</body>
</html>

Once you're set up as above, you'll need a test page to see if the hit counting is working. I used this:

<html>
<head>
<title>Hit counter test</title>
<script src=/cgi-bin/hits.cgi/poodle>
</script>
</head>
<body>
<script>document.write("Hit count is: "+hits);</script>
</body>
</html>