GAEでPython2.7を使ってthreadsafeを有効にする

なんだか解りづらいからメモ。
 

目的

本番サーバで、マルチスレッドで動かす。
開発サーバでも動くようにする。(残念ながらシングルスレッド)
 

データストアはHigh Replication datastore必須

Python2.7を使う場合、データストアはHigh Replication datastore(HRD)でなければならない。
Master/Slaveで作られたアプリの場合、HRDに移行する必要がある。
 
HRDへの移行はこの辺を参照。
 
Administering Your Datastore (Experimental) - Google App Engine - Google Code
http://code.google.com/intl/ja/appengine/docs/adminconsole/datastoreadmin.html
 

webapp2ライブラリ

webapp2とwebapp2_extrasをアプリのルートディレクトリに置く。
GAE本番サーバでは、google.appengine.ext.webappはwebapp2のエイリアスだけど、開発サーバはwebapp1が入ってる。
 
webapp1の機能しか使わないのであれば入れなくてもいい。
でもwebapp2.Routeとかの便利機能があるから入れておいたら幸せになれるはず。
 
Welcome to webapp2! — webapp2 2.3 documentation
http://webapp-improved.appspot.com/
 

app.yaml

Python2.5の時と書き方が若干違う。
一番解りづらいのはscriptの部分。
 
threadsafeを有効にした場合、Python2.5のように、/\.py$/を指定すると、以下の例外が発生してアプリを起動出来ない。

Invalid object:
threadsafe cannot be enabled with CGI handler: main.py

 
最初、main.pyをmain.appにリネームすればいいのかなーとか思ったけど全然違った。
以下のようにした場合、main.pyファイルの、変数appを使う、という指定になる。

application: myapp
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /favicon\.ico
  static_files: favicon.ico
  upload: favicon\.ico

- url: /.*
  script: main.app

 

main.py

app.yamlで指定したように、main.pyにapp変数を作る。
app変数は、webapp2.WSGIApplicationのインスタンスでなければならない。
このapp変数は、グローバル変数として定義する必要がある。
サーバは、このapp変数を複数スレッドで使いまわす。
main()とかはいらない。
 

example
import webapp2 as webapp

class MainHandler(webapp.RequestHandler):
    def get(self):
        self.response.out.write('Hello world!')

app = webapp.WSGIApplication(
    [
        ('/.*', MainHandler),
    ],
    debug=True)

 

開発サーバで動かない

開発サーバでは、まだPython2.7がサポートされてない。

Warning! The development web server, dev_appserver.py, included in the SDK does not yet support Python 2.7. In order to test your application on Python 2.7 you must deploy it using appcfg.py.

 
開発サーバを起動してアクセスすると、ImportErrorが発生する。

<type 'exceptions.ImportError'>: Could not find module main.app 
      args = ('Could not find module main.app',) 
      message = 'Could not find module main.app'

 

開発サーバで動かす

マルチスレッドで動かなくてもいいから、とりあえず開発サーバで表示だけはしたい。
 
その為に、こうする。
本番サーバでは、main/__init__.pyのapp変数を見に行かせる。
開発サーバでは、main/app.pyを実行させる。
これで両環境「script: main.app」の設定で動く。
 
main.py を main/__init__.py に移動し、mainをパッケージにする。
main/app.py は main/__init__.py のシンボリックリンク

mkdir main
mv main.py main/__init__.py
ln -s __init__.py ln -s __init__.py main/app.py

 

main/__init__.py

main()とその実行部分を追記。

import webapp2 as webapp

class MainHandler(webapp.RequestHandler):
    def get(self):
        self.response.out.write('Hello world!')

app = webapp.WSGIApplication(
    [
        ('/.*', MainHandler),
    ],
    debug=True)

def main():
    from google.appengine.ext.webapp import util
    util.run_wsgi_app(app)

if __name__ == '__main__':
    main()

 

tree

ディレクトリ構成はこんな感じ。

.
├── app.yaml
├── index.yaml
├── main
│   ├── __init__.py
│   └── app.py -> __init__.py
├── webapp2.py
└── webapp2_extras
    ├── __init__.py
    ├── appengine
    │   ├── __init__.py
    │   ├── auth
    │   │   ├── __init__.py
    │   │   └── models.py
    │   ├── sessions_memcache.py
    │   ├── sessions_ndb.py
    │   └── users.py
    ├── auth.py
    ├── config.py
    ├── i18n.py
    ├── jinja2.py
    ├── json.py
    ├── local.py
    ├── local_app.py
    ├── mako.py
    ├── protorpc.py
    ├── routes.py
    ├── securecookie.py
    ├── security.py
    ├── sessions.py
    ├── sessions_memcache.py
    ├── sessions_ndb.py
    └── users.py