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>