Chapter 4: コア
コア
コマンドライン オプション
GUIを表示せずにweb2pyを開始するには、コマンドラインから次のように入力します:
python web2py.py -a 'your password' -i 127.0.0.1 -p 8000
web2pyが起動すると、パスワードのハッシュ値の格納用に"parameters_8000.py"ファイルが作成されます。"<ask>"をパスワードの代わりに入力すると、パスワードを入力するプロンプトが表示されます。
セキュリティ向上のため、次のようにweb2pyを起動することができます:
python web2py.py -a '<recycle>' -i 127.0.0.1 -p 8000
この場合、web2pyは以前に格納したパスワードのハッシュ値を再利用します。パスワードが提供されていない場合、つまり、"parameters_8000.py"ファイルが削除されていた場合は、Webベースの管理インターフェイスは無効になります。
幾つかの Unix/Linux システム上では、パスワードが次の場合、
<pam_user:some_user>
web2pyは、オペレーティングシステムのアカウントsome_user
のPAMパスワードを、PAMの設定でブロックしていない限り、管理者認証に使用します。
web2pyは通常CPython(Guido van Rossumによって作成されたC言語で実装されたPython)で動作します。しかし、PyPyとJythonでも恐らく動作します。後者によって、J2EE上でweb2pyを動かせることができるかもしれません。Jythonを使うには、単に"python web2py.py ..."を"jython web2py.py"と置き換えるだけです。Jythonのインストールの詳細、及びデータベースのアクセスに必要なzxJDBCモジュールについては、第14章で説明します。
"web2py.py" スクリプトは、多くのコマンドライン引数を取ることができます。その中には、スレッドの最大数やSSLの有効化などがあります。以下は、完全なリストです:
>>> python web2py.py -h
Usage: python web2py.py
web2py Web Framework startup script. ATTENTION: unless a password
is specified (-a 'passwd'), web2py will attempt to run a GUI.
In this case command line options are ignored.
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-i IP, --ip=IP IP address of the server (e.g., 127.0.0.1 or ::1);
Note: This value is ignored when using the
'interfaces' option.
-p PORT, --port=PORT port of server (8000)
-a PASSWORD, --password=PASSWORD
password to be used for administration (use -a
"<recycle>" to reuse the last password))
-c SSL_CERTIFICATE, --ssl_certificate=SSL_CERTIFICATE
file that contains ssl certificate
-k SSL_PRIVATE_KEY, --ssl_private_key=SSL_PRIVATE_KEY
file that contains ssl private key
--ca-cert=SSL_CA_CERTIFICATE
Use this file containing the CA certificate to
validate X509 certificates from clients
-d PID_FILENAME, --pid_filename=PID_FILENAME
file to store the pid of the server
-l LOG_FILENAME, --log_filename=LOG_FILENAME
file to log connections
-n NUMTHREADS, --numthreads=NUMTHREADS
number of threads (deprecated)
--minthreads=MINTHREADS
minimum number of server threads
--maxthreads=MAXTHREADS
maximum number of server threads
-s SERVER_NAME, --server_name=SERVER_NAME
server name for the web server
-q REQUEST_QUEUE_SIZE, --request_queue_size=REQUEST_QUEUE_SIZE
max number of queued requests when server unavailable
-o TIMEOUT, --timeout=TIMEOUT
timeout for individual request (10 seconds)
-z SHUTDOWN_TIMEOUT, --shutdown_timeout=SHUTDOWN_TIMEOUT
timeout on shutdown of server (5 seconds)
--socket-timeout=SOCKET_TIMEOUT
timeout for socket (5 second)
-f FOLDER, --folder=FOLDER
folder from which to run web2py
-v, --verbose increase --test verbosity
-Q, --quiet disable all output
-D DEBUGLEVEL, --debug=DEBUGLEVEL
set debug output level (0-100, 0 means all, 100 means
none; default is 30)
-S APPNAME, --shell=APPNAME
run web2py in interactive shell or IPython (if
installed) with specified appname (if app does not
exist it will be created). APPNAME like a/c/f (c,f
optional)
-B, --bpython run web2py in interactive shell or bpython (if
installed) with specified appname (if app does not
exist it will be created). Use combined with --shell
-P, --plain only use plain python shell; should be used with
--shell option
-M, --import_models auto import model files; default is False; should be
used with --shell option
-R PYTHON_FILE, --run=PYTHON_FILE
run PYTHON_FILE in web2py environment; should be used
with --shell option
-K SCHEDULER, --scheduler=SCHEDULER
run scheduled tasks for the specified apps: expects a
list of app names as -K app1,app2,app3 or a list of
app:groups as -K app1:group1:group2,app2:group1 to
override specific group_names. (only strings, no
spaces allowed. Requires a scheduler defined in the
models
-X, --with-scheduler run schedulers alongside webserver
-T TEST_PATH, --test=TEST_PATH
run doctests in web2py environment; TEST_PATH like
a/c/f (c,f optional)
-W WINSERVICE, --winservice=WINSERVICE
-W install|start|stop as Windows service
-C, --cron trigger a cron run manually; usually invoked from a
system crontab
--softcron triggers the use of softcron
-Y, --run-cron start the background cron process
-J, --cronjob identify cron-initiated command
-L CONFIG, --config=CONFIG
config file
-F PROFILER_FILENAME, --profiler=PROFILER_FILENAME
profiler filename
-t, --taskbar use web2py gui and run in taskbar (system tray)
--nogui text-only, no GUI
-A ARGS, --args=ARGS should be followed by a list of arguments to be passed
to script, to be used with -S, -A must be the last
option
--no-banner Do not print header banner
--interfaces=INTERFACES
listen on multiple addresses: "ip1:port1:key1:cert1:ca
_cert1;ip2:port2:key2:cert2:ca_cert2;..."
(:key:cert:ca_cert optional; no spaces; IPv6 addresses
must be in square [] brackets)
--run_system_tests runs web2py tests
小文字のオプションは、Webサーバを設定するために使用されます。-L
オプションは、設定オプションをファイルから読み込むようにします。-W
は、web2pyをWindowsサービスとしてインストールします。-S
、-P
、-M
オプションは、インタラクティブなPythonのシェルを開始します。-T
オプションは、web2pyの実行環境にあるコントローラのdoctestを探し、実行します。次の例では、"welcome"アプリケーションの全てのコントローラのdoctestを実行します:
python web2py.py -vT welcome
web2pyをWindowsサービスとして実行している場合(-W
オプション)、コマンドライン引数を使用し設定情報を渡すのは簡単ではありません。このためweb2pyフォルダに、内部Webサーバの設定用として、"options_std.py"がサンプルで用意されています:
import socket
import os
ip = '0.0.0.0'
port = 80
interfaces = [('0.0.0.0', 80)]
#,('0.0.0.0',443,'ssl_private_key.pem','ssl_certificate.pem')]
password = '<recycle>' # ## <recycle> means use the previous password
pid_filename = 'httpserver.pid'
log_filename = 'httpserver.log'
profiler_filename = None
ssl_certificate = None # 'ssl_certificate.pem' # ## path to certificate file
ssl_private_key = None # 'ssl_private_key.pem' # ## path to private key file
#numthreads = 50 # ## deprecated; remove
minthreads = None
maxthreads = None
server_name = socket.gethostname()
request_queue_size = 5
timeout = 30
shutdown_timeout = 5
folder = os.getcwd()
extcron = None
nocron = None
このファイルには、web2pyのデフォルト値が含まれています。このファイルを編集した場合、-L
コマンドラインオプションを用いて明示的にインポートする必要があります。なおこれは、web2pyをWindowsサービスとして動作させた場合のみ機能します。
ワークフロー
web2pyのワークフローは以下の通りです:
- HTTPリクエストがwebサーバ(組み込みのRocketサーバ、WSGIを経由してweb2pyに接続する異なるサーバ、もしくは別のアダプタ)に到達します。webサーバは各リクエストを、各自のスレッドで並列に処理します。
- HTTPリクエストヘッダーが解析され、ディスパッチャー(この章で後ほど説明)に渡されます。
- ディスパッチャーは、どのインストールアプリケーションがリクエストを処理するかを決め、URLのPATH_INFOを関数呼び出しにマッピングします。各URLは、1つの関数呼び出しに対応します。
- staticフォルダのファイルに対するリクエストは、直接処理されます。また大きいファイルは自動的に、ストリーム処理されクライアントに送られます。
- staticファイル向けを除くどのリクエストも、1つのアクションにマッピングされます(つまり、リクエストされたアプリケーションのコントローラファイルの1つの関数)。
- アクションが呼び出される前に、次のように幾つかのことが起こります。リクエストヘッダーがアプリのセッションクッキーを含んでいる場合、セッションオブジェクトが取り出されます。そうでない場合は、セッションidが作成されます(ただし、セッションファイルは後になるまで保存されません)。そしてリクエストに対する実行環境が作成されます。この実行環境でモデルが実行されます。
- 最後に、コントローラのアクションが事前に構築された環境で実行されます。
- アクションが文字列を返す場合、その文字列がクライアントに返されます(もしくは、アクションがweb2pyのHTMLヘルパーオブジェクトの場合、それがシリアライズ化されてクライアントに返されます)。
- アクションがイテレート可能オブジェクを返す場合、クライアントへデータをループしストリームするために用いられます。
- アクションが辞書を返す場合、web2pyはその辞書をレンダリングするためのビューを特定しようと試みます。ビューはアクションと同じ名前を持つ必要があります(特に指定がない限り)。また、リクエストページと同じ拡張子を持つ必要があります(デフォルトは.htmlです)。特定に失敗した場合、web2pyは汎用ビュー(利用可能で有効な場合)を選択します。ビューはアクションによって返される辞書と同じように、モデルで定義した全ての変数を参照しますが、コントローラで定義したグローバル変数は参照しません。
- 全てのユーザーコードは、特に指定がない限り、単一のデータベース・トランザクションで実行されます。
- ユーザーコードが成功すると、トランザクションがコミットされます。
- ユーザーコードが失敗すると、トレースバックがチケットに格納され、クライアントにチケットIDが発行されます。システム管理者だけが、チケットのトレースバックを検索し読むことができます。
念頭に置くべき、幾つかの注意事項があります:
- 同じフォルダ/サブフォルダ内のモデルは、アルファベット順に実行されます。
- Models in the same folder/subfolder are executed in alphabetical order.
- モデルで定義した全ての変数は、アルファベット順の後続する他のモデルと、コントローラ、ビューで参照できます。
- サブフォルダのモデルは、条件付で実行されます。例えば、"a"はアプリケーション、"c"はコントローラ、"f"は関数(アクション)で、ユーザーが"/a/c/f"をリクエストする場合、次のモデルが実行されます。
applications/a/models/*.py
applications/a/models/c/*.py
applications/a/models/c/f/*.py
- リクエストされたコントローラが実行され、リクエストされた関数が呼び出されます。これは、コントローラの全てのトップレベルのコードは、そのコントローラに対するどのリクエストでも、実行されることを意味しています。
- ビューは、アクションが辞書を返す場合にのみ呼び出されます。
- ビューが見つからない場合、web2pyは汎用ビューを使用しようと試みます。デフォルトでは、汎用ビューは無効になっています。ただし'welcome'アプリでは、ローカルホストにおいてのみ、有効なるコードを/models/db.pyに含んでいます。それらは、拡張子のタイプ毎及び、アクション毎に(
response.generic_patterns
を用いて)有効にすることができます。一般に、汎用ビューは開発ツールです。そして普通は、本番環境では使用すべきではありません。汎用ビューを使用したい、幾つかのアクションがある場合、response.generic_patterns
にそれらのアクションをリストしてください(サービスに関する章で詳細に説明します)。
アクションの実行可能なふるまいは、以下の通りです:
文字列を返す
def index(): return 'data'
辞書をビューに対して返す
def index(): return dict(key='value')
全てローカル変数を返す
def index(): return locals()
ユーザーを他のページへリダイレクトさせる
def index(): redirect(URL('other_action'))
"200 OK"以外のHTTPページを返す
def index(): raise HTTP(404)
ヘルパー(FORMなど)を返す
def index(): return FORM(INPUT(_name='test'))
(これはAjaxのコールバックやコンポーネントで、主に使用されます。12章を参照してください)
アクションが辞書を返す時、データベーステーブルを元にしたフォームやファクトリからのフォームなど、ヘルパーによって生成されたコードが含まれるかもしれません。例えば:
def index(): return dict(form=SQLFORM.factory(Field('name')).process())
(web2pyによって生成されたすべてのフォームは、ポストバックを使用します。3章を参照してください)
ディスパッチ
web2pyは次のような形式のURLをマッピングします:
http://127.0.0.1:8000/a/c/f.html
"a"アプリケーションの、"c.py"コントローラの、f()
関数に、マッピングします。もしf
が存在しない場合、コントローラのindex
関数がデフォルトとして使用されます。c
が存在しない場合、"default.py"がデフォルトのコントローラとして使用されます。aが存在しない場合、init
がデフォルトのアプリケーションとして使用されます。init
アプリケーションがない場合に、web2pyはwelcome
アプリケーションの実行を試みます。下のイメージは、これを図式化して表したものです:
デフォルトでは、どの新しいリクエストも、新しいセッションを作成します。さらに、セッションクッキーは、セッションを維持するためにクライアントのブラウザに返されます。
拡張子.html
はオプションです。.html
はデフォルトとして仮定されています。拡張子は、コントローラ関数f()
の出力をレンダリングする、ビューの拡張子を決定します。拡張子によって、同じコンテンツを複数の形式(HTML、XML、JSON、RSSフィードなど)で提供することができます。
引数を取る関数や、2つのアンダースコアで始まる関数は一般には公開されず、他の関数からしか呼び出すことができません。
URLの形式では、次のような例外があります:
http://127.0.0.1:8000/a/static/filename
ここでは、"static"というコントローラは存在しません。web2pyは、"a"アプリケーションの"static"サブフォルダにある、"filename"ファイルへのリクエストと解釈します。
web2pyは、IF_MODIFIED_SINCEプロトコルもサポートしています。ファイルがすでにブラウザに格納されていて、そのファイルが現在のバージョンから変更されていない場合、ファイルは送信されません。
staticフォルダのオーディオやビデオファイルにリンクする時、メディアプレーヤー使ってオーディオやビデオをストリーミングする代わりに、ブラウザにファイルダウンロードを強制させたい場合、URLに?attachment
を加えてください。これは、HTTPレスポンスのContent-Disposition
ヘッダーに"attachment"をセットすることを、web2pyに指示します。例えば:
<a href="/app/static/my_audio_file.mp3?attachment">Download</a>
上記のリンクがクリックされると、すぐにオーディオをストリーミングするのではなく、MP3ファイルをダウンロードするよう、ブラウザはユーザにプロンプトを表示します。( 後述で触れるように、ヘッダー名とその値からなるdict
をresponse.headers
に割り当てることにより、直接HTTPレスポンスヘッダーをセットすることもできます。)
http://127.0.0.1:8000/a/c/f.html/x/y/z?p=1&q=2
アプリケーションにa
、コントローラに"c.py"、関数にf
を割り当て、次のようにrequest
変数にURLパラメータを格納します:
request.args = ['x', 'y', 'z']
さらに:
request.vars = {'p':1, 'q':2}
そして:
request.application = 'a'
request.controller = 'c'
request.function = 'f'
上記の例で、request.args[i]
とrequest.args(i)
はいずれも、request.args
のi番目の要素の取得に使用できます。しかし前者は、request.argsにi番目の要素がない場合に、例外が発生します。後者はNoneを返します。
request.url
これは、現在のリクエストの完全なURL(ただしGET変数は含まれない)を格納しています。
request.ajax
これはデフォルトではFalseですが、Ajaxリクエストによってアクションが呼ばれたとweb2pyが判断した場合には、Trueになります。
リクエストがAjaxで、web2pyのコンポーネントによって起動したものであれば、コンポーネントの名前は次によって知ることができます:
request.cid
コンポーネントについては、12章で詳しく説明します。
request.env.request_method
は"GET"がセットされます。POSTの場合、request.env.request_method
は"POST"がセットされます。URLのクエリ変数は、request.vars
のStorage辞書に格納されます。これらの値はrequest.get_vars
(GETリクエストの場合)または、request.post_vars
(POSTリクエストの場合)にも格納されます。web2pyは、WSGIとweb2pyの環境変数をrequest.env
に格納します。例えば:
request.env.path_info = 'a/c/f'
また、HTTPヘッダを環境変数に次のように格納します:
request.env.http_host = '127.0.0.1:8000'
web2pyは全てのURLに対して、ディレクトリトラバーサル攻撃を防ぐためにバリデータを行っています。
URLは、英数字、アンダースコア、スラッシュのみでしか構成されません。ただし、args
は連続しないドットを含むこともあります。空白文字は、バリデーションの前にアンダースコアに置き換えられます。URL構文が無効な場合は、HTTP 400エラーメッセージ[http-w] [http-o] を返します。
URLが静的ファイルへのリクエストに一致する場合、web2pyは要求されたファイルを単純に読み込んで返します(ストリーミングします)。
URLが静的ファイルへのリクエストでなかった場合、web2pyはリクエストを次の順序で処理します:
- クッキーを解析します。
- 関数が実行される環境を作成します。
request
、response
、cache
を初期化します。- 既存の
session
を開くか、新しいものを作成します。 - リクエストされたアプリケーションに属するモデルを実行します。
- リクエストされたコントローラのアクション関数を実行します。
- 関数が辞書を返す場合、関連付けられたビューを実行します。
- 成功した場合、すべての開いているトランザクションをコミットします。
- セッションを保存します。
- HTTPレスポンスを返します。
コントローラとビューは、同じ環境の異なるコピーで実行されることに注意してください。従って、ビューはコントローラを参照しません。代わりにモデルと、コントローラのアクション関数によって返される変数を参照します。
(HTTP以外の)例外が発生した場合、web2pyは次の処理を行います:
- トレースバックをエラーファイルに格納し、チケット番号をそれに割り当てます。
- すべてのオープンなデータベース・トランザクションをロールバックします。
- チケット番号を報告するエラーページを返します。
例外がHTTP
の例外の場合、意図した動作(例えばHTTP
のリダイレクト)と見なされ、すべてのオープンなデータベース・トランザクションがコミットされます。その後のふるまいは、HTTP
例外自身で指定されています。HTTP
例外クラスは、Python標準の例外ではなく、web2pyで定義されています。
ライブラリ
web2pyのライブラリは、グローバルオブジェクトとしてユーザアプリケーションに公開されます。例えば、request
、response
、session
、cache
、クラス(ヘルパー、バリデータ、DALのAPI)、関数(T
とredirect
)などがあります。
これらのオブジェクトは、次のコアファイルで定義されています:
web2py.py
gluon/__init__.py gluon/highlight.py gluon/restricted.py gluon/streamer.py
gluon/admin.py gluon/html.py gluon/rewrite.py gluon/template.py
gluon/cache.py gluon/http.py gluon/rocket.py gluon/storage.py
gluon/cfs.py gluon/import_all.py gluon/sanitizer.py gluon/tools.py
gluon/compileapp.py gluon/languages.py gluon/serializers.py gluon/utils.py
gluon/contenttype.py gluon/main.py gluon/settings.py gluon/validators.py
gluon/dal.py gluon/myregex.py gluon/shell.py gluon/widget.py
gluon/decoder.py gluon/newcron.py gluon/sql.py gluon/winservice.py
gluon/fileutils.py gluon/portalocker.py gluon/sqlhtml.py gluon/xmlrpc.py
gluon/globals.py gluon/reserved_sql_keywords.py
これらのモジュールの多く、特に
dal
(データベース抽象化レイヤ)、template
(テンプレート言語)、rocket
(Webサーバ)、そしてhtml
(ヘルパー) は依存関係を持たず、web2py の外部で使用出来ることを注意してください。
次の雛形アプリが、tar+gzipで圧縮されてweb2pyに同梱しています。
welcome.w2p
This is created upon installation and overwritten on upgrade.
最初にweb2pyを起動すると、depositとapplicationsという2つの新しいフォルダが作成されます。depositフォルダは、アプリケーションのインストール及びアンインストール時の一時領域として使用します。
web2pyの最初の起動時とアップグレード直後に、"welcome"アプリは"welcome.w2p"ファイルに圧縮され、ひな形アプリとして利用されます。
web2pyがアップグレードされた時点で、"NEWINSTALL"と呼ばれるファイルが付いてきます。web2pyがこのファイルを見つけると、アップグレードが実施されたと判断します。それ故、ファイルを削除し、新しい"welcome.w2p"を作成します。
現在のweb2pyのバージョンは、"VERSION"フィールド(訳注:ファイル?)に格納されます。このビルドIDは、ビルドタイムスタンプによる標準的なセマンティックバージョン表記法に従います。
web2pyのユニットテストは、次の場所にあります。
gluon/tests/
さまざまなWebサーバと接続するための、ハンドラがあります:
cgihandler.py # discouraged
gaehandler.py # for Google App Engine
fcgihandler.py # for FastCGI
wsgihandler.py # for WSGI
isapiwsgihandler.py # for IIS
modpythonhandler.py # deprecated
("fcgihandler"は、Allan Saddiによって開発された"gluon/contrib/gateways/fcgi.py"を呼び出します)、そして、
anyserver.py
これは、多くの異なるウェブサーバのインターフェースになるスクリプトです。13章で説明します。
3つのサンプルファイルがあります:
options_std.py
routes.example.py
router.example.py
1番目は、オプションの設定ファイルで、-L
オプションでweb2py.pyに渡されます。2番目は、URLマッピングファイルのサンプルです。"routes.py"にリネームすると、自動で読み込まれます。3番目は、URLマッピングのための代替構文です。"routes.py"にリネーム(またはコピー)することができます。
次のファイルは
app.example.yaml
queue.example.yaml
Google App Engine上にデプロイするために必要な、サンプルの設定ファイルです。詳しくは、デプロイレシピの章とGoogleのドキュメンテーションのページを参照ください。
サードパーティが幾つか開発した、追加のライブラリもあります:
feedparser[feedparser] は、Mark Pilgrimによって作られました。RSSとAtomのフィードの読み取りに使用します:
gluon/contrib/__init__.py
gluon/contrib/feedparser.py
markdown2[markdown2] は、Trent Mickによって作らました。wikiマークアップに使用します:
gluon/contrib/markdown/__init__.py
gluon/contrib/markdown/markdown2.py
markmin のマークアップです:
gluon/contrib/markmin
fpdf は、Mariano Reingartによって作成された、PDFドキュメント生成ツールです:
gluon/contrib/fpdf
このBookには記載されていませんが、次の場所にドキュメントがあります:
http://code.google.com/p/pyfpdf/
pysimplesoap は、Mariano Reingartによって作成された、軽量なSOAPサーバの実装です:
gluon/contrib/pysimplesoap/
simplejsonrpc は、Mariano Reingartによって作成された、軽量なJSON-RPCクライアントです:
gluon/contrib/simplejsonrpc.py
memcache[memcache] 用のPython APIです。Evan Martinによって作成されました:
gluon/contrib/memcache/__init__.py
gluon/contrib/memcache/memcache.py
redis_cache
gluon/contrib/redis_cache.py
gql は、DALをGoogle App Engine用に移植したものです:
gluon/contrib/gql.py
memdb は、DALをmemcache上に移植したものです:
gluon/contrib/memdb.py
gae_memcache は、Google App Engine上でmemcacheを使うためのAPIです:
gluon/contrib/gae_memcache.py
pyrtf[pyrtf] は、リッチテキストフォーマット(RTF)を生成するためのライブラリです。Simon Cusackによって開発され、Grant Edwardsにより改訂されました:
gluon/contrib/pyrtf/
PyRSS2Gen[pyrss2gen] は、RSSフィードを生成するためのものです。Dalke Scientific Softwareによって開発されました:
gluon/contrib/rss2.py
simplejson[simplejson] は、JSONオブジェクトを解析し、書き込みのための標準ライブラリです。Bob Ippolitoによって作成されました:
gluon/contrib/simplejson/
Google Wallet [googlewallet] 支払い処理のために、Googleにリンクする"pay now"ボタンを提供します:
gluon/contrib/google_wallet.py
Stripe.com [stripe] は、クレジットカード決済を受理するためのシンプルなAPIを提供します:
gluon/contrib/stripe.py
AuthorizeNet [authorizenet] は、Authorize.netネットワークを介して、クレジットカード決済を受理するためのAPIを提供します:
gluon/contrib/AuthorizeNet.py
Dowcommerce [dowcommerce] は、クレジットカード処理APIです:
gluon/contrib/DowCommerce.py
PaymentTech は、クレジットカード処理APIです:
gluon/contrib/paymentech.py
PAM[PAM] は、Chris AtLeeによって作られた認証APIです:
gluon/contrib/pam.py
テスト用のダミーデータをデータベースに投入する、ベイズ分類器です:
gluon/contrib/populate.py
Heroku.comで動作するためのAPIを含むファイルです :
gluon/contrib/heroku.py
web2pyがサービスとして稼働している時、windowsのタスクバーでインタラクティブな操作を行うためのファイルです:
gluon/contrib/taskbar_widget.py
オプションの login_methods とlogin_forms は、認証で使用されます:
gluon/contrib/login_methods/__init__.py
gluon/contrib/login_methods/basic_auth.py
gluon/contrib/login_methods/browserid_account.py
gluon/contrib/login_methods/cas_auth.py
gluon/contrib/login_methods/dropbox_account.py
gluon/contrib/login_methods/email_auth.py
gluon/contrib/login_methods/extended_login_form.py
gluon/contrib/login_methods/gae_google_account.py
gluon/contrib/login_methods/ldap_auth.py
gluon/contrib/login_methods/linkedin_account.py
gluon/contrib/login_methods/loginza.py
gluon/contrib/login_methods/oauth10a_account.py
gluon/contrib/login_methods/oauth20_account.py
gluon/contrib/login_methods/oneall_account.py
gluon/contrib/login_methods/openid_auth.py
gluon/contrib/login_methods/pam_auth.py
gluon/contrib/login_methods/rpx_account.py
gluon/contrib/login_methods/x509_auth.py
web2pyにはまた、以下のような便利なスクリプトが収められているフォルダがあります。
scripts/setup-web2py-fedora.sh
scripts/setup-web2py-ubuntu.sh
scripts/setup-web2py-nginx-uwsgi-ubuntu.sh
scripts/setup-web2py-heroku.sh
scripts/update-web2py.sh
scripts/make_min_web2py.py
...
scripts/sessions2trash.py
scripts/sync_languages.py
scripts/tickets2db.py
scripts/tickets2email.py
...
scripts/extract_mysql_models.py
scripts/extract_pgsql_models.py
...
scripts/access.wsgi
scripts/cpdb.py
setup-web2py-*
は特に便利で、web2pyの本番環境の完全なインストールとセットアップを、スクラッチで行うよう試みます。 これらの幾つかは、14章にて説明します。しかし、その目的と使用方法を説明するドキュメントの文字列が、スクリプトファイル中に書かれています。
最後にweb2pyは、バイナリ・ディストリビューションを作成するために必要なファイルを含んでいます。
Makefile
setup_exe.py
setup_app.py
これらはそれぞれ、 py2exe と py2app ためのセットアップスクリプトです。web2pyのバイナリ・ディストリビューションを作成するためだけに必要です。あなたが、それらを実行する必要は全くありません。
web2pyアプリケーションには、jQuery、calendar、Codemirror といった特にサードパーティのJavaScriptライブラリの追加ファイルが含まれています。それらの作者はファイル自身で確認できます。
アプリケーション
web2pyで開発されたアプリケーションは、以下のパーツから構成されています:
- models は、データベースのテーブルとテーブル間のリレーションのようなデータの表現を記述します。
- controllers は、アプリケーションのロジックとワークフローを記述します。
- views は、JavaScriptとHTMLを使用して、ユーザにデータをどのように表示するかを記述します。
- languages は、アプリケーションの文字列を、サポートされている各種の言語にどのように翻訳するかを記述します。
- static files は、処理を必要としません(例えば画像やCSSスタイルシート等)。
- ABOUT と README は、その名の通りの文書です。
- errors は、アプリケーションで発生したエラーのレポートを格納します。
- sessions は、各ユーザー固有の情報を格納します。
- databases は、SQLiteデータベースと、付加的なテーブル情報を格納します。
- cache は、キャッシュされたアプリケーションの項目を格納します。
- modules は、その他の追加Pythonモジュールです。
- private ファイルは、コントローラからアクセスされますが、開発者から直接アクセスされません。
- uploads ファイルは、モデルからアクセスされますが、開発者から直接アクセスされません(ユーザによりアップロードされたファイルなど)。
- tests は、テスト用のスクリプト、フィクスチャやモックを格納するためのディレクトリです。
models、views、controllers、languages、static filesは、webの管理インターフェイス[デザイン]を介してアクセス可能です。ABOUT、README、errorsも、管理インターフェイスを介して、対応するメニュー項目からアクセスできます。sessions、cache、modules、private filesは、アプリケーションからはアクセスできますが、管理インターフェイスからはアクセスできません。
ユーザがファイルシステムに直接アクセスする必要はないですが、全ては明確なディレクトリ構造にきれいに構造化されています。そしてこれらは、インストールした各web2pyのアプリケーションに複製されています:
__init__.py ABOUT LICENSE models views
controllers modules private tests cron
cache errors upload sessions static
"__init__.py" は空のファイルです。これはPython(そしてweb2pyも)が、modules
ディレクトリ内のモジュールを、インポートするために必要となります。
なお、adminアプリケーションは、サーバ・ファイルシステム上で web2pyアプリケーションのための、シンプルなwebインターフェイスを提供します。web2pyアプリケーションは、コマンドラインもしくはエディタやIDEから、作成と開発を行うこともできます。このため、ブラウザのadminインターフェイスを、使用しなければならないというわけではありません。また新規のアプリケーションは、上記のディレクトリ構造を、例えば、"applications/newapp/"の下に複製し、手動で作成することができます(もしくは、新規アプリケーションのディレクトリ上で、welcome.w2p
を単純にuntarで展開することでも可能です)。adminインターフェイスを使わずに、アプリケーションのファイルをコマンドラインで、作成と編集を行うこともできます。
API
models、controllers、views は、次のオブジェクトがすでにインポートされている環境で実行されます:
グローバルオブジェクト:
request, response, session, cache
国際化:
T
ナビゲーション:
redirect, HTTP
ヘルパー:
XML, URL, BEAUTIFY
A, B, BODY, BR, CENTER, CODE, COL, COLGROUP,
DIV, EM, EMBED, FIELDSET, FORM, H1, H2, H3, H4, H5, H6,
HEAD, HR, HTML, I, IFRAME, IMG, INPUT, LABEL, LEGEND,
LI, LINK, OL, UL, META, OBJECT, OPTION, P, PRE,
SCRIPT, OPTGROUP, SELECT, SPAN, STYLE,
TABLE, TAG, TD, TEXTAREA, TH, THEAD, TBODY, TFOOT,
TITLE, TR, TT, URL, XHTML, xmlescape, embed64
CAT, MARKMIN, MENU, ON
フォームとテーブル
SQLFORM (SQLFORM.factory, SQLFORM.grid, SQLFORM.smartgrid)
バリデータ:
CLEANUP, CRYPT, IS_ALPHANUMERIC, IS_DATE_IN_RANGE, IS_DATE,
IS_DATETIME_IN_RANGE, IS_DATETIME, IS_DECIMAL_IN_RANGE,
IS_EMAIL, IS_EMPTY_OR, IS_EXPR, IS_FLOAT_IN_RANGE, IS_IMAGE,
IS_IN_DB, IS_IN_SET, IS_INT_IN_RANGE, IS_IPV4, IS_LENGTH,
IS_LIST_OF, IS_LOWER, IS_MATCH, IS_EQUAL_TO, IS_NOT_EMPTY,
IS_NOT_IN_DB, IS_NULL_OR, IS_SLUG, IS_STRONG, IS_TIME,
IS_UPLOAD_FILENAME, IS_UPPER, IS_URL
データベース:
DAL, Field
下位互換性のために、SQLDB
=DAL
とSQLField=Field
となっています。新しい構文であるDAL
とField
を、古い構文の代わりに使うことを推奨します。
他のオブジェクトとモジュールは、ライブラリで定義されます。しかし、頻繁に使用されるわけではないので、自動的にはインポートされません。
web2pyの実行環境におけるコアとなるAPIの実体は、後述する、request
、response
、session
、cache
、URL
、HTTP
、redirect
、T
です。
Auth、Crud、Serviceなどの幾つかのオブジェクトや関数は、"gluon/tools.py"で定義されています。そして、それらは必要な時にimportする必要があります:
from gluon.tools import Auth, Crud, Service
PythonモジュールからのAPIアクセス
モデルやコントローラがpythonモジュールをインポートし、これらがweb2pyのAPIの幾つかを使用する必要があるかもしれません。これを実現する方法は、次のようにインポートすることです:
from gluon import *
実際、たとえweb2pyアプリケーションによってインポートされていなくても、web2pyが sys.path
に存在さえすれば、どのPythonモジュールもweb2pyのAPIをインポートすることができます。
しかし、1つの注意点があります。web2pyはHTTPリクエストがある(もしくは偽装された)時のみ存在する、幾つかのグローバルオブジェクト(request, response, session, cache, T)を定義しています。つまり、モジュールがアプリケーションから呼び出された場合にのみ、それらにアクセスできます。このため、それらのオブジェクトは、コンテナの呼び出し元という意味の current
と呼ばれる、スレッドローカル・オブジェクトに配置されます。ここに例を示します。
次のコードを含む、"/myapp/modules/test.py"モジュールを作成します:
from gluon import *
def ip(): return current.request.client
すると、"myapp"のコントローラーから次のことが可能になります:
import test
def index():
return "Your ip is " + test.ip()
幾つかの点に注意してください:
import test
は最初、現在のアプリのモジュールフォルダでモジュールを探します。次に、sys.path
のリストにあるフォルダを探します。従って、アプリレベルのモジュールは、Pythonモジュールよりも優先されます。これにより、異なるアプリで衝突することなく、異なるバージョンのモジュールを使用して出荷できるようになります。- 異なるユーザーが、モジュールの関数を呼ぶアクション
index
を、同時に呼び出すことができます。しかし衝突は起きません。なぜなら、current.request
は、異なるスレッドで異なるオブジェクトだからです。ただし、モジュールの関数やクラスの外で(つまりトップレベルで)、current.request
にアクセスしないように注意してください。 import test
はfrom applications.appname.modules import test
のショートカットです。長い構文を使うと、他のアプリケーションのモジュールをインポートすることが可能になります。
変更が行われた場合、通常のPythonの挙動と一緒で、デフォルトのweb2pyはモジュールをリロードしません。しかし、これは変更できます。モジュールの自動リロードを有効にするには、次のようなtrack_changes
関数を(通常はモデルファイルに、インポートの前で)使用してください:
from gluon.custom_import import track_changes; track_changes(True)
こうすると、モジュールがインポートされる度に、インポーターはPythonのソースファイル(.py)に変更がなかったをチェックします。変更があると、モジュールはリロードされます。
モジュール内で、 track_changes を呼び出すのは禁止です。
Track changes は、アプリケーションに格納されているモジュールの変更だけを追跡します。 current
をインポートしたモジュールは、次のものにアクセスできます:
current.request
current.response
current.session
current.cache
current.T
さらに、アプリケーションでcurrentに格納することを決めた、他のどの変数にもアクセスできます。例えば、モデルを次のようにすると
auth = Auth(db)
from gluon import current
current.auth = auth
インポートした全てのモジュールで、 current.auth
にアクセス可能です。
current
と import
はアプリケーションに対して、拡張性かつ再利用性のあるモジュールを構築するための、強力なメカニズムを作成します。
注意、
from gluon import current
が与えられた時、current.request
及び、他のどのスレッドローカルのオブジェクトを使用することも、正しいです。しかし次のように、モジュールのグローバル変数に割り当てることは、すべきではありません。request = current.request # WRONG! DANGER!
また、クラス属性に割り当てることも、しないでください。
class MyClass: request = current.request # WRONG! DANGER!
これはスレッドローカルのオブジェクトは、実行時に値を取り出す必要があるからです。代わりにグローバル変数は、モデルの最初のインポート時に、一度だけ定義されます。
もう一つの注意点は、キャッシュを使った動作についてです。モジュール内の関数に対しては、予期しない動作のため cache
デコレータは使用できません。モジュールの f
関数に対してキャッシュする場合、lazy_cache
を使用する必要があります:
from gluon.cache import lazy_cache
lazy_cache('key', time_expire=60, cache_model='ram')
def f(a,b,c,): ....
key はユーザ定義されますが、独自に関数に関連付けられている必要があることを、注意してください。省略した場合、web2pyは自動的に key を決定します。
request
request
オブジェクトは、Pythonの dict
クラスを拡張した gluon.storage.Storage
という、至るところに現れるweb2pyのクラスのインスタンスです。基本的には辞書ですが、項目の値は属性としてアクセスすることができます:
request.vars
これは次と同じです:
request['vars']
辞書とは異なり、属性(またはキー)がない場合、例外を発生させずに代わりに None
を返します。
独自のストレージオブジェクトを、作成しておくと便利な時があります。次のように行うことができます:
from gluon.storage import Storage my_storage = Storage() # empty storage object my_other_storage = Storage(dict(a=1, b=2)) # convert dictionary to Storage
request
は以下の項目/属性を持ちます。その内の幾つかはまた、Storage
クラスのインスタンスです:
request.cookies
: HTTPリクエストで渡されたクッキーを含む、Cookie.SimpleCookie()
オブジェクトです。クッキーの辞書のように動作します。各クッキーは、Morselオブジェクト[morsel](訳注:辞書風のオブジェクト、詳細はPythonのライブラリリファレンスを参照のこと)です。request.env
: コントローラに渡される環境変数を含む、Storage
オブジェクトです。HTTPリクエストからのHTTPヘッダ変数と、標準のWSGIパラメータを含みます。環境変数は全て小文字に変換され、記憶しやすいようにドットはアンダースコアに変換されます。request.application
: リクエストされたアプリケーションの名前です。request.controller
: リクエストされたコントローラの名前です。request.function
: リクエストされた関数の名前です。request.extension
: リクエストされたアクションの拡張子です。デフォルトは "html" です。もしコントローラの関数が辞書を返し、さらにビューが指定されなかった場合、辞書をレンダリングするビューファイルの拡張子を決めるために利用されます(request.env.path_info
から解析)。request.folder
: アプリケーションのディレクトリです。例えばアプリケーションが "welcome" の場合、request.folder
には絶対パス"/path/to/welcome"がセットされます。プログラムにて、アクセスが必要なファイルへのパスを作成するには、この変数とos.path.join
関数を常に使用すべきです。web2pyは常に絶対パスを使用していますが、スレッドセーフの方法ではないですので、現在の作業フォルダは(何であれ)決して変更しないということが良いルールです。request.now
: 現在のリクエストの日時を保存したdatetime.datetime
オブジェクトです。request.utcnow
: 現在のリクエストのUTC日時を保存したdatetime.datetime
オブジェクトです。request.args
: コントローラの関数名の後に続く、URLパスの構成要素のリストです。request.env.path_info.split('/')[3:]
と同等です。request.vars
: HTTP GETとHTTP POSTのクエリ変数を両方共含むgluon.storage.Storage
オブジェクトです。request.get_vars
: HTTP GETのクエリ変数のみを含むgluon.storage.Storage
オブジェクトです。request.post_vars
: HTTP POSTのクエリ変数のみを含むgluon.storage.Storage
オブジェクトです。request.client
: クライアントのIPアドレスです。存在する場合、request.env.http_x_forwarded_for
から、そうでない場合は、request.env.remote_addr
から決定します。これは便利ですが、http_x_forwarded_for
は偽装することができるため、信頼すべきものではありません。request.is_local
:True
の場合、クライアントがローカルホストです。False
なら他の状態です。プロキシがhttp_x_forwarded_for
をサポートしていれば、プロキシを通しても機能します。request.is_https
:True
の場合、HTTPSプロトコルを使ったリクエストです。False
ならその他です。request.body
: HTTPリクエストのボディが含まれている、読み取り専用ファイルストリームです。これはrequest.post_vars
を取得するために自動で解析され、解析後はストリームが巻き戻ります。request.body.read()
で読み取ることができます。request.ajax
は呼び出された関数が、Ajaxリクエストを介したものだとTrueになります。request.cid
はAjaxリクエストを生成した、(もしあれば)コンポーネントのid
です。コンポーネントの詳細については、12章を参照してください。request.requires_https()
リクエストが HTTPS ではない場合、コード実行を防ぎ、そして、HTTPSの現在のページにリダイレクトします。request.restful
これは新しく便利なデコレータです。リクエストをGET/POST/PUSH/DELETEに分離することによって、web2pyのアクションのデフォルトの挙動を変更するために使用できます。詳細は10章で説明されています。request.user_agent()
はクライアントの user_agentフィールドを解析し、辞書の形式でその情報を返します。モバイルデバイスを検出するのに便利です。Ross Peoplesによって作成された "gluon/contrib/user_agent_parser.py" を利用しています。何をしているかを確認するには、次のコードをビューに埋め込んでみてください:
{{=BEAUTIFY(request.user_agent())}}
request.global_settings
request.global_settingsには web2py システムの全体の設定が含まれます。これらは自動でセットされますので、変更はしないでください。例えば、request.global_settings.gluon_parent
には、web2pyフォルダのフルパスが含まれます。request.global_settings.is_pypy
は、PyPy上でweb2pyが動作しているかを判断します。request.wsgi
はアクションの内部から、サードパーティのWSGIアプリケーションを呼び出せるようにするフックです。
最後のものは、以下のものも含みます:
request.wsgi.environ
request.wsgi.start_response
request.wsgi.middleware
これらの使用法は、この章の最後に説明します。
例として、典型的なシステム上の次の呼び出しは:
http://127.0.0.1:8000/examples/default/status/x/y/z?p=1&q=2
次のような request
オブジェクトになります:
variable | value |
request.application | examples |
request.controller | default |
request.function | index |
request.extension | html |
request.view | status |
request.folder | applications/examples/ |
request.args | ['x', 'y', 'z'] |
request.vars | <Storage {'p': 1, 'q': 2}> |
request.get_vars | <Storage {'p': 1, 'q': 2}> |
request.post_vars | <Storage {}> |
request.is_local | False |
request.is_https | False |
request.ajax | False |
request.cid | None |
request.wsgi | <hook> |
request.env.content_length | 0 |
request.env.content_type | |
request.env.http_accept | text/xml,text/html; |
request.env.http_accept_encoding | gzip, deflate |
request.env.http_accept_language | en |
request.env.http_cookie | session_id_examples=127.0.0.1.119725 |
request.env.http_host | 127.0.0.1:8000 |
request.env.http_referer | http://web2py.com/ |
request.env.http_user_agent | Mozilla/5.0 |
request.env.path_info | /examples/simple_examples/status |
request.env.query_string | remote_addr:127.0.0.1 |
request.env.request_method | GET |
request.env.script_name | |
request.env.server_name | 127.0.0.1 |
request.env.server_port | 8000 |
request.env.server_protocol | HTTP/1.1 |
request.env.server_software | Rocket 1.2.6 |
request.env.web2py_path | /Users/mdipierro/web2py |
request.env.web2py_version | Version 2.4.1 |
request.env.wsgi_errors | <open file, mode 'w' at > |
request.env.wsgi_input | |
request.env.wsgi_url_scheme | http |
どのような環境変数が実際に定義されているかは、webサーバによって異なります。ここでは、組み込みRocket wsgiのサーバを想定しています。変数のセットはApacheのwebサーバを使用した場合でも、大きな違いはありません。
request.env.http_*
変数は、HTTPリクエストヘッダから解析されたものです。
request.env.web2py_*
変数は、webサーバ環境から解析されたものではありません。web2pyの実行場所やバージョン、あるいはGoogle App Engine上で動いているかどうか(個別の最適化が必要かもしれないため)ということをアプリケーションが知るために、web2pyによって作成されます。
また、 request.env.wsgi_*
変数もあります。これらはWSGIアダプタに固有です。
response
response
は、もう1つの Storage
インスタンスです。以下のものを格納しています:
response.body
: web2pyが出力ページのbodyを書き込む、StringIO
オブジェクトです。この変数は決して変更しないでください。response.cookies
:request.cookies
と似ていますが、後者はクライアントからサーバに送られるクッキーを格納するのに対し、前者はサーバからクライアントに送られるクッキーを格納します。セッションクッキーは自動的で処理されます。response.download(request, db)
: アップロードされたファイルのダウンロードを可能にする、コントローラ関数の実装に使用するメソッドです。request.download
は、request.args
の最後のarg
を、エンコードされたファイル名(すなわち、アップロード時に生成され、アップロードフィールドに保存されるファイル名)として受け取ります。エンコードされたファイル名から、アップロードフィールド名とテーブル名がオリジナルのファイル名と同様に抽出されます。response.download
は2つのオプション引数を取ります:chunk_size
は、ストリームをチャンクするためのバイトサイズをセットします(デフォルトは64K)。attachments
は、ダウンロードファイルが添付ファイルとして扱われるか、そうでないかを決めます(デフォルトはTrue
)。なお、response.download
は、db
のアップロードフィールドに連携しているファイルをダウンロードするのに特化しています。他のタイプのファイルをダウンロードおよびストリーミングをするには、response.stream
(後述)を用いてください。また、/staticフォルダにアップロードされたファイルにアクセスするのに、response.download
を必ずしも使う必要はありません。静的ファイルは、URL(例えば、/app/static/files/myfile.pdf)を介して直接アクセスすることができます(一般にそうすべきです)。response.files
: ページに必要な.css
、.js
、.coffee
、.less
ファイルのリストです。これらは、インクルードした"web2py_ajax.html"を介して、標準の"layout.html"のヘッダーに自動でリンクされます。新しいCSSやJS、COFFEE、LESSファイルを含めるには、このリストに追加するだけ十分です。重複を正しく処理します。順序は重要です。response.include_files()
は、全てのresponse.files
をインクルードする、htmlのheadタグを生成します("views/web2py_ajax.html" で使われています)。response.flash
: ビューに含まれるかもしれないオプションのパラメータです。通常は、何かが発生したことをユーザに通知するために使用します。response.headers
: HTTPレスポンスヘッダのためのdict
(辞書)です。web2pyはデフォルトで、"Content-Length"、"Content-Type"、"X-Powered-By"(web2pyに等しくセットする)などを含む、幾つかのヘッダをセットします。web2pyはまた、クライアント側のキャッシュが有効になっている静的ファイルへのリクエストを除いて、クライアント側のキャッシュを防ぐために、"Cache-Control"、"Expires"および "Pragma"をヘッダにをセットします。web2pyがセットしたヘッダは、上書きもしくは削除ができます。さらに新しいヘッダを追加することも可能です(例、response.headers['Cache-Control'] = 'private'
)。del response.headers['Custom-Header']
と、response.headers の辞書から、ヘッダのキーを削除することができます。しかしながら、レスポンスを返す前に web2pyのデフォルトヘッダは再追加されます。この動作を回避するには、ヘッダの値を None にセットします。例えば、デフォルトの Content-Type ヘッダを削除するには、response.headers['Content-Type'] = None
とします。response.menu
: ビューに含まれるかもしれないオプションのパラメータです。通常は、ビューにナビゲーションメニューツリーを渡すために使用します。これは、MENUヘルパーでレンダリングすることができます。response.meta
:response.meta.author
、もしくは.description
、.keywords
のように、オプションの<meta>
情報を含むストレージオブジェクトです。各々のメタ変数は、自動的に適切なMETA
タグが、 "views/layout.html" にデフォルトで含まれる、"views/web2py_ajax.html" のコードによって設定されます。response.include_meta()
は、シリアライズされた全てのresponse.meta
ヘッダを、含んだ文字列を生成します("views/web2py_ajax.html" で使用されます)。response.postprocessing
: これは関数のリストです。デフォルトは空です。これらの関数は、ビューによって出力がレンダリングされる前に、アクション出力でのレスポンスオブジェクトを、フィルタするために使用されます。これは、他のテンプレート言語に対するサポートを実装するために使用可能です。response.render(view, vars)
: コントローラ内のビューを、明示的に呼び出すために使用するメソッドです。view
はオプションのパラメータで、ビューファイルの名前を指定します。vars
は、ビューに渡される名前付きの値の辞書です。response.session_file
: セッションを含むファイルストリームです。response.session_file_name
: セッションが保存されるファイルの名前です。response.session_id
: 現在のセッションのidです。idは自動で決定されます。この変数は決して変更しないでください。response.session_id_name
: このアプリケーションのセッションクッキーの名前です。この変数は決して変更しないでください。response.static_version
: 静的アセット(訳注:静的ファイルなど)管理のためのバージョン番号です。response.status
: レスポンスに渡されるHTTPステータスコードの数字です。デフォルトは200(OK)です。response.stream(file, chunk_size, request=request, attachment=False, filename=None, headers=None)
: コントローラがこれを返す時、web2pyはファイルの内容をストリームし、chunk_size
のブロックごとにクライアントに戻します。request
パラメタは、HTTPヘッダでチャンクの開始に使用するため必要です。file
は、ファイルパスでなければなりません(後方互換のため、オープンファイル・オブジェクトでも可能ですが、推奨はされていません)。前述のように、response.download
は、アップロードフィールドを介して、保存されたファイルを取り出すために使用されるべきです。response.stream
は他のケース、例えば、一時ファイルや、コントローラで作成されたStringIOオブジェクトを返すために使用されます。attachment
が True の場合、Content-Disposition ヘッダに "添付ファイル" がセットされます。そしてもし、filename
も提供された場合、同様に Content-Disposition ヘッダに追加されます(これはattachment
が True の時だけです)。もしresponse.headers
にまだ含まれていない場合、次の response ヘッダに自動的にセットされます: Content-Type、 Content-Length、 Cache-Control、 Pragma, そして Last-Modified (最後の3つは、ブラウザのファイルキャッシュを許可するのにセットされます)。ヘッダの自動設定を上書きするのには、response.stream
を呼ぶ前にresponse.headers
にセットするだけです。response.subtitle
: ビューへの組み込みを行うことができる、オプションパラメータです。ページのサブタイトルを設定します。response.title
: ビューへの組み込みを行うことができる、オプションパラメータです。ページのタイトルを設定し、ヘッダのHTMLタイトルTAGによってレンダリングされます。response.toolbar
: デバッグ用のツールバーをページに埋め込む関数です{{=response.toolbar()}}
。ツールバーは、request、response、sessionの変数や、各クエリのデータベースアクセス時間を表示します。response._vars
: この変数は、アクションではなく、ビューにおいてのみアクセス可能です。アクションがビューに対して返す値が入ります。response._caller
: この関数は、全てのアクション呼び出しをラップします。デフォルトでは固有の関数ですが、特殊なタイプの例外をキャッチする特別なログを取るために、次のように変更することが可能です:response._caller = lambda f: f()
response.optimize_css
: "concat,minify,inline" とセットすると、web2pyに含まれているCSSファイルを、連結・圧縮・インライン化できます。response.optimize_js
: "concat,minify,inline"と設定すると、web2pyに含まれているJavaScriptファイルを連結・圧縮・インライン化できます。response.view
: ビューのテンプレートの名前です。このテンプレートはページを必ずレンダリングします。デフォルトでは次のように設定されています:上記のファイルがない場合は、次のようになります。"%s/%s.%s" % (request.controller, request.function, request.extension)
特定のアクションに関連付けられたビューファイルを変更するには、この変数の値を変更します。"generic.%s" % (request.extension)
response.delimiters
デフォルトは('{{','}}')
です。ビューに埋め込まれた、区切り文字のコードを変更することができます。response.xmlrpc(request, methods)
: コントローラがこれを返す時、この機能は、XML-RPC [xmlrpc] を介してメソッドを公開します。10章で説明しますが、より良いメカニズムが使用可能です。このため、この機能の利用は推奨されていません。response.write(text)
: 出力ページのボディに、テキストを書き込むメソッドです。response.js
は、Javascriptのコードを含めることができます。このコードは12章で説明するように、レスポンスが、web2pyコンポーネントによって受信される場合にのみ実行されます。
response
は gluon.storage.Storage
オブジェクトなので、ビューに渡したい他の属性も格納することができます。技術的な制約はありませんが、全ページによってレンダリングされる全体用のレイアウト("layout.html")上の変数だけを、格納することを推奨します。
いずれにせよ、次のリストの変数を利用することを強くお勧めします:
response.title
response.subtitle
response.flash
response.menu
response.meta.author
response.meta.description
response.meta.keywords
response.meta.*
これにより、web2pyに同梱された標準の "layout.html" ファイルを、同じ変数セットを利用する別のレイアウトファイルに置き換えることが容易になります。
古いバージョンのweb2pyは response.meta.author
の代わりに、 response.author
を使っていました。他のmeta属性も同様です。
session
session
は、もうひとつの Storage
インスタンスです。 session
に格納したものは何でも、例えば次のようにセットした場合:
session.myvariable = "hello"
後で読み出すことができます:
a = session.myvariable
同じユーザの同じセッションの範囲内である限り、コードが実行されます(ユーザがセッションクッキーを削除せず、セッション期限が切れていない条件下です)。session
は Storage
オブジェクトですので、存在しない属性/キーのセットにアクセスしても例外は発生しません。代わりに None
が返されます。
セッションオブジェクトには、3つの重要なメソッドがあります。1つは forget
です:
session.forget(response)
これは、web2pyにセッションを保存しないよう指示します。アクションが頻繁に呼ばれ、さらにユーザの活動を追跡する必要がないコントローラで使用されます。 session.forget()
は、例えセッションオブジェクトが変更されたとしても、セッションファイルに対する書き込みを防ぎます。 session.forget(response)
はさらに、セッションファイルをアンロックしクローズします。セッションは変更されない限り保存されないので、このメソッドを呼び出すことはほとんどありません。しかし、ページが同時に複数のAjaxリクエストを行う場合、Ajax経由で呼び出されたアクションが、 session.forget(response)
を呼び出すことは良いアイデアです(このアクションはセッションを必要としていないと仮定しています)。そうでない場合、個々のAjaxのアクションは処理開始前に、前のアクションが完了するまで(そしてセッションファイルをアンロックするまで)待つ必要があり、ページのローディングが遅くなります。ただし、セッションがデータベースに格納されている場合はロックされません。
もう1つはメソッドは:
session.secure()
です。これはweb2pyに、セッションクッキーをセキュアクッキーにセットするよう指示します。アプリがhttpsで動いている場合は、これをセットすべきです。セッションクッキーをセキュアにすることで、https接続でない場合にサーバは、ブラウザがセッションクッキーをサーバに送信しないように求めます。
もう1つのメソッドは、 connect
です。 デフォルトでは、セッションはファイルシステムに格納され、セッションクッキーは session.id
の格納及び取得のため利用されます。connectメソッドを利用すると、セッションをデータベースもしくは、セッション管理のためにファイルシステムにアクセスする必要を除いたクッキーに格納するように、web2pyに指示することが可能になります。
例えば次のように、 データベースにセッションを格納:
session.connect(request, response, db, masterapp=None)
ここで、db
は、(DALによって返される)オープンしたデータベース接続の名前です。これはweb2pyに、ファイルシステムではなくデータベースに、セッションを保存するよう指示します。 session.connect
は、db=DAL(...)
の後に呼び出す必要があります。しかしセッションを要求する全てのロジックの前、例えば Auth
の設定前に、呼び出すことが必要です。
web2pyは、次のテーブルを作成します:
db.define_table('web2py_session',
Field('locked', 'boolean', default=False),
Field('client_ip'),
Field('created_datetime', 'datetime', default=now),
Field('modified_datetime', 'datetime'),
Field('unique_key'),
Field('session_data', 'text'))
そして、cPickleしたセッションを session_data
フィールドに格納します。
masterapp=None
オプションはデフォルトでは、動作中のアプリケーション、つまり request.application
の名前を持つアプリケーションに対し、既存のセッションを取得するようweb2pyに指示します。
複数のアプリケーションでセッションを共有したい場合は、 masterapp
にマスターとなるアプリケーション名をセットします。
クッキーにセッションを格納 するためには、代わりに次のようにします:
session.connect(request,response,cookie_key='yoursecret',compression_level=None)
ここで cookie_key
は、対称暗号キーになります。 compression_level
は、オプションの zlib
暗号化レベル(注訳:圧縮レベル)のことです。
クッキー内のセッションは、多くの場合スケーラビリティを理由に、サイズに制限を掛けることが推奨されます。巨大なセッションは、クッキを破壊する結果になります。
アプリケーションの状態は、request
、session
、response
のシステム変数をプリントすることで、いつでもチェックすることができます。それを行う一つの方法は、次のような専用アクションを作成することです:
def status():
return dict(request=request, session=session, response=response)
"generic.html" ビューでは、これを {{=response.toolbar()}}
を使って実現しています。
セッションの分割
ファイルシステム上にセッションを格納しており、それらの多くを使用している場合、ファイルシステムのアクセスがボトルネックになる可能性があります。解決策の1つは次のようにすることです:
session.connect(request, response, separate=True)
separate=True
にすると、"sessions/" フォルダではなく、"sessions/" フォルダのサブフォルダにセッションが格納されるようになります。サブフォルダは自動で作成されます。同じプレフィックスのセッションは、同じサブフォルダに格納されます。またこれは、セッションを要求するどのロジックよりも前に、呼び出す必要があることを注意してください。
cache
cache
は、web2pyの実行環境でも使用可能なグローバルオブジェクトです。これは次の2つの属性を持っています:cache.ram
: メインメモリ上のアプリケーションキャッシュです。cache.disk
: ディスク上のアプリケーションキャッシュです。
cache
は呼び出し可能であり、アクションやビューをキャッシュするための、デコレータとして使うことができます。
次の例では、time.ctime()
関数をRAM上でキャッシュしています:
def cache_in_ram():
import time
t = cache.ram('time', lambda: time.ctime(), time_expire=5)
return dict(time=t, link=A('click me', _href=request.url))
lambda: time.ctime()
の出力結果は、RAM上で5秒間キャッシュされます。文字列 'time'
はキャッシュのキーとして使用されます。
次の例は、time.ctime()
関数をディスク上でキャッシュします:
def cache_on_disk():
import time
t = cache.disk('time', lambda: time.ctime(), time_expire=5)
return dict(time=t, link=A('click me', _href=request.url))
lambda: time.ctime()
の出力結果は、(shelveモジュールを用いて)ディスク上で5秒間キャッシュされます。
cache.ram
と cache.disk
の第2引数は、関数もしくは呼び出し可能オブジェクトでなければなりません。関数の出力ではなく、既存のオブジェクトをキャッシュしたい場合は、ラムダ関数からそのオブジェクトを返すようにしてください:
cache.ram('myobject', lambda: myobject, time_expire=60*60*24)
次の例は、time.ctime()
関数をRAMとディスクの両方でキャッシュします:
def cache_in_ram_and_disk():
import time
t = cache.ram('time', lambda: cache.disk('time',
lambda: time.ctime(), time_expire=5),
time_expire=5)
return dict(time=t, link=A('click me', _href=request.url))
lambda: time.ctime()
の出力結果は5秒間、(shelveモジュールを用いて)ディスク上にキャッシュされ、続いてRAM上でもキャッシュされます。web2pyは最初にRAMを検索し、見つからない場合はディスクを検索します。RAMとディスクのいずれにも存在しない場合、lambda: time.ctime()
が実行され、キャッシュが更新されます。この手法はマルチプロセッサ環境で有用です。2つのtimeは同じである必要はありません。
次の例は、コントローラの関数の結果をRAM上にキャッシュします(ビューはキャッシュしません):
@cache(request.env.path_info, time_expire=5, cache_model=cache.ram)
def cache_controller_in_ram():
import time
t = time.ctime()
return dict(time=t, link=A('click me', _href=request.url))
cache_controller_in_ram
によって返される辞書は、RAM上に5秒間キャッシュされます。注意として、データベースの選択結果は最初に、シリアル化されなければキャッシュすることができません。より良い方法は、select
メソッドの cache
引数を使用し、データベースのセレクト結果を直接キャッシュすることです。
次の例は、コントローラの関数の結果をディスクにキャッシュします(ビューはキャッシュしません):
@cache(request.env.path_info, time_expire=5, cache_model=cache.disk)
def cache_controller_on_disk():
import time
t = time.ctime()
return dict(time=t, link=A('click to reload',
_href=request.url))
cache_controller_in_disk
によって返される辞書は、ディスク上に5秒間キャッシュされます。なお、pickle化できないオブジェクトを含む辞書は、キャッシュできないことに注意してください。
ビューをキャッシュすることも可能です。仕組みは、コントローラで文字列を返すようにするために、コントローラ関数でビューをレンダリングします。これは、response.render(d)
を返すことで実行されます。ここでdは、ビューに渡す予定であった辞書です。以下の例では、コントローラ関数の出力(レンダリングされたビューを含む)をRAMでキャッシュしています:
@cache(request.env.path_info, time_expire=5, cache_model=cache.ram)
def cache_controller_and_view():
import time
t = time.ctime()
d = dict(time=t, link=A('click to reload', _href=request.url))
return response.render(d)
response.render(d)
はレンダリングされたビューを文字列で返し、それは5秒間キャッシュされます。これは最良で最速のキャッシュ方法です。
web2py 2.4.4 から実装された、@cache.client を使うことをお勧めします。
なお time_expire
は、リクエストされたオブジェクトが最後にキャッシュに保存された時刻と、現在の時刻を比較するために使用されます。これは将来のリクエストに影響を与えません。またこれは、オブジェクトが保存された時に定めるのではなく、オブジェクトがリクエストされた時に time_expire
を動的にセットすることを可能にします。例えば:
message = cache.ram('message', lambda: 'Hello', time_expire=5)
この時、次のような呼び出しが、上記の呼び出しの10秒後に行われることを想像してください:
message = cache.ram('message', lambda: 'Goodbye', time_expire=20)
time_expire
は第2の呼び出しにおいて20秒間と設定され、なおかつ、メッセージが最初に保存されてから10秒間しか経過していないので、"Hello" という値はキャッシュから取り出され、さらに "Goodbye" には更新されません。最初の呼び出しでの time_expire
の値である5秒は、第2の呼び出しでは何の影響も与えません。
time_expire=0
(もしくは負の値)にセットすると、キャッシュされた項目を強制的にリフレッシュすることができます(最後に保存してからの経過時間は常に > 0 のため)。また time_expire=None
とセットすると、保存してからの経過時間に関係なく、常にキャッシュの値を取り出すようにします(time_expire
が常に None
ならば、キャッシュされた項目は実質的に期限切れになりません)。
次のように、一つもしくは複数のキャッシュ変数をクリアすることができます。
cache.ram.clear(regex='...')
ここで、regex
は、キャッシュから削除したい全てのキーにマッチする正規表現です。また、次のようにして単一の項目をクリアすることができます:
cache.ram(key, None)
ここで、key
はキャッシュした項目のキーです。
また memcache のような、他のキャッシュメカニズムを定義することも可能です。Memcache は gluon.contrib.memcache
を介して利用可能で、14章で詳しく説明されています。
キャッシュは通常ユーザーレベルではなく、アプリレベルで行われることに注意してください。もし必要なら、例えばユーザー固有の内容をキャッシュするには、ユーザーIDを含むキーを選んでください。
cache.client
デフォルトでは、web2pyの返すコンテンツはキャッシュされないのを前提としています。これは不適切なクライアントサイドのページキャッシュの問題点を減らすためです。
例えば、ユーザのフォームもしくはレコードリストを表示する際に、他のユーザが表示されたテーブルに対してレコードを挿入してしまう可能性があるので、ウェブページのキャッシュはされるべきではありません。
代わりに、もし変更されないコンテンツ(もしくは週一度変更される)の wikiページでユーザで表示される場合、ページを格納するには便利です。しかし、ページが変更されることがないことをクライアントに指示する方が、より一層便利です。
これはページと一緒に、幾つかの特定のヘッダを送信すれば果たせます: クライアントのブラウザがコンテンツを受け取る時に、ブラウザのキャッシュに格納すると共に、再びサイトにリクエストしません。これはもちろん、一般向けサイトの 主要な 速度向上になります。
web2py 2.4.4 では、このような状況をスマートに取り扱うことを可能にする、新しい cache.client
デコレータを導入しました。 cache.client
は次のように使用します:
- スマート・キャッシュ・ヘッダを設定します。
- 結果に応じてキャッシュします。
注意: 一つもしくは別の方、もしくは 両方 行います。
@cache(request.env.path_info, time_expire=300, cache_model=cache.ram)
でビューをキャッシュした場合の主な問題は、例えば、 キーとしての request.env.path_info は幾つかの問題に繋がります。
- URL vars を考慮しません。
- /app/default/index?search=foo の結果をキャッシュする: 300秒後、/app/default/index?search=bar は、/app/default/index?search=foo と全く同じものを返します。
- ユーザを考慮しません。
- ユーザはしばしば、あるページにアクセスします。このため、それをキャッシュに選択します。 しかしながらキャッシュした結果、/app/default/index はキーとして、request.env.path_info を使用します。それで他のユーザには、意味のないページが表示されます。
- "Bill" 用にページをキャッシュし、"Bill" はデスクトップからページにアクセスしました。ところが今、彼は携帯からアクセスを試みています: もし標準とは異なる携帯ユーザのためのテンプレートを用意した場合、"Joe" はそのページを見ることができません。
- 言語を考慮しません。
- ページをキャッシュする時、もし幾つかの要素に T() を使っている場合、ページは固定の言語での翻訳で格納されてしまいます。
- メソッドを考慮しません。
- ページをキャッシュする時、もし対象が GET オペレーションの結果なら、それだけキャッシュする必要があります。
- ステータスコードを考慮しません。
- 最初にキャッシュした時、何らかのエラーが発生し、ナイスな 404ページに戻ります。 キャッシュエラーは望まないでしょうが。 ^_^
これら全ての問題を扱うために、ユーザはたくさんの定形コードを書かされますが、その代わりに cache.client
が作成されました。 これはデフォルトでブラウザがキャッシュ結果を利用できるよう、スマート・キャッシュヘッダを使用します : キャッシュモデルを渡した場合、それはまた、自動的にベストのキーを理解します。同一ページの違うバージョンを格納し、状況に応じて取り出すことができます(例、一つは英語ユーザ用、一つはスペインユーザ用)。
スマートデフォルト設定で、幾つかのパラメータを取ります:
- time_expire : いつものです。デフォルトは300秒です。
- cache_model : デフォルトは None です。これは、@cache.client の場合 のみ 、クライアントのブラウザにコンテンツのキャッシュをさせるよう、デフォルトのヘッダを変更するということを意味しています。
- もし例えば、
cache.ram
を渡した場合は、同様に結果を(訳注:RAM)キャッシュに格納します。
- もし例えば、
- prefix : 自動生成キーにプレフィックスを付けたい場合に使用します (後でクリアする場合に有用です。例、
cache.ram.clear(prefix*)
)。 - session : セッションを考慮する場合に使用します。デフォルトは False です。
- vars : vars URL変数を考慮する場合に使用します。デフォルトは True です。
- lang : 言語を考慮する場合に使用します。デフォルトは True です。
- lang : if you want to consider the language, defaults to True
- user_agent : ユーザエージェントを考慮する場合に使用します。デフォルトは False です。
- public : そこにアクセスする全てのユーザに、同じページを表示する場合に使用します。デフォルトは True です。
- valid_statuses : デフォルトは None です。cache.client は、ステータスコードが1か2もしくは3で始まる、GETメソッドでリスエストされたページのみキャッシュします。 ステータスコードのリストを渡すことが可能です。(これらのステータスコードのページをキャッシュさせる時に使用します。例えば、status_codes=[200] は200ステータスコードの結果のページのみキャッシュします。)
- quick : デフォルトは None ですが、特定の機能を設定するために初期設定のリストを渡すことができます:
- Session, Vars, Lang, User_agent, Public 例、
@cache.client(time_expire=300, cache_model=cache.ram, quick='SVP')
は次と同じです@cache.client(time_expire=300, cache_model=cache.ram, session=True, vars=True, public=True)
- Session, Vars, Lang, User_agent, Public 例、
"考慮する"の意味は、例えば vars でページが vars で違うとされる時にキャッシュしたい場合、/app/default/index?search=foo と /app/default/index?search=bar は同じと判断されません。 一部の設定は他を上書します。これは例えば、session=True, public=True
とセットした時に、後で設定が破棄されます。 上手く、これらを使ってください!
URL
URL
関数は、web2pyにおいて最も重要な関数の1つです。これは、アクションと静的ファイルのための内部URLのパスを生成します。
例えば、これは:
URL('f')
次のようにマッピングされます。
/[application]/[controller]/f
ただし、URL
関数の出力は現在のアプリケーションの名前や、呼び出したコントローラ、その他のパラメータに依存します。web2pyは、URLマッピングや、URLマッピングのリバースもサポートしています。URLマッピングによって、外部URLのフォーマットを再定義することができます。URL
関数を全ての内部URLの生成に使用する場合、URLマッングに追加や変更を行うことで、web2pyアプリケーション内のリンク切れを予防します。
URL
関数に追加のパラメータを渡すことができます。すなわち、URLの特別な項目のパス(args)や、URLのクエリ変数(vars)といったものです:
URL('f', args=['x', 'y'], vars=dict(z='t'))
これは、次のようにマッピングされます。
/[application]/[controller]/f/x/y?z=t
args
属性は、web2pyによって自動で解析・デコードされ、最後に request.args
に格納されます。同様に vars
は、解析・デコードされ、request.vars
に格納されます。 args
と vars
は、web2pyがクライアントのブラウザと情報の交換をするための基本的なメカニズムを提供します。
args が1つの要素しか含まない場合、リストにして渡す必要はありません。
URL
関数は、他のコントローラやアプリケーションのURLを生成するためにも使用することができます:
URL('a', 'c', 'f', args=['x', 'y'], vars=dict(z='t'))
これは、次のようにマッピングされます。
/a/c/f/x/y?z=t
アプリケーション、コントローラ、関数を、名前付き引数で指定することも可能です:
URL(a='a', c='c', f='f')
アプリケーションの名前 a がない場合は、現在のアプリを想定します。
URL('c', 'f')
コントローラの名前 c がない場合は、現在のものを想定します。
URL('f')
コントローラ関数の名前を渡す代わりに、関数自身を渡すことも可能です。
URL(f)
上記の理由から、アプリケーションの静的ファイルのURLを生成するために、常に URL
関数を使用する必要があります。静的ファイルは、アプリケーションの static
サブフォルダに保存されています(管理インターフェースを使ってアップロードすることができる場所です)。web2pyは仮想的な、'static' コントローラを提供しています。それによって、static
サブフォルダからファイルが取り出され、content-typeが決められ、クライアントにファイルがストリームされます。次の例では、"image.png" という静的ファイルに対するURLを生成しています:
URL('static', 'image.png')
これは、次のようにマッピングされます。
/[application]/static/image.png
静的ファイルが static
フォルダのサブフォルダに入っている場合、サブフォルダをファイル名の一部として含むことができます。例えば、次のように生成します:
/[application]/static/images/icons/arrow.png
次のように利用してください:
URL('static', 'images/icons/arrow.png')
args
と vars
の引数は、エンコード/エスケープする必要はありません。自動的で行われます。
デフォルトでは、現在のリクエストに対応する拡張子( request.extension
で見つかります)は、request.extension がデフォルトの html でない限り、関数に追加されます。これを書き換えるには、URL(f='name.ext')
のように関数名の一部として拡張子を明示的に含めるか、次のように拡張子の引数を指定します:
URL(..., extension='css')
現在の拡張子を、明示的に抑制するには次のようにします:
URL(..., extension=False)
絶対URL
デフォルトでは、URL
は相対URLを生成します。しかし scheme
や host
引数を指定することで、絶対URLを生成することができます(これは、例えば、emailのメッセージにURLを挿入する時などに便利です)。
URL(..., scheme='http', host='www.mysite.com')
引数を True
にセットすることで簡単に、現在のリクエストのスキーマとホストを自動的に含めることができます。
URL(..., scheme=True, host=True)
URL
関数はサーバのポートを指定する port
引数も、必要な場合は受け取ることができます。
デジタル署名つきURL
URLを生成時に、デジタル署名を利用することも可能です。これによりサーバで検証することのできる、_signature
というGET変数が追加されます。またこれを実施するには、2通りの方法が存在します。
URL関数に次の引数を渡すことができます:
hmac_key
: URLに署名するためのキー(文字列)です。salt
: 署名前のデータをソルト(原文:salt)するための、オプションの文字列です。hash_vars
: 署名に含まれるクエリ文字列(つまりGET変数)からの、オプションの変数名のリストです。True
(デフォルト)をセットすると全ての変数を含むことができ、False
だと変数を全く含まないようになります。
以下は使用例です:
KEY = 'mykey'
def one():
return dict(link=URL('two', vars=dict(a=123), hmac_key=KEY))
def two():
if not URL.verify(request, hmac_key=KEY): raise HTTP(403)
# do something
return locals()
こうすることで two
アクションは、デジタル署名つきのURLからしかアクセスできなくなります。署名つきURLは次のようになります:
'/welcome/default/two?a=123&_signature=4981bc70e13866bb60e52a09073560ae822224e9'
ここでデジタル署名は、URL.verify
関数によって、認証されていることに注目してください。URL.verify
はまた、上述の hmac_key
、salt
、hash_vars
引数を取ります。それらの値は、URL確認用のデジタル署名を作成した時に、URL
関数に渡した値と一致しなければなりません。
2番目の、より洗練されており、しかしながら、より一般的なデジタル署名のURLは、Authと一緒に使用するものです。これには、次の例を使って説明するのがベストです:
@auth.requires_login()
def one():
return dict(link=URL('two', vars=dict(a=123), user_signature=True)
@auth.requires_signature()
def two():
# do something
return locals()
この場合、hmac_key
は自動で生成され、セッション内で共有されます。これにより、two
アクションが one
アクションに、アクセスコントロールを委ねることを可能にします。リンクが生成され署名されているならば有効で、そうでないならば無効です。もしリンクが他のユーザーに盗まれた場合、リンクは無効になります。
デジタル署名つきのAjaxコールバックを、常に利用することは良いプラクティスです。LOAD
関数を利用する場合も、user_signature
引数があり、この目的のために使用することができます:
{{=LOAD('default', 'two', vars=dict(a=123), ajax=True, user_signature=True)}}
HTTP
とredirect
web2pyは HTTP
という、新しい例外を1つだけ定義しています。この例外は、モデル、コントローラ、ビューのどこでも、次のコマンドで発生させることができます:
raise HTTP(400, "my message")
これによりフロー制御は、ユーザのコードからジャンプしてweb2pyに戻り、次のようなHTTPレスポンスを返します:
HTTP/1.1 400 BAD REQUEST
Date: Sat, 05 Jul 2008 19:36:22 GMT
Server: Rocket WSGI Server
Content-Type: text/html
Via: 1.1 127.0.0.1:8000
Connection: close
Transfer-Encoding: chunked
my message
HTTP
の最初の引数は、HTTPステータスコードです。2番目の引数は、レスポンスのボディとして返される文字列です。その他のオプションの名前付き引数は、HTTPレスポンスヘッダを作成するために使用されます。 例えば:
raise HTTP(400, 'my message', test='hello')
これは以下を生成します:
HTTP/1.1 400 BAD REQUEST
Date: Sat, 05 Jul 2008 19:36:22 GMT
Server: Rocket WSGI Server
Content-Type: text/html
Via: 1.1 127.0.0.1:8000
Connection: close
Transfer-Encoding: chunked
test: hello
my message
もしオープンしているデータベーストランザクションをコミットしたくなければ、例外が発生する前にロールバックしてください。
HTTP
以外のどの例外でも、全てのオープンしているデータベーストランザクションをロールバックし、エラーのトレースバックをログに保存し、訪問者にチケットを発行し、標準のエラーページを返すよう、web2pyを動作させます。
これは HTTP
のみが、クロスページのフロー制御に使用できることを意味します。その他の例外は、アプリケーションによって捕捉されなければならず、そうしないとweb2pyによってチケットが発行されます。
次のコマンドは:
redirect('http://www.web2py.com')
単に次のショートカットです:
raise HTTP(303,
'You are being redirected <a href="%s">here</a>' % location,
Location='http://www.web2py.com')
この HTTP
を初期化するメソッドの名前付き引数は、HTTPヘッダのディレクティブに変換されます。この場合は、リダイレクト先になります。redirect
は、リダイレクトのためのHTTPステータスコード(デフォルトは303)を、オプションの2番目の引数にて受け取ります。この数字を307に変更すると一時的なリダイレクトになり、301に変更すると永久的なリダイレクトになります。
ユーザリダイレクトの最も一般的な使い方は、次のように同じアプリ内の他のページにリダイレクトし、(オプションで)パラメータを渡すことです:
redirect(URL('index', args=(1,2,3), vars=dict(a='b')))
12章で、web2pyコンポーネントの説明をします。それらは、web2pyアクションに対する Ajax リクエストを行います。呼び出されたアクションがリダイレクトを実行する場合、リダイレクトに従って Ajaxリクエストしたり、もしくはページ全体を Ajaxリクエストのリダイレクトしたいかもしれません。後者のケースでは、次のようにセットできます:
redirect(...,type='auto')
国際化と T
を用いた複数形
Internationalization, and Pluralization with T
T
オブジェクトは、言語のトランスレータです。これはweb2pyのクラスである、gluon.language.translator
の単一のグローバルインスタンスから構成されています。全ての文字列定数は(文字列定数のみが)、次の例のように T
によってマークされるべきです:
a = T("hello world")
T
によってマークされた文字列は、言語の翻訳が必要なものとしてweb2pyによって特定され、(モデル、コントローラ、ビューの)コードが実行された時に翻訳されます。翻訳する文字列が定数でなく変数の場合は、実行時に(GAEを除く)、後で翻訳するため翻訳ファイルにその文字列が追加されます。
T
オブジェクトは、補間される変数を含む、複数の同等の構文をサポートすることができます:
a = T("hello %s", ('Tim',))
a = T("hello %(name)s", dict(name='Tim'))
a = T("hello %s") % ('Tim',)
a = T("hello %(name)s") % dict(name='Tim')
後者の構文は翻訳がより簡単になるので、推奨されています。 最初の文字列はリクエストされた言語のファイルに従って翻訳され、name
変数が言語とは独立して置換されます。
翻訳された文字列と通常の文字列を、連結することができます:
T("blah ") + name + T(" blah")
次のようなコードも可能で、大抵の場合、望ましいです:
T("blah %(name)s blah", dict(name='Tim'))
もしくは代替構文で
T("blah %(name)s blah") % dict(name='Tim')
両者共、変数名が"%(name)s"の位置で置換される前に、翻訳が行われます。次の代替は使用すべきではありません:
T("blah %(name)s blah" % dict(name='Tim'))
なぜなら、翻訳が置換の後に行われるからです。
言語の決定
リクエストされる言語は、HTTPヘッダにある "Accept-Language" フィールドによって決められます。この選択は、次のように特定のファイルを要求することによって、プログラムで上書きされる可能性があります:
T.force('it-it')
これは、"languages/it-it.py" の言語ファイルを読み込みます。言語ファイルは管理インターフェースを介して、作成及び編集することができます。
文字列単位で言語を強制することも可能です:
T("Hello World", language="it-it")
複数の言語が要求されるケース、例えば "it-it, fr-ft" では、web2pyは "it-it.py" 及び "fr-fr.py" 翻訳ファイルを検索しようとします。要求されたファイルが存在しない場合、"it.py" 及び "fr.py" に戻ろうとします。これらのファイルが存在しない場合、 "default.py" がデフォルトになります。どちらも存在しない場合、「翻訳しない」がデフォルトになります。もっと一般的なルールは、訪問者の好みに最も近く合致する "xx-xy-yy" 形式の言語検索のため、web2pyは "xx-xy-yy.py", "xx-xy.py", "xx.py", "default.py" と順に試みます。
次のようにして、翻訳を完全に無効にすることもできます。
T.force(None)
通常での文字列の変換は、ビューがレンダリングされる時に遅延評価されます。従って、翻訳オブジェクトの force
メソッドは、ビュー内で呼び出してはいけません。
遅延評価は、次のように無効化することが可能です
T.lazy = False
これにより文字列は、現在の容認されている、もしくは強制された言語に基づいて、T
演算子により直ちに翻訳されます。
個々の文字列に対して、遅延評価を無効にすることも可能です:
T("Hello World", lazy=False)
次のような、一般的な問題があります。元のアプリケーションが、英語で書かれていたとします。翻訳ファイル(例えばイタリア語、"it-it.py")があり、かつ、HTTPクライアントが英語(en)とイタリア語(it-it)の順序で、受け入れることを宣言していると想定してください。この場合、次のような望ましくない状況が発生します。つまり、web2pyはデフォルトが英語で書かれていることは知りません。従って、イタリア語の翻訳ファイルしか見つからないので、全てをイタリア語(it-it)に翻訳するようにします。もし"it-it.py"ファイルが見つからなかったら、デフォルト言語の文字列(英語)が使用されたはずです。
この問題には2つの解決方法があります。一つは英語の翻訳ファイルを作成することですが、しかしファイル自体は、冗長で不必要です。より良い方法はweb2pyに、どの言語を使用すべきか、デフォルト言語の文字列を指示することです。これは次のようにして行うことができます:
T.set_current_languages('en', 'en-en')
これは T.current_languages
に、翻訳が必要でない言語のリストを格納し、そして言語ファイルのリロードを強制します。
なお "it" と "it-it" は、web2pyのビューの観点からすると違う言語になります。それらの両方をサポートするためには、常に小文字の名前を持つ、2つの翻訳ファイルが必要となります。他の全ての言語についても同様です。
現在受け入れている言語は、次に格納されています
T.accepted_language
変数の翻訳
T(...) は単に文字列を翻訳するだけでなく、変数に格納された値も翻訳できます:
>>> a="test"
>>> print T(a)
この場合、翻訳されるのは単語 "test" です。しかし、もし翻訳語がファイルに見つからず、ファイルシステムが書き込み可能であれば、言語ファイルにある翻訳対象の単語リストに追加されます。
これは、たくさんのファイルIOの結果であることに注意してください、そして、これを無効にしたい場合があります:
T.is_writable = False
動的な言語ファイルの更新から、Tを防ぎます。
コメントと複数の翻訳
アプリケーションの同一文字列が違った文脈として出現し、そして文脈を異なる翻訳にすることが必要になる場合があります。このために、元の文字列にコメントを追加することができます。コメントはレンダリングされませんが、最も適切な翻訳を決定するためにweb2pyに使用されます。例えば次のようになります:
T("hello world ## first occurrence")
T("hello world ## second occurrence")
テキストに続く##
は、ダブルの ##
も含めてコメントです。
複数形エンジン
web2py バージョン2.0 以降、パワフルな複数形システム(PS)が組み込まれています。これは翻訳用にマークされたテキストが数値変数に依存する場合、数値変数に基いて異なった翻訳にすることができることを意味します。例えば英語で次のようにレンダリングできます:
x book(s)
と、
a book (x==1)
5 books (x==5)
英語は単数形と複数形を持っています。複数形は "-s" もしくは "-es" を追加したり、例外的な形を使用し組み立てられます。web2pyはそれそれの言語用に複数形のルールと、同様にデフォルトの例外ルールを定義する方法を提供します。実際web2pyは既に、多くの言語用に複数形のルールを分かっています。例えば、スロベニア語の単数形と3の複数形を持っています (for x==1, x==3 or x==4 and x>4)。これらのルールは、"gluon/contrib/plural_rules/*.py" ファイルにエンコードされており、新しいファイルを作成することもできます。単語用の明白な複数形は、管理者インターフェースを使用し複数形のファイルを編集することによって作成されます。
デフォルトでは、PSは作動していません。T
関数の symbol
引数によって起動します。例えば:
T("You have %s %%{book}", symbols=10)
現在、単語の "book" の番号10に関しては、PSは起動されています。 Now the PS is activated for the word "book" and for the number 10. 英語での結果は次のようになります: "You have 10 books" 。 "book" が複数形 "books" になったことを注意してください。
PSは3つの部分で構成されています:
T
入力の中で単語をマークするプレースホルダ%%{}
- 単語が使用するルール ("app/languages/plural-*.py")
- 単語の複数形の辞書 ("app/languages/plural-*.py")
シンボルの値は、単一の変数、リスト/タプル変数、もしくは辞書が可能です。
プレースホルダ %%{}
は、3つの部分で構成されています:
%%{[<modifier>]<word>[<parameter>]},
where:
<modifier>::= ! | !! | !!!
<word> ::= any word or phrase in singular in lower case (!)
<parameter> ::= [index] | (key) | (number)
例えば:
%%{word}
は%%{word[0]}
と同じです (修飾子が使用されていない場合)。%%{word[index]}
はシンボルがタプルの時に使用されます。symbols[index] は、単語形式の選択を決定するための番号を渡します。%%{word(key)}
は symbols[key] から、番号パラメータを取得するために使用されます。%%{word(number)}
は直接number
のセットを許可します(例:%%{word(%i)}
)。%%{?word?number}
number==1
の場合 "word" を返し、さもなければnumber
を返します。%%{?number} or %%{??number}
number!=1
の場合number
を返し、さもなければ何も返しません。
T("blabla %s %%{word}", symbols=var)
%%{word}
デフォルトの意味では %%{word[0]}
、 [0]
はシンボルタプルのアイテムのインデックスです。
T("blabla %s %s %%{word[1]}", (var1, var2))
PS はそれぞれ、"word" と "var2" に使用されます。
ひとつのインデックスを持つ、幾つかの %%{}
プレースホルダを使用できます:
T("%%{this} %%{is} %s %%{book}", var)
あるいは
T("%%{this[0]} %%{is[0]} %s %%{book[0]}", var)
これらは次のように生成します:
var output
------------------
1 this is 1 book
2 these are 2 books
3 these are 2 books
同様に、シンボルに辞書を渡すこともできます:
T("blabla %(var1)s %(wordcnt)s %%{word(wordcnt)}",
dict(var1="tututu", wordcnt=20))
その生成は
blabla tututu 20 words
プレースホルダ %%{?word?number}
によって、"1" を望む単語に置き換えることが可能です。 例えば、
T("%%{this} %%{is} %%{?a?%s} %%{book}", var)
生成は:
var output
------------------
1 this is a book
2 these are 2 books
3 these are 3 books
...
%%{...}
の内部で、次の修飾子も使用できます:
!
テキストの先頭文字を大文字に (string.capitalize
と同等)!!
全ての単語の先頭文字を大文字に (string.title
と同等)!!
to capitalize every word (equivalent tostring.title
)!!!
全ての文字を大文字に (string.upper
と同等)
\ は !
と ?
のエスケープに使うことができるのを注意してください。
翻訳、複数形、MARKMIN
翻訳文字列内部で置き換えることにより、パワフルな MARKMIN 構文も使用することができます。
T("hello world")
と
T.M("hello world")
本書で後述する、MARKMINマークアップを入れた文字列です。MARKMIN内部でも複数形システムを使用可能です。
クッキー
web2pyは、Pythonのクッキー・モジュールを、クッキー処理のために使用します。
ブラウザからのクッキーは request.cookies
にあり、サーバから送られるクッキーは response.cookies
にあります。
クッキーは次のようにセットすることができます:
response.cookies['mycookie'] = 'somevalue'
response.cookies['mycookie']['expires'] = 24 * 3600
response.cookies['mycookie']['path'] = '/'
2行目は、ブラウザにクッキーを24時間保持するように伝えます。3行目は、現在のドメインの任意のアプリケーション(URLパス)にクッキーを返送するように、ブラウザに指示します。注意、クッキーのパスを指定しない場合、ブラウザはリクエストされたパスを前提とします。このため、同じURLパスがリクエストされた時のみ、クッキーをサーバに返します。
クッキーは次のように、セキュリティで保護にすることができます:
response.cookies['mycookie']['secure'] = True
こうすることで、ブラウザはクッキーをHTTPではなくHTTPS経由でのみ返送するようになります。
クッキーは次のように、取り出すことができます:
if request.cookies.has_key('mycookie'):
value = request.cookies['mycookie'].value
セッションが無効になってない限り、web2pyの内部では、次のようにクッキーをセットし、セッション処理のために使用します:
response.cookies[response.session_id_name] = response.session_id
response.cookies[response.session_id_name]['path'] = "/"
ただし、単一のアプリケーションが複数のサブドメインを含み、セッションをそれらのサブドメインで共有したい場合(例えば、sub1.yourdomain.com, sub2.yourdomain.comなど)、セッションクッキーのドメインを次のように明示的にセットしてください:
if not request.env.remote_addr in ['127.0.0.1', 'localhost']:
response.cookies[response.session_id_name]['domain'] = ".yourdomain.com"
上記の方法は例えば、サブドメイン間でログイン状態を保持したい場合に便利です。
init アプリケーション
web2pyをデプロイする時、デフォルトアプリケーションをセットしたい場合があります。すなわち、次のようにURLのパスが空の時に、起動するアプリケーションのことです:
http://127.0.0.1:8000
デフォルトで空のパスに出くわしたら、web2pyは init という名のアプリケーションを探します。もしinitアプリケーションがなかったら、welcome と呼ばれるアプリケーションを探します。
routes.pyの default_application
の設定で、デフォルトアプリケーションの名前を、init から別の名前にすることで変更できます:
default_application = "myapp"
注: default_application
はweb2pyのバージョン1.83で最初に登場しました。
デフォルトアプリケーションを設定する方法は4通りあります:
- 作成するデフォルトアプリケーションの名前を "init" とします。
- routes.pyの
default_application
に、作成したアプリケーションの名前をセットします。 - "applications/init" から、作成したアプリケーションのフォルダへのシンボリックリンクを作成します。
- 次のセクションで説明する、URLリライトを使用します。
URLリライト
web2pyでは、コントローラのアクションを呼び出す前に、着信リクエストのURLパスを書き換えることができます(URLマッピング)。逆に、URL
関数によって生成されたURLパスも書き換えることができます(リバースURLマッピング)。これを行う理由の1つは、古い仕様のURLを扱うためです。もう1つはパスを単純化し、短くするためです。
web2pyには、2つの異なるURLリライトシステムを組み込んでいます。多くのユースケースに対して簡単に利用できるパラメタベースのシステムと、より複雑なケースのための柔軟な、パターンベースのシステムです。URLのリライトルールを指定するためには、"web2py"フォルダで、routes.py
という名の新しいファイルを作成してください(routes.py
の内容は、次の2つのセクションで説明する2つのリライトシステムのどちらか選択したほうに依存します)。2つのシステムを混在させることはできません。
routes.pyを編集する場合、リロードする必要があります。これは次の2つの方法で行われます。webサーバをリスタートするか、管理画面のルーティングの再読み込み(Reload routes)ボタンをクリックするかです。routersにバグがある場合、リロードされません。
パラメータベースのシステム
パラメータベース(パラメトリック)のルーターは、幾つかの "予め準備された" のURLリライトメソッドへの、簡単なアクセスを用意します。その機能は次の通りです:
- 外部から見ることのできるURL(これらはURL()関数から作成されたものです)から、デフォルトのアプリケーション、コントローラ、関数の名前を取り除きます。
- ドメイン(やポート)を、アプリケーションやコントローラにマッピングします。
- URL中に、言語セレクタを埋め込みます。
- 着信URLから固定のプレフィックスを取り除き、発信URLに再び付け加えます。
- /robots.txtのようなルートファイルを、アプリケーションの静的ディレクトリにマッピングします。
パラメトリックのルーターは、着信URLへのより柔軟なバリデーションを提供します。
myapp
アプリケーションを作成し、アプリケーション名をユーザーが参照するURLの一部にしないよう、デフォルトにすることを希望しているとします。デフォルトのコントローラはまだ default
で、それもユーザーが参照するURLから同様に取り除きたいとします。この場合、routes.py
に次の記述を入れてください:
routers = dict(
BASE = dict(default_application='myapp'),
)
これだけです。パラメトリックルーターは、次のようなURLに対して何が正しいのか知っており、適切に対応します:
http://domain.com/myapp/default/myapp
もしくは
http://domain.com/myapp/myapp/index
これらは、通常の省略では曖昧さが残るものです。myapp
と myapp2
という2つのアプリケーションがある場合、同様の効果が得られます。さらに、myapp2
のデフォルトのコントローラが安全な時(大抵の場合)は、いつでもURLから取り除かれます。
もう1つの例を示します。URLは次のように、URLベースの言語をサポートするとします:
http://myapp/en/some/path
もしくは(書き換え)
http://en/some/path
これは次のようにします:
routers = dict(
BASE = dict(default_application='myapp'),
myapp = dict(languages=['en', 'it', 'jp'], default_language='en'),
)
この時、着信URLが次のような場合:
http:/domain.com/it/some/path
/myapp/some/path
にルーティングされます。そして、request.uri_languageは 'it' がセットされ、翻訳を強制することができます。言語固有の静的ファイルを持つことも可能です。
http://domain.com/it/static/filename
これは次にマッピングされます:
applications/myapp/static/it/filename
ただし、ファイルが存在する場合に限ります。存在しない場合、次のようなURLは:
http://domain.com/it/static/base.css
今までどおり、次にマッピングされます:
applications/myapp/static/base.css
(なぜなら static/it/base.css
が存在しないからです)
従って必要なら、画像などの言語固有の静的ファイルをもつことができます。ドメインのマッピングも同様にサポートしています:
routers = dict(
BASE = dict(
domains = {
'domain1.com' : 'app1',
'domain2.com' : 'app2',
}
),
)
これは期待通りに動作します。
routers = dict(
BASE = dict(
domains = {
'domain.com:80' : 'app/insecure',
'domain.com:443' : 'app/secure',
}
),
)
これは、http://domain.com
へのアクセスを、insecure
という名のコントローラにマッピングする一方、HTTPS
でのアクセスは、secure
というコントローラにアクセスさせます。また同様に、異なるポートを異なるアプリケーションへマッピングすることも可能です。
詳細な情報は、標準のweb2py配布のベースフォルダにある router.example.py
ファイルを調べてください。
注: パラメータベース システムは、web2pyのバージョン1.92.1で最初に登場しました。
パターンベースのシステム
先に説明したパラメータベースのシステムは、ほとんどの場合で十分なものです。しかし代わりに パターンベース システムを使用する場合は、より複雑なケースに対する幾つかの追加の柔軟性を提供します。パラメータベースのシステムを利用するには、ルーターをルーティングパラメータの辞書として定義する代わりに、タプルの組からなる routes_in
と routes_out
という2つのリスト(もしくはタプル)を定義します。各タプルは2つの要素を保持します。これは、置換されるパターンとそれを置換する文字列です。例えば:
routes_in = (
('/testme', '/examples/default/index'),
)
routes_out = (
('/examples/default/index', '/testme'),
)
これらのルーティングによって、次のURLは:
http://127.0.0.1:8000/testme
次にマッピングされます:
http://127.0.0.1:8000/examples/default/index
訪問者には、ページのURLへの全リンクは /testme
のように見えます。
パターンはPythonの正規表現と同じ構文を持っています。例えば:
('.*.php', '/init/default/index'),
これは ".php" で終わる全てのURLが、indexページにマッピングされます。
ルールの第二項目は、他のページにリダイレクトすることも可能です:
('.*.php', '303->http://example.com/newpage'),
ここで 303 は、リダイレクト・レスポンスの HTTP コードです。
一つのアプリケーションしか公開予定がない場合、時にはアプリケーションのプレフィックスをURLから取り除きたい場合があります。これは次のように、実現できます:
routes_in = (
('/(?P<any>.*)', '/init/\g<any>'),
)
routes_out = (
('/init/(?P<any>.*)', '/\g<any>'),
)
上記の正規表現と混ぜることができる、もう1つの別の構文があります。これは、(?P<name>\w+)
や \g<name>
の代わりに $name
を使用します。例えば:
routes_in = (
('/$c/$f', '/init/$c/$f'),
)
routes_out = (
('/init/$c/$f', '/$c/$f'),
)
これは、"/example" アプリケーションのプレフィックスを、全てのURLで取り除きます。
$name
表記法を使用し、routes_in
から routes_out
へ自動的にマッピングすることが可能になります。ただしこの場合、正規表現を使用することはできません。例えば: Using the $name
notation, you can automatically map routes_in
to routes_out
, provided you don't use any regular expressions. For example:
routes_in = (
('/$c/$f', '/init/$c/$f'),
)
routes_out = [(x, y) for (y, x) in routes_in]
ここで複数のルーティングがある場合、最初にマッチしたURLが実行されます。もしマッチするパターンがない場合、パスはそのままになります。
$anything
を使うと、行の最後までに何か(.*
)をマッチさせることができます。
ここでは、faviconとrobotsリクエストを処理するための最小限の "routes.py" を示します:
routes_in = (
('/favicon.ico', '/examples/static/favicon.ico'),
('/robots.txt', '/examples/static/robots.txt'),
)
routes_out = ()
さらに複雑な例を示します。"myapp" という単一のアプリを、不必要なプレフィックスなしに公開しますが、同時に、admin と appadmin 及びstaticも公開するという例です:
routes_in = (
('/admin/$anything', '/admin/$anything'),
('/static/$anything', '/myapp/static/$anything'),
('/appadmin/$anything', '/myapp/appadmin/$anything'),
('/favicon.ico', '/myapp/static/favicon.ico'),
('/robots.txt', '/myapp/static/robots.txt'),
)
routes_out = [(x, y) for (y, x) in routes_in[:-2]]
ルーティングの一般的な構文は、これまで見てきた簡単な例よりも複雑です。ここでは、より一般的で代表的な例を示します:
routes_in = (
('140.191.\d+.\d+:https?://www.web2py.com:post /(?P<any>.*).php',
'/test/default/index?vars=\g<any>'),
)
これは、http
もしくは https
の POST
リクエスト(小文字の "post" に注意)を、次の正規表現にマッチしたリモートIPから、www.web2py.com
というホストにマッピングします。
'140.191.\d+.\d+'
そして、次の正規表現にマッチしたページを
'/(?P<any>.*).php'
次へリクエストします。
'/test/default/index?vars=\g<any>'
ここで \g<any>
は、マッチした正規表現によって置換されます。
全般的な構文は以下の通りです:
'[remote address]:[protocol]://[host]:[method] [path]'
もしパターンの最初のセクション(全てが [path]
)が欠けている場合、web2pyはデフォルトを提供します:
'.*?:https?://[^:/]+:[a-z]+'
式全体は正規表現としてマッチするので、"." はエスケープされる必要があります。マッチしたどの部分式も、Pythonの正規表現の構文を使用した (?P<...>...)
を使って捉えることができます。リクエストメソッド(一般に GET もしくは POST)は、小文字でなければなりません。マッチするURLは、%xx
エスケープクォート(引用符)を持っています。
これにより、クライアントのIPアドレス、ドメイン、リクエストのタイプ、メソッド、パス、などに基づいて、リクエストを再ルーティングすることが可能になります。また、異なるバーチャルホストを、異なるアプリケーションにマッピングすることも可能です。マッチした部分式はターゲットのURLを構築するために使用することができ、結果的に、GET変数に渡すことができます。
Apacheやlighttpdなどの全ての主要なWebサーバは、URLをリライトする機能を持っています。本番環境では、それらは routes.py
に代わる選択肢になります。いずれにせよ、アプリの内部URLをハードコーディングせず、URL関数で生成することを強く推奨します。これにより、必要であればroutesを変更することで、アプリケーションはよりポータブルなものになります。
アプリケーション固有のURLリライト
パターンベースのシステムを使用する場合、アプリケーションのベースフォルダにある固有のroutes.pyファイルに、アプリケーションの独自のルーティングを設定することができます。これはベースのroutes.pyで、着信URLからアプリケーション名を決定するための、routes_app
を設定することによって有効になります。これが起動すると、アプリケーション固有のroutes.pyは、ベースのroutes.pyの代わりに使用されます。
routes_app
のフォーマットは、置換パターンが単純にアプリケーションの名前になることを除いて、routes_in
と全く同じです。routes_app
を着信URLに適用してもアプリケーション名にならない場合、または、アプリケーション固有のroutes.pyが見つからない場合、ベースのroutes.pyがこれまで通り使用されます。
注: routes_app
はweb2pyのバージョン1.83で最初に登場しました。
デフォルトのアプリケーション、コントローラ、関数
パターンベースのシステムを使用する場合、デフォルトのアプリケーション、コントローラ、関数は、routes.pyで適切な値をセットすることで、init、default、index から違う名前にそれぞれ変更することができます:
default_application = "myapp"
default_controller = "admin"
default_function = "start"
注: これらの項目は、web2pyのバージョン1.83で最初に登場しました。
エラーのルーティング
routes.py
を使用し、サーバにエラーが起こった時、特定のアクションにリクエストを再ルーティングすることも可能です。このようなマッピングを、各アプリケーション、各エラーコード、各アプリケーションのエラーコード、に対してグローバルに指定することができます。以下はその例です:
routes_onerror = [
('init/400', '/init/default/login'),
('init/*', '/init/static/fail.html'),
('*/404', '/init/static/cantfind.html'),
('*/*', '/init/error/index')
]
各タプルに対して、最初の文字列は "[app name]/[error code]" に照合されます。一致した場合、失敗したリクエストは2番目の文字列にあるURLに再ルーティングされます。エラーハンドリングのURLが静的ファイルでない場合、次のようなGET変数がエラーのアクションに渡されます:
code
: HTTPのステータスコードです(例、404や500など)。ticket
: "[app name]/[ticket number]"形式のチケットです(チケットがない場合は"None"になります)。requested_uri
:request.env.request_uri
と等価です。request_url
:request.url
と等価です。
これらの変数はエラーを処理するアクションで、request.vars
を介して利用可能です。そして、エラーのレスポンスを生成するために使用することができます。とりわけ、デフォルトの200(OK)ステータスコードの代わりに、元のHTTPエラーコードを返すのは良いアイデアです。これは response.status = request.vars.code
とすることによって、実現することができます。エラーアクションから管理者に、admin
のチケットへのリンクを含んだメールを送信させることも可能です。
照合できなかったエラーは、デフォルトのエラーページを表示します。デフォルトのエラーページは、カスタマイズすることができます(web2pyのルートフォルダにあるrouter.example.py
とroutes.example.py
を参照してください):
error_message = '<html><body><h1>%s</h1></body></html>'
error_message_ticket = '''<html><body><h1>Internal error</h1>
Ticket issued: <a href="/admin/default/ticket/%(ticket)s"
target="_blank">%(ticket)s</a></body></html>'''
最初の変数は、無効なアプリケーションや関数がリクエストされていた時のエラーメッセージを含みます。2番目の変数は、チケットが発行された時のエラーメッセージが入っています。
routes_onerror
は両方のルーティングメカニズムで機能します。
"routes.py" では、エラーハンドリングを担当するアクションも指定可能です:
error_handler = dict(application='error',
controller='default',
function='index')
error_handler
が指定したアクションは、ユーザのリダイレクト無しで呼び出され、ハンドラアクションがエラーの処理を担当します。エラー処理ページ自身でエラーが発生した場合、web2pyは古いタイプの静的レスポンスに戻ります。
静的アセット管理
web2pyバージョン2.1.0 から、静的アセットの管理ができるようになりました。
アプリケーションが開発中の場合、静的ファイルは頻繁に変更できるため、web2py は静的ファイルをキャッシュヘッダー無しで送信します。これは、全リクエストでブラウザが静的ファイルのリクエストをするため、"強制" の副作用になっています。この結果が、ページロード時の低パフォーマンスです。
"稼働" 中のサイトでは静的ファイルは変更されないので、不必要なダウンロードを防ぐ キャッシュ
ヘッダが付いた、静的ファイルを提供したい場合があります。
キャッシュ
ヘッダは、一度だけそれぞれのファイルを取得するのをブラウザに認め、従って、帯域を節約しロード時間の削減を行います。
まだ問題があります: キャッシュヘッダは何を宣言すべきなのか?。いつファイル期限が切れるのか?。ファイルが最初に保存された時、サーバはそれらのファイルがいつ変更されるか予想できない。
手動でのアプローチでは、静的ファイルの違うバージョン毎にサブフォルダを作成し構成します。例えば、"layout.css" の初期バージョンは、"/myapp/static/css/1.2.3/layout.css" のURLを利用可能にできます。ファイルを変更した場合、"/myapp/static/css/1.2.4/layout.css" のような新しいサブフォルダとリンクを作成します。
この手順は動作します。しかしcssファイルを更新する度に、他のフォルダにファイルを移動させ、layout.htmlのファイルURLを変更し、そしてデプロイする、ことを思えておく必要があり杓子定規的です。
静的アセット管理は、開発者が静的ファイルのグループバージョンを宣言できるようにすることで問題を解決します。バージョン番号変更した場合は、再度リクエストされるだけです。アセットのバージョン番号は前の例のように、URLの一部分として作成されます。前のアプローチとの違いは、バージョン番号はファイルシステム上ではなく、URL上に現れることです。
もしキャッシュヘッダで "/myapp/static/layout.css" を提供したい場合、バージョン番号が含まれた更新URLを入れることが必要なだけです。
/myapp/static/_1.2.3/layout.css
(URLに定義されたバージョン番号は、他のどこにも表示されないことに注意してください)。
"/myapp/static/" で始まるURLに、アンダースコアとピリオドで区切られた3つの数字で構成されたバージョン番号(SemVer に説明があります)が続き、それからファイル名が続くことを注意してください。"_1.2.3/" を作成する必要がないことも、注意してください。
静的ファイルはURL内でバージョンが要求される度に、"遠い未来" のキャッシュヘッダで提供されます。具体的には:
Cache-Control : max-age=315360000
Expires: Thu, 31 Dec 2037 23:59:59 GMT
これは、ブラウザが一度だけファイルを取得し、ブラウザキャッシュに "永遠に" 保存されることを意味しています。
"_1.2.3/filename" がリクエストされる度に、web2py はパスのバージョン部分を削除し、永遠にキャッシュされる遠い未来のヘッダでファイルが提供されます。もしURLのバージョン番号を変更した場合、別ファイルがリクエストされたとブラウザを偽り、ファイルを再度取得します。
"_1.2.3"、 "_0.0.0"、 "_999.888.888" など、アンダースコアで始まりピリオドで区切られた3つの数字が続く、バージョンが使用可能です。
開発では、静的ファイルの静的URLにリンクする response.files.append(...)
を使用できます。この場合 "_1.2.3/" 部分は手動で付け加えるか、レスポンスオブジェクトの新しいパラメータ: response.static_version
を活用することができます。 使用するファイルをただインクルードする方法、例えば、
{{response.files.append(URL('static','layout.css'))}}
さらに、モデルにセット、
response.static_version = '1.2.3'
これは、response.files
に含まれる全てのファイルに対して、"/myapp/static/layout.css" のURLを "/myapp/static/_1.2.3/layout.css" として、全て自動で書き換えます。
作成中にWebサーバ(apatch、nginx など)から、静的ファイルを提供させることがしばしばあります。この場合、"_1.2.3/" の部分を "スキップ" させるような、設定の調整が必要になります。
例えば、Apache の次のコードを:
AliasMatch ^/([^/]+)/static/(.*) /home/www-data/web2py/applications/$1/static/$2
次のように変更します:
AliasMatch ^/([^/]+)/static/(?:/_[\d]+.[\d]+.[\d]+)?(.*) /home/www-data/web2py/applications/$1/static/$2
同様に、Nginx の次のコードを:
location ~* /(\w+)/static/ {
root /home/www-data/web2py/applications/;
expires max;
}
次のように変更します:
location ~* /(\w+)/static(?:/_[\d]+.[\d]+.[\d]+)?/(.*)$ {
alias /home/www-data/web2py/applications/$1/static/$2;
expires max;
}
バックグランドでのタスク実行
web2pyでは、各々のHTTPリクエストは、それぞれのスレッドで扱われます。スレッドはwebサーバによって、効率化のためにリサイクルされ管理されています。セキュリティのために、webサーバは各リクエストにタイムアウトを設けています。これはアクションが、時間のかかるタスクを処理すべきでないこと、新規のスレッドを作成すべきでないこと、プロセスをフォーク(これは可能ですが推奨されません)すべきでないこと、を意味します。
時間のかかるタスクを実行する正しい方法は、バックグランドで行うことです。その方法は一通りではありませんが、しかしここでは、web2pyに組み込まれている3つの機構を説明します。これは、 cron、homemade task queues、scheduler です。
またここでは、cron を、Unixのクーロンシステムではなく、web2pyの1つの機能として言及します。web2pyのクーロンはwindowsでも動作します。
web2pyのクーロンは、スケジューリングされた時刻にバックグラウンドでのタスクを必要とし、さらにそれらのタスクが、2つの呼び出しの間隔時間に比べて、短い時間で処理する場合に有効な方法です。各タスクはそれぞれ独自のプロセスで実行され、複数のタスクは同時に実行されます。しかし、実行するタスクの数を制御する方法はありません。誤ってタスクが自身をオーバーラップすると、データベースロックやメモリの激しい消費を引き起こします。
web2pyのスケジューラは、別のアプローチを取ります。実行するプロセスの数は固定され、それらは異なるメカニズムで動作します。各プロセスはワーカと呼ばれます。各ワーカは、タスクが実行可能な時にピックアップし、スケジュールされた時刻のなるべくすぐ後に実行されます。ただし、必ずしも正確な時刻に実行されるとは限りません。スケジュールされたタスクの数以上に、実行プロセスの数が多くなることはありません。従って、メモリの激しい消費は起こりません。スケジューラーのタスクはモデルで定義でき、データベースに保存されます。web2pyのスケジューラは、分散キューを実装しません。なぜなら、タスクの実行時間に比べ、タスクを分散する時間は無視できると想定しているからです。ワーカはデータベースからタスクをピックアップします。
ホームメードのタスクキューは、幾つかのケースでスケジューラの単純な代替になります。
クーロン
web2pyのクーロンは、アプリケーションが予め設定された時刻に、プラットフォームに依存しない形で、タスクを実行できるように提供されています。
各アプリケーションでの、クーロンの機能は次のようなクーロンタブ・ファイルで定義されます:
app/cron/crontab
これは、[cron] で定義されている構文に従っています(web2py独自の拡張が幾つかあります)。
web2.1.1 以前では、クーロンはデフォルトで有効でした。コマンドラインで
-N
オプションを付けることで無効にできます。2.1.1 以降、デフォルトでは無効です。有効にするには-Y
を付けます。この変更は、ユーザに新しいスケジューラ(クーロンメカニズムより優れています)を使って貰いたいという、希望が動機になっています。またクーロンは、パフォーマンスに強い影響を与える可能性もあります。
このことは、ホストOSに影響されることなく、全てのアプリケーショが個別にクーロンの設定を持つことができ、クーロンの設定がweb2pyから変更できることを意味します。
次の例を見てください:
0-59/1 * * * * root python /path/to/python/script.py
30 3 * * * root *applications/admin/cron/db_vacuum.py
*/30 * * * * root **applications/admin/cron/something.py
@reboot root *mycontroller/myfunction
@hourly root *applications/admin/cron/expire_sessions.py
この例の最後の2行は、web2pyの追加機能を提供するために、標準のクーロン構文を拡張したものを利用しています。
"applications/admin/cron/expire_sessions.py" というファイルは実際に存在し、admin アプリとともに配布されています。これは有効期限の切れたセッションをチェックし、削除します。"applications/admin/cron/crontab" は、1時間毎に実行されます。
もしスクリプト/関数の名前がアスタリスク(*
)で始まり、.py
で終わる場合、それはweb2pyの環境で実行されます。これは、全てのコントローラとモデルを自由に使えることを意味します。2つのアスタリスク(**
)を使用した場合、モデルは実行されません。これはオーバーヘッドが少なく、ロックの可能性の問題を回避する、推奨される呼び出し方法です。
ただし、web2py環境で実行されるスクリプト/関数は、関数の最後に手動で db.commit()
が必要です。そうでない場合、トランザクションが戻ります。
クーロンが動作するシェルモードにおいて、web2pyはチケットや意味のあるトレースバックを生成しません。従ってweb2pyコードが、エラーなしで動作することを、クーロンタスクを設定する前に確認してください。さらに、どのようにモデルを使用しているかに注意してください。これは、タスクの実行は別プロセスで行われますが、データベースのロックを考慮する必要があります。つまり、データベースをブロックするクーロンタスクのために、ページが待機するのを避けるためです。クーロンタスクでデータベースを使わない場合は、**
構文を使用してください。
また、コントローラの関数を呼び出すことができます。この場合、パスを指定する必要はありません。コントローラと関数は、呼び出し側のアプリケーションのものです。さらに、上に列挙した注意事項に特に注意を払ってください。例えば:
*/30 * * * * root *mycontroller/myfunction
クーロンタブの最初のフィールドで、@reboot
と明記すると、所定のタスクはweb2py起動時に一度だけ実行されます。この特徴を利用して、web2py起動時にアプリケーションのデータを、事前にキャッシュ、検証、初期化したりすることができます。なお、クーロンタスクはアプリケーションとは並行して実行されます。従ってもしアプリケーションが、クーロンタスクが終わるまでリクエストを受け取る準備ができない場合、タスクが反映されたかチェックするような実装をするべきです。例えば:
@reboot * * * * root *mycontroller/myfunction
web2pyの起動方法に応じて、web2pyクーロンは4つの動作モードがあります。
- ソフトクーロン: 全ての実行モード下で利用可能です。
- ハードクーロン: 組み込みサーバを使用している場合に利用可能(直接もしくはApacheのmod_proxyを介して)です。
- 外部クーロン: システム自身のクーロンサービスにアクセスできる場合に利用可能です。
- クーロンなし、です。
組み込みのウェブサーバを利用している場合、デフォルトはハードクーロンです。他の全てでは、デフォルトはソフトクーロンです。ソフトクーロンはCGI、FASTCGI、WSGIを使用している場合、デフォルトになります(ただし、ソフトクーロンはweb2pyが提供する標準の wsgihandler.py
では、デフォルトは enabled
にはなりません)。
タスクは、クーロンタブで指定した時刻の後の、最初のweb2pyに対する呼び出し(ページロード)で実行されます。ただし、ユーザーへの遅延を発生させないために、ページの処理が終わった後に実行されます。明らかに、タスク実行に関する正確な時刻は、サイトが受け取るトラフィックに依存するため不確実性があります。また、webサーバがページロードのタイムアウトをセットしている場合、クーロンタスクは中断されるおそれがあります。これらの制限が許容できない場合は、"外部クーロン" を参照してください。ソフトクーロンは妥当で、最後の手段ですが、webサーバが他のクーロン方式を利用できる場合には、ソフトクーロンよりそちらを利用したほうがよいです。
ハードクーロンは(直接またはApacheのmod_proxyを介して)組み込みのwebサーバを使用している場合、デフォルトになります。ハードクーロンは並列スレッドによって実行されるので、ソフトクーロンとは異なり、実行に要する時間や、実行する時間の精度に関して制約はありません。
外部クーロンはどのような場合でもデフォルトではありませんが、しかし、システムクーロン機能へのアクセスを持っている必要があります。これは並列プロセスで動作するため、ソフトクーロンで適用されるような制限はありません。WSGIやFastCGIの下で、クーロンを利用する場合に推奨される方法です。
次のサンプル行は、システムクーロン(通常は /etc/crontab)に追加されるものです:
0-59/1 * * * * web2py cd /var/www/web2py/ && python web2py.py -J -C -D 1 >> /tmp/cron.output 2>&1
外部 クーロン
では、上記のように -J
(もしくは --cronjob
、でも同じ)のどちらかを追加したことを確認してください。これによりweb2pyが、タスクがクーロンによって実行されていることを検知します。ソフトもしくはハード クーロン
の場合、web2pyはこれを内部でセットしています。
ホームメード・タスクキュー
クーロンは一定の時間間隔で実行するタスクには便利ですが、バックグラウンドタスクを実行するための解決策として常に最良というわけではありません。このためweb2pyは、どのpythonスクリプトもコントローラのように実行する機能を提供しています:
python web2py.py -S app -M -R applications/app/private/myscript.py -A a b c
ここで、-S app
は "myscript.py" を "app" として実行することをweb2pyに指示します。-M
はモデルを実行することを指示し、-A a b c
はオプションのコマンドライン引数 sys.args=['a','b','c']
を、"myscript.py" に渡します。
このようなタイプのバックグラウンド・プロセスは、クーロンを介して実行すべきではありません(たぶん、@rebootを除いて)。なぜなら、同時に1つのインスタンスしか実行しないことを、保証する必要があるからです。クーロンでは、あるプロセスがクーロンのイテレーション1で始まり、完了する前にクーロンのイテレーション2が来る可能性があるからです。さらにクーロンは次々にそれを開始しするので、メールサーバのようなものを妨害する可能性があります。
8章において、どのように上記の方法を使ってメール送信するか、例を使って説明します。
スケジューラー (実験的)
web2pyのスケジューラは前のサブセクションで説明した、タスクキューととてもよく似た方法で動作しますが、幾つか違いがあります:
- タスクを作成し、スケジューリング、監視するするための標準的なメカニズムを提供します。
- 単一のバックグラウンド・プロセスではなく、複数のワーカプロセスからなります。
- ワーカノードのジョブは、監視することができます。なぜなら、それらの状態はタスクの状態とともに、データベースに格納されるからです。
- web2py抜きで動作しますが、ここでは説明しません。
スケジューラはクーロンを使用しませんが、@rebootクーロンでワーカのノードを起動することができます。
Linux及びWindowsでのスケジューラのデプロイに関しては、デプロイレシピの章でさらに紹介します。
スケジューラでは、タスクはモデルに(もしくはモデルからインポートされたモジュールに)定義された単なる関数です。例えば:
def task_add(a,b):
return a+b
タスクは、コントローラによって参照されるものと同じ環境下で、呼び出されます。従って、モデルで定義された全てのグローバル変数、例えばデータベース接続(db
)などを参照することができます。タスクはHTTPリクエストと関連付けされていないという点で、コントローラのアクションとは異なります。このため、request.env
がありません。
もしデータベースの挿入/更新がある場合、タスクの最後で
db.commit()
を呼ぶことを忘れないでください。web2pyは成功したアクションの最後に、デフォルトでコミットします。しかしスケジューラタスクは、何もしません。
スケジューラを有効にするにはモデルで、Scheduler クラスをインスタンス化することが必要です。 スケジューラを有効にするお勧めの方法は、アプリのモデルに scheduler.py
という名前のファイルを作成し、そこに関数を定義することです。その後、次のコードをモデルにセットすることができます:
from gluon.scheduler import Scheduler
scheduler = Scheduler(db)
もし(モデルではなく)モジュールにタスクを定義した場合、ワーカを再起動する必要があります。
次で、タスクはスケジュールされます。
scheduler.queue_task(task_add,pvars=dict(a=1,b=2))
パラメータ
Scheduler
クラスの最初の引数は、スケジューラがワーカとやり取りするために使用するデータベースであることが必要です。これはアプリケーションの db
、もしくは、複数のアプリで共有するの専用の db
が可能です。もし SQLite を使用する場合、アプリの応答性の保持のため、別のdbを使用することをお勧めします。 タスクを定義し、Scheduler
がインスタンス化されたなら、後はワーカを起動するだけです。これには幾つかの方法があります:
python web2py.py -K myapp
myapp
アプリのワーカを起動します。もし同じアプリに対して複数のワーカを起動させたい場合、myapp,myapp
と渡すだけで可能です。次のように、group_names
と渡すことも可能です(モデルを上書きします)。
python web2py.py -K myapp:group1:group2,myotherapp:group1
もし scheduler.py
というモデルがある場合、web2pyのデフォルトのウィンドウ(IPアドレスとポートをセットするために使用するもの)から、ワーカの起動/停止が可能です。
最後に素晴らしい追加です: もし埋め込みのwebサーバを使用した場合、一行のコードでwebサーバとスケジューラを起動することができます(これは、web2pyのポップアップ・ウィンドウを使用したくなく、代わりに "スケジューラ" メニューを使用できると仮定した場合です)。
python web2py.py -a yourpass -K myapp -X
- K パラメータ及び付加する -X パラメータで、通常のパラメータ(-i、-p、ここでは -a がウィンドウの起動を抑制します)を、どのようなアプリに対しても渡すことができます。スケジューラはwebサーバと一緒に実行されます!。
Scheduler の完全なシグネチャは次のとおりです:
Scheduler(
db,
tasks=None,
migrate=True,
worker_name=None,
group_names=None,
heartbeat=HEARTBEAT,
max_empty_runs=0,
discard_results=False,
utc_time=False
)
順にそれらを見て行きましょう:
db
はスケジューラのテーブルを格納したいデータベースの DALインスタンスです。tasks
は関数にタスク名をマッピングした辞書です。このパラメータを渡さない場合、関数はアプリ環境内で検索を行います。worker_name
はデフォルトで None です。ワーカが起動するとすぐ、ホスト名-uuid として名前が生成されます。もし指定したい場合、その名前がユニークだということを確認してください。group_names
はデフォルトでは [main] がセットされます。 main をデフォルトでセットした場合、全てのタクスはgroup_name
を持っています。ワーカは自分に割り当てられたグループのタスクのみ、ピックアップすることができます。
注意: これは、違うワーカのインスタンス(例えば、違うマシン上の)があり、特定のワーカにタスクを割り当てたい場合に便利です。
注意2: ワーカを他のグループに割り当てるのも可能です。また、
['mygroup','mygroup']
のように全て同じものをセットすることも可能です。グループ名['mygroup']
が二重でワーカタスク処理ができるよう、タスクはグループ名['mygroup','mygroup']
も勘案し配置します。
heartbeat
はデフォルトで3秒がセットされています。このパラメータはスケジューラが、scheduler_worker
テーブルの状態と、任意の ASSIGNED (割り当て済み)タスク自身の処理が存在するかどうかを確認する、チェックの頻度をコントロールするものです。max_empty_runs
はデフォルトでは0です。これは、ワーカが ASSIGNED (割り当て済み)タスク処理を、直ぐに継続することを意味しています。もしこの値を10にした場合、ワーカが ACTIVE (活動中)で ASSIGNED (割り当て済み)タスクがない状態が10ループ続いた場合、自動的に終了します。ワーカがタスクを検索する場合、ループは3秒毎になります(もしくはheartbeat
の設定値です)。discard_results
はデフォルトではFalseです。もしTrueがセットされると、scheduler_run レコードは作成されません。
注意: scheduler_run レコードは、FAILED (失敗)、TIMEOUT (タイムアウト)、及び STOPPED (停止)タスク状態の前に生成されます。
utc_time
はデフォルトではFalseです。異なるタイムゾーンで実行されているワーカの同期を取る必要がある場合、もしくは 標準/夏時間に問題がない場合、異なる国からdatetimeを供給される、などの理由により、Trueにセットすることもできます。スケジューラはローカルタイムは忘れて、UTC時間を尊重します。警告: UTC時間でタスクをスケジュールする必要があります(開始時刻、終了時刻、など)。
このようにして、所定のインフラを持つことができました: タスクを定義し、スケジューラにそれらを伝え、ワーカを起動しました。残りの作業は、実際にタスクをスケジューリングすることです。
タスク
タスクはプログラムによって、もしくはappadminを介して、スケジューリングすることができます。実際、タスクは単純に "scheduler_task" のテーブルに、一つのエントリーを加えることでスケジューリングされます。これはappadminを介してアクセスできます:
http://127.0.0.1:8000/myapp/appadmin/insert/db/scheduler_task
このテーブルのフィールドの意味は明白です。"args" と "vars" フィールドは、JSONフォーマットでタスクに渡される値です。上記の "task_add" の場合、"args" と "vars" の例は、以下のようになります:
args = [3, 4]
vars = {}
または
args = []
vars = {'a':3, 'b':4}
scheduler_task
テーブルはタスクの準備を行います。
全てのタスクはライフサイクルに従います。
デフォルトでは、スケジューラにタスクを送った時に QUEUED (待機)状態になります。 もし後で実行する必要がある場合、start_time
パラメータ(デフォル値はnow)を使用します。 幾つかの理由で、ある時点の後に実行されるタスクがないことを確認する場合(もしかしたら、AM1時にwebサービスのシャットダウンがリクエストされる、稼働時間終了前に送信が必要なメールがある、など)、stop_time
をセットすることができます(デフォルト値は None)。 stop_time
以前にワーカによって、ピックアップされなかったタスクは、EXPIRED (失効)としてセットされます。 stop_time
がセットされていないか、BEFORE (以前)にピックアップされたタスクは、ワーカに ASSIGNED (割り当て済み)になります。ワーカがタスクをピックアップした時に、状態は RUNNING (稼働中)にセットされます。 RUNNING (稼働中)タスクは次のように終了します:
- TIMEOUT (タイムアウト)
timeout
パラメータ(デフォルト値は60秒)をn
秒以上過ぎた時。 - FAILED (失敗) 例外が発生した時。
- COMPLETED (完了) 正常に完了した時。
start_time
と stop_time
の値は、datetimeオブジェクトでなければなりません。"mytask" を現在時刻から30秒でスケジュールするには、例えば、次のようにします:
from datetime import timedelta as timed
scheduler.queue_task('mytask',
start_time=request.now + timed(seconds=30))
さらに、タスクを何回繰り返すべきか、コントロールすることが可能です(すなわち、指定した間隔で幾つかのデータを集計することが必要である)。そのためには、repeats
パラメータをセットします(デフォルト値は1で1回のみ、0の場合は無制限です)。period
パラメータ(デフォルト値は60秒)で実行間隔が何秒なのか、影響を与えることもできます。
最初のサイクルの終了と、次のサイクルの開始の時間間隔は計算されません。しかし最初のサイクルの開始時刻から、次のサイクルの開始時刻はの間隔は計算されます。
関数が何回例外を発生させることができる設定(すなわち、遅いwebサービスからデータを要求します)や、retry_failed
パラメータ(デフォルト値は0、-1の場合無制限です)を使用し、FAILED (失敗)状態での停止の代わりに再度キューに入れることも、設定が可能です。
要約すると、
period
及びrepeats
は自動的に再スケジューリングする関数を提供します。timeout
は関数が一定の合計時間を超過しないことを保証します。retry_failed
はタスクの"失敗"する回数をコントロールします。start_time
及びstop_time
は制限時間内に関数をスケジュールします。
queue_task
と task_status
このメソッドは:
scheduler.queue_task(function, pargs=[], pvars={}, **kwargs)
ワーカによるキュータスクの実行を許可します。これは次のパラメータを取ります:
function
(必須): これはタスク名か、実際の関数への参照にすることができます。pargs
: タスクに渡す引数で、Pythonリストとして格納されます。pvars
: タスクに渡す名前付き引数で、Python辞書として格納されます。kwargs
: 他の全ての scheduler_task の列をキーワード引数のように渡すことができます(例えば、repeats、period、timeout)。
例えば:
scheduler.queue_task('demo1', [1,2])
これと全く同じで
scheduler.queue_task('demo1', pvars={'a':1, 'b':2})
同様に
st.validate_and_insert(function_name='demo1', args=json.dumps([1,2]))
さらに同様に:
st.validate_and_insert(function_name='demo1', vars=json.dumps({'a':1,'b':2}))
より複雑で完全な例:
def task_add(a,b):
return a+b
scheduler = Scheduler(db, tasks=dict(demo1=task_add))
scheduler.queue_task('demo1', pvars=dict(a=1,b=2),
repeats = 0, period = 180)
バージョン2.4.1以降、もし追加のパラメータ immediate=True
を渡した場合、メインワーカにタスクの再割り当てを強制します。2.4.1までは、ワーカは5サイクル毎に新しいタスクをチェックします(実際は、5*heartbeats
秒です)。もし新しいタスクのチェックが頻繁に必要なアプリがある場合、きびきびした 振る舞いにするために heartbeat
パラメータを下げるのを余儀なくされ、理由なくdbを高いプレッシャ下に置きます。immediate=True
では、新しいタスクのチェックを強制することができます: 最大で heartbeat
秒でチェックします。
scheduler.queue_task
を呼び出すと、キューに入ったタスク(手動もしくは自動生成されたもの)の id
と uuid
を返します。また errors
も可能です:
<Row {'errors': {}, 'id': 1, 'uuid': '08e6433a-cf07-4cea-a4cb-01f16ae5f414'}>
エラーがある場合(通常は構文エラーもしくはバリデーションエラー)、バリデーションされた結果を取得し、id及びuuidはNoneになります。
<Row {'errors': {'period': 'enter an integer greater than or equal to 0'}, 'id': None, 'uuid': None}>
結果と出力
"scheduler_run" テーブルは、全ての実行中のタスクの状態を格納します。個々のレコードは、ワーカによってピックアップされたタスクを参照しています。一つのタスクは、複数の実行を持つことができます。例えば、1時間に10回繰り返すようにスケジュールされたタスクは、恐らく10実行あります(失敗したか、1時間より長い時間かかった場合を除いては)。もしタスクが値を返さない場合、終了後直ぐにscheduler_runテーブルから削除されることに、注意してください。
取ることのできるランの状態は、次の通りです:
RUNNING, COMPLETED, FAILED, TIMEOUT
もしランが完了し、何の例外もなく、タスクのタイムアウトもない場合、ランは COMPLETED
にマークされ、タスクも後で実行されるか否かに応じて、QUEUED
もしくは COMPLETED
にマークされます。タスクの出力はJSONでシリアライズされ、ランのレコードに格納されます。
RUNNING
タスクに例外を発生した時は、ランは FAILED
にマークされ、タスクも FAILED
にマークされます。トレースバックはランのレコードに格納されます。
同様にランがタイムアウトを超える場合、それは停止させられ TIMEOUT
にマークされます。タスクもまた TIMEOUT
にマークされます。
どのような場合でも、stdoutはキャプチャされ、ランのレコードにログが保存されます。
appadminを使用すると、全ての RUNNING
タスク、COMPLETED
タスクの出力、FAILED
タスクのエラーなどを、チェックすることができます。
スケジューラは、さらにもう1つの "scheduler_worker" というテーブルを作成します。これはワーカーのハートビートとステータスを保存します。
プロセス管理
ワーカのきめ細かい管理は困難です。このモジュールは任意のプラットフォーム(Mac、Win、Linux)を置き去りにしないようにしています。
ワーカを起動すると、次のことをしたくなります:
- "何を実行しているかに関係なく" killする
- 応答しないタスクのみ、killする
- スリープ状態にする
たぶん、キューにタスクがまだある時は、幾つかのリソースの節約を望みます。 毎時間、処理中のものに対して希望することが分かります。それで、次のようなことを望むでしょう:
- 全てのキューにあるタスクの実行と、自動的な終了
これらの全ては、Scheduler
パラメータ、もしくは scheduler_worker
テーブルで管理可能です。 より正確には、起動したワーカ用に status
の値を変えることにより、ワーカの振る舞いに影響を与えることができます。 タスクに対して、ワーカは次の状態の何れかになります: ACTIVE、DISABLED、TERMINATE もしくは KILLED
ACTIVE と DISABLED は、TERMINATE もしくは KILL までの間で、"持続的" な状態です。 名前が暗示するのは、実際の状態より "コマンド" です。 ctrl+c を押すことは、ワーカを KILL にセットすることと同等です。
バージョン2.4.1 以降、幾つかの有用な関数があります(自己解釈)。 There are a few commodity functions since version 2.4.1 (self-explanatory)
scheduler.disable()
scheduler.resume()
scheduler.terminate()
scheduler.kill()
それぞれの関数はオプションのパラメータを取り、それらは文字列もしくはリストで、group_names
に基いてワーカを管理します。スケジューラのインスタンスでデフォルトの group_names
が定義されています。
たくさんの言葉よりサンプルの方が優れています: scheduler.terminate('high_prio')
は high_prio
タスクが実行されている全てのワーカを終了します。scheduler.terminate(['high_prio', 'low_prio'])
とすると、high_prio
と low_prio
の全てのワーカを終了します。
危険: もし
high_prio
とlow_prio
を実行中のワーカがある場合、low_prio
を終了したくない場合でも、scheduler.terminate('high_prio')
はワーカを全部まとめて終了します。
appadminを介してできることは全て、これらのテーブルにレコード挿入・更新することにより、プログラムからでも可能です。
いずれにせよ、RUNNING
のタスクのレコードを変更すべきではありません。予期せぬ挙動をする可能性があります。ベストプラクティスは、"queue_task" メソッドを使用しタスクをキューイングすることです。
例えば:
scheduler.queue_task(
function_name='task_add',
pargs=[],
pvars={'a':3,'b':4},
repeats = 10, # run 10 times
period = 3600, # every 1h
timeout = 120, # should take less than 120 seconds
)
なお、"times_run"、 "last_run_time"、"assigned_worker_name" のフィールドは、スケジュールした時点では提供されず、ワーカーによって自動で設定されます。
完了したタスクの出力を、次のようにして取り出すことができます:
completed_runs = db(db.scheduler_run.run_status='COMPLETED').select()
スケジューラは実験検討中です。なぜなら、より徹底的なテストが必要で、さらに機能追加される時にテーブルの構造が変更されるかもしれないからです。
パーセント・リポート
前の出力の全てをクリアする機能のprint文に、特別な "ワード" があります。それが、!clear!
です。 これは sync_output
パラメータと対になって、パーセントを報告することが可能です。
ここでサンプルを示します:
def reporting_percentages():
time.sleep(5)
print '50%'
time.sleep(5)
print '!clear!100%'
return 1
reporting_percentages
関数は、5秒スリープし、50%
を出力します。 次に5秒スリープし、100%
を出力します。scheduler_runテーブルの出力は2秒毎に同期し、2回目のprint文は !clear!100%
を含んでおり、50%
の出力をクリアし 100%
に置き換えているだけということに、注意してください。
scheduler.queue_task(reporting_percentages,
sync_output=2)
サードパーティのモジュール
web2pyはPythonで書かれていますので、サードーパーティのものも含め、任意のPythonモジュールをインポートして使うことができます。これには、モジュールを見つけられるようにすることだけが必要です。他のPythonアプリケーションと同様に、モジュールは公式のPythonの "site-packages" ディレクトリにインストールすることができます。インストールしたモジュールは、コード上の任意の場所からインポートすることができます。
"site-packages" にあるモジュールは、その名が示すように、サイトレベルのパッケージです。モジュールが個別にインストールできないのであれば、site-packagesを必要とするアプリケーションはポータブルではありません。"site-pacakages" にモジュールを持つ利点は、複数のアプリケーションがそれを共有できることです。例えば、"matplotlib" というグラフ描画用のパッケージを考えましょう。これはPEAKの easy_install
コマンド [easy-install] (もしくはより新しい pip
[PIP] ) を使用して、シェルからインストールすることができます:
easy_install py-matplotlib
そうすると、全てのモデル/コントローラ/ビューでインポートすることができます:
import matplotlib
web2pyのソースディストリビューションとWindows版のバイナリディストリビューションは、1つのsite-packagesをトップレベルのフォルダに持っています。Mac版のバイナリディストリビューションは、次のフォルダ内にsite-packagesがあります:
web2py.app/Contents/Resources/site-packages
site-packagesを使用する時の問題は、1つのモジュールで複数のバージョンを同時に使用することが難しいことです。例えば、2つのアプリケーションが同じファイルの異なるバージョンを使用する、といった場合です。この例では、sys.path
はどちらのアプリケーションにも影響するため、変更することができません。
このような状況に対応するため、web2pyはグローバルな sys.path
を変更しないで、モジュールをインポートする別の方法を提供しています。これは、アプリケーションの "modules" フォルダに、モジュールを置くことです。利点の1つは、アプリケーションと共に、モジュールが自動的にコピーされ配布されることです。
"mymodule.py" モジュールがアプリの "modules/" フォルダに置かれた場合、(
sys.path
を変える必要なく) web2pyアプリケーションの任意の場所からインポートできます:import mymodule
実行環境
ここで議論されてる全てのものは正しく動きますが、代わりに、12章で説明するコンポーネントを使用してアプリケーションを構築することをお勧めします。
web2pyのモデルとコントローラのファイルは、Pythonの import
文を使用してインポートできないという点でPythonモジュールではありません。この理由は、モデルとコントローラが、用意された環境で実行されるように設計されているためです。その環境ではweb2pyのグローバルオブジェクト(request、response、session、cache、T)と、ヘルパー関数が予め公開されています。これは、web2pyの環境は動的に作られるのに関わらず、Pythonが静的(レキシカル)スコープの言語であるため、必要になります。
web2pyは exec_environment
関数を用意し、モデルとコントローラに直接アクセスすることを許しています。exec_environment
は、web2pyの実行環境を作り出し、ファイルをロードし、その環境を含むStorageオブジェクトを返します。Storageオブジェクトはまた、名前空間のメカニズムとして機能します。この実行環境で実行されるように設計された任意のPythonファイルは、exec_environment
を使ってロードすることができます。exec_environment
には、次のような利用方法が含まれます:
- 他のアプリケーションからのデータ(モデル)にアクセスします。
- 他のモデルやコントローラからグローバルオブジェクトにアクセスします。
- 他のコントローラからコントローラの関数を実行します。
- サイト全体のヘルパーライブラリをロードします。
次の例では、cas
アプリケーションの user
テーブルから、rowsを読み出します:
from gluon.shell import exec_environment
cas = exec_environment('applications/cas/models/db.py')
rows = cas.db().select(cas.db.user.ALL)
もう1つの例で、次のコードを含む "other.py" コントローラを仮定します:
def some_action():
return dict(remote_addr=request.env.remote_addr)
このアクションを、他のコントローラから(またはweb2pyのシェルから)呼び出す方法は以下の通りです:
from gluon.shell import exec_environment
other = exec_environment('applications/app/controllers/other.py', request=request)
result = other.some_action()
2行目の request=request
は省略可能です。これは現在のリクエストを、"other" の環境に渡す効果があります。この引数の指定がない時は、新規の空の(ただし request.folder
を除く)リクエストオブジェクトを含む環境になります。レスポンスやセッションオブジェクトも、exec_environment
に渡すことが可能です。ただし、リクエスト、レスポンス、セッションオブジェクトを渡す時は注意してください。呼び出されたアクションによる修正や、呼び出されたアクションでのコードの依存性は、予期せぬ副作用につながる可能性があります。
3行目で呼ぶ出す関数は、ビューを実行しません。つまり "some_action" で明示的に、response.render
を呼び出ししない限り、単純に辞書を返します。
最後の注意:exec_environment
を不適切に使用しないでください。他のアプリケーションでのアクションの結果が必要な場合、おそらくXML-RPC APIで実装すべきです(web2pyで、XML-RPC APIを実装するのは容易です)。そして exec_environment
を、リダイレクトの仕組みとして使用しないでください。代わりに redirect
ヘルパーを使用してください。
協調
複数のアプリケーションを協調させる方法は多数あります:
- アプリケーションは同じデータベースに接続することができ、テーブルを共有することができます。データベースの全てのテーブルを、全てのアプリケーションで定義する必要はありません。しかし、それらを使用するアプリケーションでは、定義しなければなりません。同じテーブルを使用する全てのアプリケーションは一つを除いて、
migrate=False
としてテーブルを定義しなければなりません。 - アプリケーションは、(12章で説明する)LOADヘルパーを使って、他のアプリケーションのコンポーネントを埋め込むことができます。
- アプリケーションは、セッションを共有することができます。
- アプリケーションは、XML-RPCを介して、リモートで互いのアクションを呼び出すことができます。
- アプリケーションは、ファイルシステム(それらが同一のファイルシステムを共有すると仮定)を介して、互いのファイルにアクセスすることができます。
- アプリケーションは、互いのアクションを、前述のようにexec_environmentを用いて、ローカルに呼び出すことができます。
- アプリケーションは、次の構文を使って、互いのモジュールをインポートすることができます:
from applications.otherapp.modules import mymodule
あるいは
import applications.otherapp.modules.othermodule
- アプリケーションは、
PYTHONPATH
の検索パス、つまりsys.path
にある、任意のモジュールをインポートすることができます。
アプリは他のアプリのセッションを、次のコマンドで読み込むことができます:
session.connect(request, response, masterapp='appname', db=db)
ここで "appname" は、クッキーに最初のsession_idを設定する、マスターアプリケーションの名前です。db
はセッションテーブル(web2py_session
)を含む、データベース対するデータベース接続です。セッションを共有する全てのアプリは、セッションストレージとして同じデータベースを使用する必要があります。
ロギング
Pythonはロギング用のAPIを提供しています。Web2pyは、アプリが利用できるように、それを設定するメカニズムを提供します。
アプリケーションでは、ロガーを作成することができます。モデルで次のように記述します:
import logging
logger = logging.getLogger("web2py.app.myapp")
logger.setLevel(logging.DEBUG)
これを用いて、様々な重要度でメッセージのログを取ることができます。
logger.debug("Just checking that %s" % details)
logger.info("You ought to know that %s" % details)
logger.warn("Mind that %s" % details)
logger.error("Oops, something bad happened %s" % details)
ロギング
は、次で説明されているpython標準のモジュールです:
http://docs.python.org/library/logging.html
"web2py.app.myapp" 文字列は、アプリレベルのロガーを定義します。
これが正しく動作するために、このロガーに対する設定ファイルが必要となります。 その1つは、web2pyのルートフォルダの "logging.example.conf" で提供されています。このファイルを "logging.conf" にリネームし、必要ならカスタマイズしてください。
このファイルは自己ドキュメント化されています。ファイルを開いて、参照してください。
"myapp" アプリケーションに対する設定可能なロガーを作成するためには、[loggers]キーのリストにmyappを加える必要があります:
[loggers]
keys=root,rocket,markdown,web2py,rewrite,app,welcome,myapp
そして、[logger_myapp]セクションを加え、[logger_welcome]を参考に設定してください。
[logger_myapp]
level=WARNING
qualname=web2py.app.myapp
handlers=consoleHandler
propagate=0
"handlers" ディレクティブは、ロギングのタイプを指定します。ここでは "myapp" を、コンソールへロギングしています。
WSGI
web2pyとWSGIは愛憎の関係にあります。私たちの観点では、WSGIは、ポータブルな方法でWebサーバーがWebアプリケーションに接続するためのプロトコルとして開発されたものと見ていて、私たちはその目的で使用しています。web2pyはコア部分において、一つのWSGIアプリケーションです(gluon.main.wsgibase
)。一部の開発者は、WSGIをミドルウェア通信プロトコルとしての限界まで推し進め、Webアプリケーションを多数の層からなる、たまねぎのように開発しています(各層は、全体的なフレームワーク上で独立に開発されたWSGIミドルウェアからなります)。web2pyはこのような構造を内部で採用していません。これは、フレームワークのコアとなる機能(クッキーやセッション、エラー、トランザクション、ディスパッチの処理)は、1つの包括的な層で扱ったほうが、速度とセキュリティの面でより最適化できると、私たちは感じているからです。
それでもなおweb2pyでは、サードパーティのWSGIアプリケーションとミドルウェアを、次の3通りの方法で(もしくはそれらの組み合わせで)使用することができます。
- "wsgihandler.py" ファイルを編集し、任意のサードパーティのWSGIミドルウェアを取り込むことができます。
- アプリケーション内の任意の指定したアクションに対し、サードパーティのWSGIミドルウェアを接続することができます。
- アクションから、サードパーティのWSGIアプリを呼び出すことができます。
唯一の制限は、サードパーティ製のミドルウェアを使用しても、web2pyのコア機能を置き換えることはできないことです。
外部ミドルウェア
"wsgibase.py" ファイルを検討してみましょう:
#...
LOGGING = False
#...
if LOGGING:
application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase,
logfilename='httpserver.log',
profilerfilename=None)
else:
application = gluon.main.wsgibase
LOGGING
が True
にセットされている時、gluon.main.wsgibase
はミドルウェアの関数 gluon.main.appfactory
によってラップされます。それにより "httpserver.log" ファイルへのロギング機能が提供されます。同じようにして、任意のサードパーティ製のミドルウェアを追加することができます。詳細については、公式のWSGIドキュメントを参照してください。
内部ミドルウェア
コントローラのアクション(例えばindex
)と、サードパーティ製のミドルウェアのアプリケーション(例えば、出力を大文字に変換する MyMiddleware
)があるとします。この時、web2pyのデコレータを使って、ミドルウェアをアクションに適用することができます。以下はその例です:
class MyMiddleware:
"""converts output to upper case"""
def __init__(self,app):
self.app = app
def __call__(self, environ, start_response):
items = self.app(environ, start_response)
return [item.upper() for item in items]
@request.wsgi.middleware(MyMiddleware)
def index():
return 'hello world'
全てのサードパーティのミドルウェアが、このメカニズムで動作することは保障できません。
WSGIアプリケーションの呼び出し
WSGIアプリをweb2pyアクションから呼び出すことは簡単です。以下はその例です:
def test_wsgi_app(environ, start_response):
"""this is a test WSGI app"""
status = '200 OK'
response_headers = [('Content-type','text/plain'),
('Content-Length','13')]
start_response(status, response_headers)
return ['hello world!\n']
def index():
"""a test action that calls the previous app and escapes output"""
items = test_wsgi_app(request.wsgi.environ,
request.wsgi.start_response)
for item in items:
response.write(item,escape=False)
return response.body.getvalue()
この場合、index
アクションは test_wsgi_app
を呼び出し、値を返す前にエスケープします。また、index
自身はWSGIアプリではなく、通常のweb2pyのAPI(ソケットに書き込む response.write
など)を使用する必要があることに、注意してください。