Archive for 2月 2009

s3sync.rb と Ruby のバージョン

結論から言うと s3sync.rb 1.2.6 は、Ruby 1.9.1 では動作しない。Ruby 1.8.7 で問題なく動いた。

Linux サーバのバックアップ先を Amazon S3 にするため、s3sync.rb を利用することにした。昨年、サーバを CentOS にしてから Ruby を使う機会が無かったため、Ruby をインストールしていなかった。そこでちょうど 1.9 がリリースされたところでもあったので、何も考えずに 1.9.1 を何も考えずにインストールしたのだが、残念ながらこの版では s3sync.rb が動作しないことが分かった。これは s3sync.rb と同時に入手できる s3cmd.rb も同様である。

せっかく入れた Ruby 1.9.1 をダウングレードするのは気が進まなかったので、これを残したまま 1.8.7 を別のディレクトリに導入して、両方のバージョンを切り替えられるようにした。Ruby のインストール時、次のように configure を指定してやれば、既存バージョンと競合することはなさそうだ。

$ ./configure –prefix=/usr/local/opt/ruby-1.8.7

これでインストールすれば prefix で指定したディレクトリの下にすべてのファイルが配置される。この状態で /usr/local/opt/ruby-1.8.7/bin にパスを通せば、1.8.7 の Ruby を利用することができるようになり、結果的に s3sync.rb を動作させることができるようになった。もともと s3sync.rb は cron で動かすつもりだったので、バッチファイルの中で Ruby のバージョンを切り替えることにする。

Google App Engine 用のログイン認証機能を作ってみた

Google App Engine では Google アカウントの認証機能がそのまま利用できるので、ログイン管理の手間が掛からない。しかし、当然のことながらアプリケーションの利用者は Google アカウント保持者に限定されてしまうし、逆に何も制限を加えないと Google アカウント保持者は誰でもアクセスできてしまうことになる。

そこで Google アカウントとは無関係にアカウントを管理する仕掛けのひな形を作ってみた。サンプルは http://teshigoto-showcase-02.appspot.com/ で実際に稼働している。ここで新規登録を行えばアカウントを取得することができるが、試用アカウントを使えば新規登録しなくてもログインすることができる。試用アカウントはサンプルサイトのトップページに記載してある。また、ソースコードは http://www.teshigoto.net/showcase.html の「Another Loin Utility – GAE case study 2」に置いた。

アカウントの新規登録を行うとき、同一ログイン名の同時登録を避けるため、ファイルを使ったロック処理を行おうとしたが、Google App Engine では肝心の fcntl を利用できない。そこで今回は登録依頼のあったアカウントを無条件に Datastore に格納してしまい、その後改めて同一のログイン名による検索を行って複数のエンティティが見つかったときは、自分自身を削除して「同じログイン名がすでに登録されてます」メッセージを出す、という手順にしてみた。具体的には次のような感じ。

new_user = users.Users(id = new_login_id, ...)
new_user.put()

query = users.Users.all()
query.filter('id =', new_login_id)

count = 0
for user in query:
    count += 1

if count == 1:
    #  regist copleted
    pass
else:
    # duplicated id
    new_user.delete()

このやり方だと同じログイン名を同時に登録しようとして、どちらも失敗する可能性が残るが、一般的なログイン名ではそれでも構わないだろうと判断した。

ログインすればセッション管理も必要になるので、これも作ってみた。前回公開したサンプルでもセッション管理を作ったものの中途半端だったので、もう少し手を入れてみた。セッション ID を Cookie として送信するために準備する機能(送信そのものは画面表示モジュールが行うことになる)や、セッション継続時に保存しておくデータを Datasore に格納する機能などがある。

import os
import re
import time
import random
import hashlib

from google.appengine.ext import db


class SessionDb(db.Expando):
    sid = db.StringProperty()


DEFAULT_SID_NAME = 'alu_001'

class Session():

    def __init__(self, req, res, sid_name=DEFAULT_SID_NAME):
        self.sid_name = sid_name
        self.req = req
        self.res = res
        if sid_name in req.cookies:
            self.sid_value = req.cookies[sid_name]
        else:
            self.sid_value = ''

    def new_ssn(self, ssl=False):
        random.seed()
        random_str = str(random.random()) + str(random.random())
        random_str = random_str + str(time.time())
        random_str = random_str + os.environ['REMOTE_ADDR']

        self.sid_value = hashlib.sha256(random_str).hexdigest()

        cookie_val = self.sid_name + '=' + self.sid_value
        if ssl:
            cookie_val += ';secure'

        self.res.headers.add_header('Set-Cookie', cookie_val)

        ssn_db = SessionDb(sid=self.sid_value)
        ssn_db.put()

    def destroy_ssn(self):
        ssn_db = SessionDb.all()
        ssn_db.filter('sid =', self.sid_value)
        ssn = ssn_db.fetch(1)
        db.delete(ssn)

        expires = time.strftime("%a, %d-%b-%Y %H:%M:%S GMT", time.gmtime(0))
        cookie_val = self.sid_name + '=null' + ';expires=' + expires
        self.res.headers.add_header('Set-Cookie', cookie_val)

    def get_ssn_data(self, k):
        ssn_db = SessionDb.all()
        ssn_db.filter('sid =', self.sid_value)
        ssn = ssn_db.fetch(1)

        return ssn[0]._dynamic_properties[k]

    def set_ssn_data(self, k, v):
        ssn_db = SessionDb.all()
        ssn_db.filter('sid =', self.sid_value)
        ssn = ssn_db.fetch(1)
        ssn[0]._dynamic_properties[k] = v

        ssn[0].put()

    def chk_ssn(self):
        ssn_db = SessionDb.all()
        ssn_db.filter('sid =', self.sid_value)
        count = 0
        for i in ssn_db:
            count += 1

        if count == 1:
            return True
        else:
            return False

さらに、今回のサンプルでも多言語対応を試みた。前回はオリジナルのちょっと安直な方法で実装したが、今回は gettext を利用してみた。これによって言語リソースを完全に Python のソースコードと分離することができた。また、表示言語を固定せずに、利用者からの指示で動的に言語を切り替えられるようにした。具体的には Python ライブラリリファレンス「6.28 gettext — 多言語対応に関する国際化サービス」を参考にしながら、おおよそ次のような流れで処理を行っている。

locale_path = os.path.dirname(__file__) + '/../locale'   # リソースファイルを置いた場所
supported_lang = ['ja', 'en']    # サポートする言語の一覧
for lang_name in self.supported_lang:
    lang[lang_name] = gettext.translation('resource', locale_path, languages=[lang_name])

accept_lang = 'ja'   # 実際の表示言語をここで指定
lang[accept_lang].install()
_ = lang[accept_lang].gettext

print _('message_sentence')    # 指定した言語で表示される

Google App Engine と fcntl

今、Google App Engine 用のサンプル・アプリケーションを作っている。その中で排他制御のためにファイルのロック機能を利用しようと思ったのだが、やはり予想通り Python の fcntl モジュールは Google App Engine 環境に含まれていなかった。確かにファイルの書き出しは仕様上出来ないことになっているものの、読み込みは可能なので、もしかしたらロックはできるかなと考えたのだが、やはり無理なようだ。排他制御は他の方法を考えなければならなくなった。