Google App Engine でカウンターを作ってみた


↑試作品なので消える可能性あり
こちらで動かしています。
http://sugi1982.appspot.com/
どちらも共通、リロードされるたびに数字がインクリメントされます。


SDKのversion 1.2.1 からimages APIがパワーアップして、画像を合成することができるようになったとか。

  • The Images API now supports compositing images and calculating a color histogram for an image.

ということは内部で数字の画像をつなぎ合わせて、アクセスされるたびに画像の数字が変わるカウンターができるんじゃないか?と思って作ってみた。
まだPythonに慣れてなかったりGoogle App Engineにも慣れてなかったりで予想以上に時間と手間がかかってしまったけど…orz


作りは単純で、

  • 内部で0~9に該当する画像データを持つ(管理者権限で画像をupload)
  • アクセスカウンターのModelを作成してアクセス数を保持する
  • getを受けたときにカウンターの数字をインクリメントさせ、その数字の各桁に対応する画像データを作成、つなぎ合わせ出力

というだけ。


とりあえずアクセス数だけはこうやって取れるようになった。せっかくなので

  • ユーザー毎にURLを作成して独立して動くようにさせたり
  • 画像のデザインやサイズを調整したりできるように

ということも考慮したい。
あとアクセス解析的なものはどこまでできるんだろう…?ちょっとそこまで作ろうと思ったけど各アクセス毎に時間やUserAgentをレコードに残すとなるとカウンターのインクリメントと同時にトランザクションで処理して…とか考えたらよくわからなくなったのでもう1回勉強し直してじっくり検討する。


ソースを以下にべたべたと貼ってみる。
コメントも書いてないし自分で見直しもしていないので色々イケてない部分があるかと思います。
これはマズいだろ、などといった点がありましたらご指摘いただけると幸いです。

application: sugi1982
version: 1
runtime: python
api_version: 1

handlers:
- url: /uploader
  script: main.py
  login: admin

- url: /.*
  script: main.py
  • main.py
# -*- coding: utf-8 -*-

import logging
import os
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app
from counter import Counter
from uploader import Uploader
from viewer import Viewer


class MainPage(webapp.RequestHandler):
    def get(self):
        template_values = {
            }
        path = os.path.join(os.path.dirname(__file__), 'templates', 'index.html')
        self.response.out.write(template.render(path, template_values))


application = webapp.WSGIApplication([
    ('/counter/.*', Counter),
    ('/view/.*', Viewer),
    ('/uploader', Uploader),
    ('/', MainPage),
    ], debug = True)

def main():
    logging.getLogger().setLevel(logging.DEBUG)
    webapp.template.register_template_library('templatefilters')
    run_wsgi_app(application)
    
if __name__ == "__main__":
    main()
  • counter.py
# -*- coding: utf-8 -*-

import logging
from google.appengine.api import images
from google.appengine.ext import db
from google.appengine.ext import webapp
from numbers import Numbers
from record import Record


class Counter(webapp.RequestHandler):
    def get(self):
        record = Record.get_or_insert("test")
        key = db.run_in_transaction(self.increment_count, record.key())
        number   = Record.get(key).count
        
        digits = []
        while number / 10 != 0:
            digits.append(number % 10)
            number /= 10
        digits.append(number)
        digits.reverse()

        image_list = []
        numbers = Numbers.get_by_key_name("test")
        for i in range(len(digits)):
            image_list.append((numbers.digits[digits[i]], 64 * i, 0, 1.0, images.TOP_LEFT,))

        image = images.composite(image_list, 64 * len(digits), 128)
        self.response.headers['Content-Type'] = 'image/png'
        self.response.out.write(image)

    def increment_count(self, key):
        record = Record.get(key)
        if record.count == None:
            record.count = 1
        else:
            record.count += 1
        return record.put()
  • numbers.py
# -*- coding: utf-8 -*-

from google.appengine.ext import db


class Numbers(db.Model):
    digits = db.ListProperty(db.Blob, default = [db.Blob()] * 10)
  • record.py
# -*- coding: utf-8 -*-

from google.appengine.ext import db


class Record(db.Model):
    count    = db.IntegerProperty()
    datetime = db.DateTimeProperty(auto_now = True)
  • uploader.py(管理者しかアクセスできない…はず)
# -*- coding: utf-8 -*-

import os
import logging
from google.appengine.api import images
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from numbers import Numbers


class Uploader(webapp.RequestHandler):
    key = "test"
    def get(self):
        template_values = {
            }
        path = os.path.join(os.path.dirname(__file__), 'templates', 'upload.html')
        self.response.out.write(template.render(path, template_values))

    def post(self):
        num = int(self.request.get('num'))
        logging.debug(num)
        try:
            image = images.Image(self.request.get('image'))
            logging.debug(image)
            image.resize(64, 128)
            numbers = Numbers.get_or_insert(self.key)
            numbers.digits[num] = db.Blob(image.execute_transforms())
            numbers.put()
        except images.NotImageError:
            logging.error(images.NotImageError)

        self.redirect(self.request.url)