Google App Engine (Python) でセッションを使ってみる (PHPのsession_regenerate()も)

前提としてDjangoフレームワークは使わない。


まずは、app/util.pyなんかに次のようなデコレーターを作っておく。

# -*- coding: utf-8 -*-
import os
import pickle
import hashlib
import Cookie
import uuid
from datetime import date, date, datetime, timedelta

# Google App Engine imports.
from google.appengine.ext.webapp import template, util
from google.appengine.api import memcache

def use_session(regenerate):
    def use_session_internal(func):
        def use_session_decorator(self):
            self.session_id = None
            self.session = None

            session_id = self.request.cookies.get('coiled-session', None)
            mc = memcache.Client()
            if session_id is not None:
                session = mc.get(session_id, 'session')
                if session is not None:
                    self.session = session
                    if regenerate:
                        mc.delete(session_id, namespace="session")
                        self.session_id = hashlib.sha1(str(uuid.uuid4())).hexdigest()
                    else:
                        self.session_id = session_id

            if self.session_id is None:
                self.session_id = hashlib.sha1(str(uuid.uuid4())).hexdigest()
                self.session = {}

            expires_date = datetime.utcnow() + timedelta(days=1)
            # expires_str = expires_date.strftime("%d %b %Y %H:%M:%S GMT")
            expires_str = expires_date.strftime("%a, %d %b %Y %H:%M:%S UTC")

            cookie = Cookie.SimpleCookie()
            cookie["coiled-session"] = self.session_id
            cookie["coiled-session"]["path"] = "/"
            cookie["coiled-session"]["expires"] = expires_str
            self.response.headers.add_header("Set-Cookie", cookie.output(header=''))

            func(self)

            if self.session_id is not None:
                if self.session is None:
                    mc.delete(self.session_id, namespace="session")
                else:
                    mc.set(self.session_id, self.session, time=60*60*24, namespace="session")
        return use_session_decorator

    return use_session_internal


このデコレーター使う側は、こんな風に書ける。

# -*- coding: utf-8 -*-
from google.appengine.ext.webapp import WSGIApplication, RequestHandler
from app.util import use_session

class LoginPage(RequestHandler):
    @use_session(regenerate=False)
    def get(self):
        pass

    @use_session(regenerate=True)
    def post(self):
        pass


完全なサンプルにはなっていないが、なんとなく雰囲気だけ伝えたいところ。


ポイントとして、PHPのセッションはデフォルトではファイルに勝手に格納されるが、Google App Engineではファイルに書き込むことはできないので、memcachedを使っている。


また、デコレーターで引数を取れるようにするために、デコレーターの定義が三重の定義になっている。(defの中にdefの中にdefが入っている。)


アクセスするたびにセッションの保持期間が延長される実装になっている。


セッションIDは必ずアプリケーションで作成し、外部で作られた不正なセッションIDを受け入れないように気をつけている。(PHPで有名なセキュリティーホールありましたよね・・・。)