標準ハンドラ¶
Publisher ハンドラ¶
publisher
ハンドラを使えば、わざわざ自分でハンドラを書く必要がなく、素早いアプリケーション開発に専念できます。
publisher
ハンドラは、 Zope の ZPublisher から着想を得ています。
はじめに¶
ハンドラを使うには、設定ファイルに以下のように書きます:
<Directory /some/path>
SetHandler mod_python
PythonHandler mod_python.publisher
</Directory>
このハンドラを使うと、モジュール内の関数や変数を URL でアクセスできます。
例えば、 hello.py
という名の以下のようなモジュールがあるとしましょう:
""" Publisher example """
def say(req, what="NOTHING"):
return "I am saying %s" % what
これで、 http://www.mysite.com/hello.py/say
という URL は I am saying NOTHING
を返します。
一方、 http://www.mysite.com/hello.py/say?what=hello
は I am saying hello
を返します。
パブリッシュのアルゴリズム¶
publisher ハンドラは URI を Python の変数や呼び出し可能オブジェクトに直接対応づけます。 変数の場合には文字列表現を、呼び出しオブジェクトの場合には呼び出した戻り値の文字列表現を返します。
トラバース¶
publisher ハンドラは、 URI に指定したモジュールを探して import します。
モジュールの位置は request.filename
属性で決まります。
import の際、ファイル拡張子がある場合には無視します。
request.filename
が空の場合には、 'index'
をデフォルト値のモジュール名として使います。
モジュールを import すると、URI の残りの部分からクエリデータまで (いわゆる PATH_INFO
) の部分を使って、モジュール内のオブジェクトを探します。
publisher ハンドラは各要素をモジュール内のPython オブジェクトに対応づけながら、パスの要素を左から右にひとつづつ トラバース します。
URL に PATH_INFO
がない場合、publisher ハンドラは index
をデフォルト値に使います。
パスの最後の要素がモジュール内のオブジェクト名で、かつその前にある要素がディレクトリ名の場合 (モジュール名が指定されていない場合)、モジュール名はデフォルト値の index
になります。
以下のような場合にはトラバースを停止して HTTP_NOT_FOUND
を返します:
- トラバースしたオブジェクトの名前がアンダースコア (
_
) で始まっている場合。 逆に、Web からアクセスさせたくないオブジェクトを保護するにはアンダースコアを使ってください。 - モジュールに到達した場合。セキュリティ上の理由から、モジュールはパブリッシュの対象にできません。
パス上にオブジェクトが見つからなかった場合、クライアントに HTTP_NOT_FOUND
を返します。
例えば、以下のような設定があるとしましょう:
DocumentRoot /some/dir
<Directory /some/dir>
SetHandler mod_python
PythonHandler mod_python.publisher
</Directory>
そして、以下のようなファイル /some/dir/index.py
があったとします:
def index(req):
return "We are in index()"
def hello(req):
return "We are in hello()"
URL にアクセスした結果は次のようになります:
- http://www.somehost/index/index は
'We are in index()'
を返します。 - http://www.somehost/index/ は
'We are in index()'
を返します。 - http://www.somehost/index/hello は
'We are in hello()'
を返します。 - http://www.somehost/hello は
'We are in hello()'
を返します。 - http://www.somehost/spam は
'404 Not Found'
を返します。
引数のマッチングと呼び出し¶
publisher ハンドラがパブリッシュ対象のオブジェクトを見つけたとします。
オブジェクトが呼び出し可能オブジェクトであってクラスでない場合、ハンドラはオブジェクトの受け取る引数のリストを調べます。
次に、このリストを POST
や GET
経由で受け取ったフォームデータのパラメタ名と比較します。
引数と名前の一致するフィールドの値は、呼び出し可能オブジェクトの該当する引数に文字列で渡します。
名前の一致しないフィールドは暗黙のまま捨てます。ただし、対象のオブジェクトが **kwargs
形式の引数を受け取る場合には、名前の一致しなかったフィールドを **kwargs
引数で渡します。
パブリッシュ対象のオブジェクトが呼び出し可能オブジェクトの場合やクラスの場合、その文字列表現をクライアントに返します。
Authentication¶
The publisher handler provides simple ways to control access to modules and functions.
At every traversal step, the Publisher handler checks for presence of
__auth__
and __access__
attributes (in this order), as
well as __auth_realm__
attribute.
If __auth__
is found and it is callable, it will be called
with three arguments: the request
object, a string containing
the user name and a string containing the password. If the return
value of
__auth__
is false, then HTTP_UNAUTHORIZED
is
returned to the client (which will usually cause a password dialog box
to appear).
If __auth__()
is a dictionary, then the user name will be
matched against the key and the password against the value associated
with this key. If the key and password do not match,
HTTP_UNAUTHORIZED
is returned. Note that this requires
storing passwords as clear text in source code, which is not very secure.
__auth__
can also be a constant. In this case, if it is false
(i.e. None
, 0
, ""
, etc.), then
HTTP_UNAUTHORIZED
is returned.
If there exists an __auth_realm__
string, it will be sent
to the client as Authorization Realm (this is the text that usually
appears at the top of the password dialog box).
If __access__
is found and it is callable, it will be called
with two arguments: the request
object and a string containing
the user name. If the return value of __access__
is false, then
HTTP_FORBIDDEN
is returned to the client.
If __access__
is a list, then the user name will be matched
against the list elements. If the user name is not in the list,
HTTP_FORBIDDEN
is returned.
Similarly to __auth__
, __access__
can be a constant.
In the example below, only user 'eggs'
with password 'spam'
can access the hello
function::
__auth_realm__ = "Members only"
def __auth__(req, user, passwd):
if user == "eggs" and passwd == "spam" or \
user == "joe" and passwd == "eoj":
return 1
else:
return 0
def __access__(req, user):
if user == "eggs":
return 1
else:
return 0
def hello(req):
return "hello"
Here is the same functionality, but using an alternative technique::
__auth_realm__ = "Members only"
__auth__ = {"eggs":"spam", "joe":"eoj"}
__access__ = ["eggs"]
def hello(req):
return "hello"
Since functions cannot be assigned attributes, to protect a function,
an __auth__
or __access__
function can be defined within
the function, e.g.::
def sensitive(req):
def __auth__(req, user, password):
if user == 'spam' and password == 'eggs':
# let them in
return 1
else:
# no access
return 0
# something involving sensitive information
return 'sensitive information`
Note that this technique will also work if __auth__
or
__access__
is a constant, but will not work is they are
a dictionary or a list.
The __auth__
and __access__
mechanisms exist
independently of the standard
PythonAuthenHandler. It
is possible to use, for example, the handler to authenticate, then the
__access__
list to verify that the authenticated user is
allowed to a particular function.
注釈
In order for mod_python to access __auth__
, the module
containing it must first be imported. Therefore, any module-level
code will get executed during the import even if
__auth__
is false. To truly protect a module from being
accessed, use other authentication mechanisms, e.g. the Apache
mod_auth
or with a mod_python PythonAuthenHandler.
Form Data¶
In the process of matching arguments, the Publisher handler creates an
instance of FieldStorage class.
A reference to this instance is stored in an attribute member{form}
of the request
object.
Since a FieldStorage
can only be instantiated once per
request, one must not attempt to instantiate FieldStorage
when
using the Publisher handler and should use
request.form
instead.
WSGI Handler¶
WSGI handler can run WSGI applications as described in PEP 333.
Assuming there exists the following minimal WSGI app residing in a file named
mysite/wsgi.py
in directory /path/to/mysite
(so that the full
path to wsgi.py
is /path/to/mysite/mysite/wsgi.py
):
def application(environ, start_response):
status = '200 OK'
output = 'Hello World!'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
It can be executed using the WSGI handler by adding the following to the Apache configuration:
PythonHandler mod_python.wsgi
PythonOption mod_python.wsgi.application mysite.wsgi
PythonPath "sys.path+['/path/to/mysite']"
The above configuration will import a module named mysite.wsgi
and
will look for an application
callable in the module.
An alternative name for the callable can be specified by appending it
to the module name separated by '::'
, e.g.:
PythonOption mod_python.wsgi.application mysite.wsgi::my_application
If you would like your application to appear under a base URI, it can
be specified by wrapping your configuration in a <Location>
block. It can also be specified via the mod_python.wsgi.base_uri
option, but the <Location>
method is recommended, also because it
has a side-benefit of informing mod_python to skip the map-to-storage
processing phase and thereby improving performance.
For example, if you would like the above application to appear under
'/wsgiapps'
, you could specify:
<Location /wsgiapps>
PythonHandler mod_python.wsgi
PythonOption mod_python.wsgi.application mysite.wsgi
PythonPath "sys.path+['/path/to/mysite']"
</Location>
With the above configuration, content formerly under
http://example.com/hello
becomes available under
http://example.com/wsgiapps/hello
.
If both <Location>
and mod_python.wsgi.base_uri
exist, then
mod_python.wsgi.base_uri
takes precedence.
mod_python.wsgi.base_uri
cannot be '/'
or end with a
'/'
. “Root” (or no base_uri) is a blank string, which is the
default. (Note that it is allowed for <Location>
path to be
"/"
or have a trailing slash, it will automatically be removed by
mod_python before computing PATH_INFO
).
注釈
PEP 333 describes SCRIPT_NAME
and PATH_INFO
environment
variables which are core to the specification. Most WSGI-supporting
frameworks currently in existence use the value of PATH_INFO
as the
request URI.
The two variable’s name and function originate in CGI
(RFC 3875), which describes an environment wherein a script (or
any executable’s) output could be passed on by the web server as
content. A typical CGI script resides somewhere on the filesystem
to which the request URI maps. As part of serving the request the
server traverses the URI mapping each element to an element of the
filesystem path to locate the script. Once the script is found, the
portion of the URI used thus far is assigned to the SCRIPT_NAME
variable, while the remainder of the URI gets assigned to
PATH_INFO
.
Because the relationship between Python modules and files on disk
is largely tangential, it is not very clear what exactly
PATH_INFO
and SCRIPT_NAME
ought to be. Even though Python
modules are most often files on disk located somewhere in the
Python path, they don’t have to be (they could be code objects
constructed on-the-fly), and their location in the filesystem has
no relationship to the URL structure at all.
The mismatch between CGI and WSGI results in an ambiguity which
requires that the split between the two variables be explicitely
specified, which is why mod_python.wsgi.base_uri
exists. In essence
mod_python.wsgi.base_uri
(or the path in surrounding
<Location>
) is the SCRIPT_NAME
portion of the URI and
defaults to ''
.
An important detail is that SCRIPT_NAME
+ PATH_INFO
should
result in the original URI (encoding issues aside). Since
SCRIPT_NAME
(in its original CGI definition) referrs to an
actual file, its name never ends with a slash. The slash, if any,
always ends up in PATH_INFO
. E.g. /path/to/myscrip/foo/bar
splits into /path/to/myscript
and /foo/bar
. If the whole
site is served by an app or a script, then SCRIPT_NAME
is a
blank string ''
, not a '/'
.
PSP Handler¶
PSP handler is a handler that processes documents using the
PSP
class in mod_python.psp
module.
To use it, simply add this to your httpd configuration:
AddHandler mod_python .psp
PythonHandler mod_python.psp
For more details on the PSP syntax, see Section psp – Python Server Pager.
If PythonDebug
server configuration is On
, then by
appending an underscore ('_'
) to the end of the url you can get a
nice side-by-side listing of original PSP code and resulting Python
code generated by the psp} module
. This is very useful for
debugging. You’ll need to adjust your httpd configuration::
AddHandler mod_python .psp .psp_
PythonHandler mod_python.psp
PythonDebug On
注釈
Leaving debug on in a production environment will allow remote users to display source code of your PSP pages!
CGI ハンドラ¶
CGI ハンドラは、 mod_python
下で CGI 環境をエミュレートするためのハンドラです。
この環境は、「真の」CGI 環境ではなく、Python レベルのエミュレーションなので注意してください。
この環境では、 stdin
および stdout
は、それぞれ sys.stdin
と sys.stdout
に置き換わり、環境変数は辞書に置き換わります。
そのため、CGI ハンドラの環境から os.system
などで呼び出した外部プログラムは、 Python プログラム側で利用できる環境変数を見られないばかりか、 「真の」 CGI 環境ではできるはずの標準入出力を使った結果の読み書きも行えません。
このハンドラは CGI の古いコードから移行するための飛び石として提供されています。
mod_python
を使う方法として、このハンドラに長く腰を落ち着けるのはお勧めしません。
なぜなら、この環境はスレッド内での実行を想定しておらず、 mod_python
の数多くの利点を最初から台無しにしてしまうような実装しかできないからです
(例えば、CGI の実行には現在のディレクトリの変更が必要ですが、これは本来スレッドセーフな操作ではありません。
cgihandler はこの問題を解決するためにスレッドをロックして、マルチスレッドサーバであっても一度に一つのリクエストしか処理できないようにしてしまいます)。
CGI ハンドラを使いたければ、.htaccess
ファイルに:
SetHandler mod_python
PythonHandler mod_python.cgihandler
のように書くだけです。
バージョン 2.7 からは、 cgihandler は間接的に import されたモジュールも正しくリロードできます。
この機能は、 CGI スクリプトの呼び出し前にロード済みのモジュールのリスト (sys.modules
) を保存しておき、CGI スクリプトを実行した後のリストと比較することで可能にしています。
リロード対象のモジュールは (__file__
属性が標準 Python ライブラリの置き場所を指しているものを除いて) sys.modules
から除去されます。その結果、次に CGI スクリプトがモジュールを import するときに、Python にモジュールをリロードを強制します。
上記の動作を望まないのなら、 cgihandler.py
ファイルを編集して、
###
で区切られているコードをコメントアウトしてください。
テストの結果、 cgihandler は多数のファイルアップロードを処理する際に何らかのメモリリークを起こすことが分かっています。
原因についてはよく分かっていません。この問題を回避するには、 Apache の MaxRequestsPerChild
設定をゼロでない値にしてください。