Hit Counter

This page includes a hit counter that uses a Google App Engine to store, retrieve, and increment a count. Polling is used continuously after page load to keep the count current - if the page is accessed by another browser, the page count will update this page as well. Polling is set to each 10 seconds.

This page has been accessed times

Error messages:

OK

The Ajax

With the help of the Prototype library, the javascript to do the Ajax request is shown in the box. The hitcount URL has two actions inc and get. The increment uses a POST action, since it updates state on the server, where as the get is a query, and uses the GET action.

An initial inc is followed by periodical get's, to keep the count current. The tag is a string identifing the counter, but could be the URL of this page, properly escaped for CGI.

<body onload="dohitcounter('hitcounter');">

<script type="text/javascript">
function dohitcounter(tag) {
  new Ajax.Updater({success:'hitcount',failure:'errmsg'}, 
      '/hitcount/inc', { method: 'post', parameters: { 'tag':tag }});
      $('errmsg').update("OK");
  new Ajax.PeriodicalUpdater({success:'hitcount',failure:'errmsg'}, 
      '/hitcount/get', { method: 'get', parameters: { 'tag':tag }, frequency: 10 });
      $('errmsg').update("OK");
}
</script>

The Python/GAE

The following URL's are suppored:

inc?tag=<tagname>
Increments an existing tag, creates a new tag, with tagname.
get?tag=<tagname>
Returns the value of an existing tag, or zero.
zero?tag=<tagname>
Destroys tag named tagname.
dump
Lists out all tags and values.

Shown in the box are the three class that I think you should look at first: the data model, the increment code and the query code.

A certain amount of clean up and protection occurs in gettag(), limit the length of tags, and restricting them to the character set of a URL. Also, cancreate() budgets the number of counters that can be created per server start, to avoid a resource exhaustion attack (and running up a bill for storage).

class Counter(db.Model):
    count = db.IntegerProperty(required=True)
    label = db.StringProperty(required=True)


class IncPage(webapp.RequestHandler):
    def post(self):
        self.response.headers['Content-Type'] = 'text/plain'
        tag = gettag(self.request)
        # query database for url u, increment by 1, or otherwise set to 1
        c = db.GqlQuery("SELECT * FROM Counter WHERE label IN :1", [tag]).get()
        if c == None:
            if cancreate():
        	    c = Counter(count=0,label=tag)
            else:
        	    self.response.out.write(str(0))
        	    return
        c.count += 1
        c.put()
        self.response.out.write(str(c.count))
        
    def get(self):
        # only for debugging, get's can be cached and count might not increment
        self.post()

class GetPage(webapp.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        tag = gettag(self.request)
        # query database for url u, return result, or otherwise return 0
        c = db.GqlQuery("SELECT * FROM Counter WHERE label IN :1", [tag]).get()
        if c == None:
            self.response.out.write(str(0))
        else:
            self.response.out.write(str(c.count))
            

The entire code.

Here is the entire code as a text file.