Lofi Page Counters
I made a (very) quick proof of concept for a page visitor counter on a non-database site. Doing this with a database seems trivial, but doing it without seemed like a fun tiny challenge.
The Code
There are essentially three parts:
- The counter file
- The CGI script
- The invocation
The counter file you need to create and make it's content a number "0".
The CGI script is a bash script that makes a lockfile and increments the number in the counter.
#!/usr/bin/env bash
lockfile=/var/tmp/mylock
counter_file="counter.txt"
HOST_NAME='foo-bar-baz.nfshost.com'
# Don't allow outside requests to increment the counter
if [[ ! $HTTP_REFERER =~ $HOST_NAME ]]; then
echo 'Status: 401 Not Authorized'
echo ''
exit 1
fi
# Ensure multiple visits at once don't cause a race condition.
# I'd rather have lower counts than a corrupted file.
# From https://unix.stackexchange.com/a/22047
if (set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null; then
# If script exit early, delete lockfile before ending
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
current_count="$(cat "$counter_file")"
new_count=$(( current_count + 1 ))
echo $new_count > "$counter_file"
# clean up after yourself, and release your trap
rm -f "$lockfile"
trap - INT TERM EXIT
fi
# Return new count
echo 'Status: 200 OK'
echo 'Content-Type: text/plain'
echo ''
echo "$new_count"
The invocation is a Javascript call that returns the new count and updates the element on the page.
<body>
<p>Some test content</p>
<p id="counter__wrapper" style="visibility: hidden;">Visitors: <span id="counter"></span></p>
<script>
const getCount = async () => {
const response = await fetch('./cgi-bin/count.cgi');
const newCount = await response.text();
document.getElementById('counter').textContent = newCount;
document.getElementById('counter__wrapper').style.visibility = 'visible';
}
getCount();
</script>
</body>
I learned some stuff
- File locks are funny. I tried learning about `fcntl` and had the same dread I do when I approach anything in C: lots of old dads trying to explain a complex thing like it's simple with lots of assumed priors. The implementation I found above is certainly not as sexy, and likely not as effective, but it works fine.
- I initially didn't have a check for the HTTP referer and realized I could call the CGI script from anywhere and increment the counter. It was fun to solve a problem that likely every developer in the 90's solved before.
- I really should learn more about UNIX file permissions.
- I love mucking around on file hosting platforms. It feels like magic, reminds me of when I made webpages as a kid.