非同期サービスフレームワーク ginkgo (docs) というものが今年の PyCon US で発表された。サービスを作るには、デーモン化なり、PIDファイルの作成なり、シグナルの処理なり、プログラムでやらないといけないことが多い。 Twisted フレームワークだと、サービスまわりの機能がありますが、 gevent ベースのサービスを作るには結構大変なので、 ginkgo というものが作られた。
ginkgo は Service クラスを提供して、そのクラスを継承して、サービスのことを実装するだけ。
簡単なウェブサービス
まずは、WSGIベースの Hello World
from ginkgo import Service
from ginkgo.async.gevent import WSGIServer
class HelloWorldWebServer(Service):
def __init__(self):
self.add_service(WSGIServer(('0.0.0.0', 8000), self.handle))
def handle(self, environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return ["<strong>Hello World</strong>"]
これを server.py に保存することで、ginkgo コマンドで起動する
$ ginkgo server.HelloWorldWebServer
Starting process with server.HelloWorldWebServer...
そして、 curl でリクエストを投げると、Hello World が返ってくる
$ curl localhost:8000
<strong>Hello World</strong>
簡単な cron サービス
次は何秒ごとにハンドラーを実行する cron サービスを作ります。
class CronService(Service):
def __init__(self, seconds=1, handle=None, start=None):
self.seconds = seconds
self.start_time = start
if handle:
self.handle = handle
def handle(self, dt):
print("Cron: %s" % dt)
def do_start(self):
self.spawn(self.serve)
def serve(self):
"""
Serve forever calling the self.handle function
with the run time every self.seconds.
next_time - The actual time the handler should be run.
now - the current time
"""
now = time.time()
next_time = now
if self.start_time and now < self.start_time:
# If we aren't yet at the start time sleep until then.
self.async.sleep(self.start_time - now)
next_time = self.start_time
while True:
# self.handle could take some time to run so we calculate
# the next time to run from the previous run time rather
# than just sleeping for self.seconds.
self.spawn(self.handle, next_time) # Could take some seconds?
now = time.time()
next_time = next_time + self.seconds
self.async.sleep(next_time - now)
デフォルトで 1秒毎に実行時間を出力することになる。
$ ginkgo server.CronService
Starting process with server.CronService...
Cron: 1342061996.95
Cron: 1342061997.95
Cron: 1342061998.95
Cron: 1342061999.95
Cron: 1342062000.95
全部合流するサービスを作る
ginkgo は gevent を使っているので、非同期で処理できる。add_service()メソッドでサブサービスが設定できる。上の、CronServer の do_start()メソッドで、サービスを起動するときに、cron の greenlet を別に起動しますので、他の CronServer インスタンスと同時に使えます。さらに、ウェブサーバー加えると、hello world が返す cron サーバーが作れる。
ちょっとやってみよう。
class CronMonitorService(Service):
def __init__(self):
self.add_service(CronService(5, self.ping))
self.add_service(CronService(7, self.pong))
self.add_service(HelloWorldWebServer())
def ping(self, *args, **kwargs):
print("Ping")
def pong(self, *args, **kwargs):
print("Pong")
これで起動すると、
$ ginkgo server.CronMonitorService
Starting process with server.CronMonitorService...
Ping
Pong
127.0.0.1 - - [2012-07-12 12:05:46] "GET / HTTP/1.1" 200 129 0.000110
Ping
Pong
Ping
複数の cron が動いているウェブリクエストも処理するサーバーが立ち上げる。これは結構面白いことが簡単にできそう。