Milo Land

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

A repo with the code

There are essentially three parts:

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