第一章 简介
1.1性能
使用mod_python的主要优势在于比传统CGI更高的性能。一个测试,使用在Pentium 1.2GHz的机器上运行Red Hat Linux 7.3。使用4种类型的脚本,基于标准的CGI导入模块(以典型的Python CGI脚本开始),然后输出'Hello!',测试10000次请求作为基准。
标准CGI: 23 次请求/秒
mod_python CGI处理器: 385 次请求/秒
mod_python 发布处理器: 476 次请求/秒
mod_python 处理器: 1203 次请求/秒
1.2可移植性
apache是按照阶段处理请求的(比如:读取请求、处理请求头、检查存取等)。这些阶段可以分别用处理器调用的函数来实现。传统上,使用C语言编写处理器模块。mod_python提供了使用Python拜你些apache处理器扩展apache功能的方法。更多的apache请求处理过程,参考"Apache API Notes",或者参考"Mod_python-Integrating Python with Apache"文件。
为了方便的从CGI移植,标准的mod_python处理器提供了CGI模拟环境,允许用户不修改遗留下来的脚本,而在大多数时候无需进行任何修改。
参考:
http://dev.apache.org
apache开发者资源
http://www.modpython.org/python10/
mod_python-整合Python和Apache,出现在Python 10(?)
1.3历史
mod_python起源于Httpdapy项目(1997)。很久以后Httpdapy才被叫做mod_python,因为Httpdapy并不是特定用于apache服务器的。Httpdapy设计成跨平台的,而实际上主要从Netscape服务器开始(追溯到1997年的Nsapy)。
摘录自Httpdapy项目的README文件的描述将Python嵌入到apache服务器的挑战与解决方法。
""" #小字体
在我多年前开发第一个WEB应用程序时就发现CGI访问数据库非常慢,因为每次点击请求都会装入解释器并执行数MB的数据。数据库往往都很大,并将大量时间浪费在数据库的连接/认证等"意义重大"的事情上,像DNS查询、编码与解码、内存分配等等。在应用提速的压力下,我尝试过使用Python和多种工具的综合使用。我并不迷信微软的ASP,又被Netscape的性能和满是臭虫而郁闷。"Cold Fusion"(冷聚变)看起来有希望,但我稍后学会了HTML类标签的书写方式,可读性跟汇编有一拼。比如PHP。但是我真的很想用Python来写东西。
就在同时有关Python互联网编程的书籍出版了,其中将Python嵌入到Netscape服务器的方式立即引起了我的注意。我在我的项目中使用了这个例子,并在稍后开发了我自己的改进版,叫做Nsapy,并在WindowsNT和Solaris上编译。
尽管Nsapy仅能工作在Netscape服务器上,但是出于灵活通用的面向对象设计,在Python精灵中,很容易移植到其他的WEB服务器。
后来,曾经流行的Netscape服务器挂了,我就将Nsapy移植到最流行的服务器apache上。所以从Nsapy衍生出了Httpdapy。
"""
继续这个传奇,你会想到将Httpdapy移植到其他服务器上,实际上他们比预想要复杂而且枯燥。
反之,提供一个类似于Perl语言的apache扩展mod_perl一样或更好性能的部件会更有意思。
俱往矣,mod_python的第一个版本发布于2000年5月。
完成...
第二章 安装
注意:获得关于安装的帮助和其他问题的最好地方是mod_python的邮件列表。可以发送标题为"subscribe"的邮件到mod_python-request@modpython.org。
2.1先决条件
Python 2.2.1或更新的版本,早起版本将无法使用。
apache 2.0.40或更新的版本,如果要使用apache 1.3.x,请使用mod_python的版本2.7.x。
编译mod_python需要有apache和Python的包含文件,并且确保安装了Python标准库。如果你从源代码安装了Python和apache,那么你已经拥有了这些了。如果你使用了预打包软件,如RPM或其他方式则只有二进制版本,而没有源码。通常apache和Python的包含文件和库文件在各自的"development"包中。如果你不确定拥有这些重要文件,请从源码安装Python和apache,或者阅读你系统的文档以了解如何获得开发包。
2.2编译
有两种链接apache模块的方式:静态的和DSO方式。
DSO方式现在非常流行,并推荐用于mod_python的安装。模块被编译成共享库,并在运行时动态装入。
DSO方式的优势在于安装模块时无需重新编译apache。关于DSO机制的详细信息参见http://httpd.apache.org/docs-2.0/dso.html。
当前版本的mod_python仅支持DSO方式。
静态链接是一种较老的方式,比动态连接的方式支持更多的平台,但是较少使用。主要的缺陷是必须要重新编译apache,在很多情况下并不是一个好的选择。
2.2.1运行./configure
./configure脚本将会分析你的运行环境,并创建自定义的Make文件用于你的系统。使用autoconf生成的./configure包含如下内容:
查看apxs程序是否存在。这个程序作为apache标准发行版的一部分提供DSO支持。如果找不到则无法实现DSO功能。使用--with-apxs指定选项:
$ ./configure --with-apxs=/usr/local/apache/bin/apxs
强烈建议使用这个选项。
检查Python版本,并尝试找到libpython参数变量的二进制发行版。缺省时可以在Path变量中找到python。如果找到的首个python二进制版本不适合mod_python,则可以手动指定python的路径使用--with-python,如:
$ ./configure --with-python=/usr/local/bin/python2.3
设置mod_python保留的互斥锁数量。在某些系统中,互斥锁是一种有限的资源。增加最大互斥锁数量将会增加使用session锁定的性能。缺省值是8,一个性能不错的数值是32。使用-with-max-locks选项:
$ ./configure --with-max-locks=32
这个选项是在3.2.0版本开始提供的。
尝试查找flex并确定版本。如果PATH中找不到flex则configure会出错。如果版本不对则会给出警告。一般可以忽略这个警告,除非需要重新创建src/psp_parser.c。剖析器(parser)会被PSP调用,使用flex的C语言书写。要求的版本是2.5.31,在大多数平台上2.5.4并不合适。所以已经源码里已经包含了一个psp_parser.c的副本。如果需要编译src/psp_parser.c则需要获取正确版本的flex。如果PATH中的flex版本有误,则可用-with-flex选项:
$ ./configure --with-flex=/usr/local/bin/flex
这是3.2.0版本中的新特性
构建mod_python文档时需要用到python的源代码。可以如果不需要文档,可以忽略这个选项。如果需要则用--with-python-src选项:
$ ./configure --with-python-src=/usr/src/python2.3
这个是3.2.0版本中的新特性
2.2.2运行make
开始构建过程,简单的运行如下即可:
$ make
2.3安装
2.3.1运行make install
这个安装过程必须在root用户下完成:
$ su
# make install
这将会简单的将二进制版本复制到apache的libexec目录。
并且会安装python-libraries到site-packages并编译他们。
如果你想只安装python库或者只安装DSO,可以用如下make目标:
# make install_py_lib
# make install_dso
2.3.2配置apache
按照DSO模式安装的apache,需要在httpd.conf中加入启动mod_python的句子:
LoadModule python_module libexec/mod_python.so
实际的mod_python.so路径可能有所变化,但是make install将会显示这个文件的确切路径。
2.4测试
注意次部分内容仅适用于3.x版本,如果使用2.7.x版本,你需要找到更适合的文档。
创建一个在WEB站点可见的目录,如htdocs/test。
在httpd.conf或者.htaccess文件中添加如下内容。如果是在.htaccess文件中,则无需<Directory>标签。并且确保httpd.conf文件中的AllowOverride目标至少包含FileInfo,缺省是None,将会无法工作。
<Directory /APACHEHOME/htdocs/test>
AddHandler mod_python .py
PythonHandler mptest
PythonDebug On
</Directory>
重定向所有以.py结尾的URL到mod_python处理器。mod_python收到请求之后查找合适的处理器来处理请求。这里有个简单的自定义处理器叫mptest。我们看看如何定义这个处理器。
此时如果修改了主配置文件httpd.conf则需要重启apache来使之生效。
编辑mptest.py文件在htdocs/test目录。
from mod_python import apache
def handler(req):
req.content_type='text/plain'
req.write("Hello, world!")
return apache.OK
指向URL路径到mptest.py。将会看到"Hello, world!"。如果看不到则参考故障处理。
依照配置文件的规则,指定任何.py文件都会最终指向mptest.py做处理。因为已经明确的指定了mptest做处理器。如果需要用不同的处理器处理不同的文件则需要更高层的处理器,比如publisher发布器、mpservlets或vampire。这些都是动态装入的处理器。
如果都运行无误则转入第三章,入门。
参考:
http://home.comcast.net/d.popowich/mpservlets
http://www.dscpl.com.au/projects/vampire
2.5故障处理
如果出现问题,可以按照如下的处理:
检查错误输出
检查服务器错误日志
单处理执行apache
$ ./httpd -X
这样将会显示更多有用信息
从mod_python3.2.0开始,可以使用mod_python.testhandler来诊断你的配置。添加到httpd.conf文件:
<Location /mpinfo>
SetHandler mod_python
PythonHandler mod_python.testhandler
</Location>
这时指向你的/mpinfo的URL则会显示一些信息帮助处理mod_python的问题。
在mod_python邮件列表提问,并确保提供如下信息:
mod_python版本
操作系统类型、名字、版本号
Python版本、非常规的编辑选项
apache服务器版本
相关的apache配置,.htaccess
相关的Python代码
完成...
第三章 指南
3.1使用发布器(Publisher)的快速例子
这一节提供无需太多细节的发布器指南。更多如何使用mod_python处理器的细节将在以后解释。
发布器(publisher)提供了发布mod_python标准模块的方式。需要在配置文件中加入如下配置:
AddHandler mod_python .py
PythonHandler mod_python.publisher
PythonDebug On
下例会返回简单的表单数据。询问姓名、电子邮件、注释之后将会给管理员发邮件。这个简单的应用程序由两个文件组成:form.html用于提交数据,form.py用于处理表单动作。
如下是表单:
<html>
Please provide feedback below:
<p>
<form action="form.py/email" method="POST">
Name: <input type="text" name="name"><br>
Email: <input type="text" name="email"><br>
Comment: <textarea name="comment" rows=4 cols=20></textarea><br>
<input type="submit">
</form>
</html>
注意表单的action属性指向form.py/email调用,如下创建form.py,如下:
import smtplib
WEBMASTER="webmaster" #管理员电邮
SMTP_SERVER="localhost" #本机SMTP服务器
def email(req,name,email,comment):
#一定要确保用户提供了所有的参数
if not(name and email and comment):
return "缺少必要的参数"
#创建消息文本
msg=""" \
From: %s
Subject: feedback
To: %s
I have the following comment:
%s
Thank You,
%s
""" % (email,WEBMASTER,comment,name)
#发送信息
conn=smtplib.SMTP(SMTP_SERVER)
conn.sendmail(email,[WEBMASTER],msg)
conn.quit()
#提供返回页面
s="""\
<html>
Dear %s,<br>
Thank you for your kind comments, we
will get back to you shortly.
</html>""" % name
return name
当用户点击提交按钮时,发布器将会装入email函数到form模块,传递表单字段作为关键字参数。也会传递请求对象到req。
注意逆必须要有req参数。发布器已经可以很聪明的传递必要的参数。函数的返回值将被发送到客户端浏览器。
即使使用发布器可以非常简单的处理,但是程序仍然可以利用mod_python的强大功能,比如调用请求对象。当然也可以调用本地的mod_python处理器来完成大量的工作。比如设置HTML头路由req.headers_out;返回错误抛出apache.SERVER_ERROR异常;从客户端的重定向读写文件req.write()和req.read(),等等。
阅读6.1节了解更多关于发布器的详细信息。
3.2apache处理器概览
节标题翻译有误,应为"快速学习apache如何处理请求"。
如果你想要深入钻研mod_python的功能,需要理解什么是处理器。
apache分阶段(phase)处理请求。比如第一个阶段是用户认证,之后是检验用户是否有权访问特定的文件,然后是读取文件并发送到客户端。一个典型的静态文件请求包含三个阶段:1.翻译URI到文件位置2.读取文件并发送到客户端3.记录请求日志。更复杂的处理依赖于配置文件。
一个处理器就是初始处理某个阶段的函数。同一个阶段可以有多于一个处理器进行处理,被叫做apache序列。对应每个阶段有个缺省的apache处理器(大多数只做缺省动作或者什么都不作)。然后由其他的apache模块提供处理器,比如mod_python。
mod_python提供了apache每一个重要的处理器。mod_python处理器缺省时不会做任何事情,除非用特定的配置文件标志。这些标志以"Python"开始并以"Handler"结尾(如:PythonAuthenHandler),指定Python函数来处理指定的阶段。所以mod_python的主函数只是作为发报机的角色连接apache处理器和Python函数。
最常用的处理器是PythonHandler。它处理含有上下文的请求。因为它没有名字,所以有时也成为通用处理器。这个处理器的apache缺省行为是读取文件并发送到客户端。大多数应用应该重写这个处理器。
3.3mod_python做了什么
我们假设有如下配置:
<Directory /mywebdir>
AddHandler mod_python .py
PythonHandler myscript
PythonDebug On
</Directory>
所以还需要一个如下文件,'/mywebdir/myscript.py'如下:
from mod_python import apache
def handler(req):
req.content_type="text/plain"
req.write("Hello World!")
return apache.OK
如下的事情将会发生:AddHandler标志告诉apache所有的以.py结尾的'/mywebdir'下或其子目录下的文件请求都由mod_python负责处理。'PythonHandler myscript'标志告诉mod_python通用处理器在myscript脚本。'PythonDebug On'标志告诉mod_python如果发生错误,则把错误信息输出到客户端,便于开发。
当请求到来时,apache开始分步骤处理请求并讲请求发送到mod_python。mod_python处理器检查配置获取处理器。在本例,我们除了通用处理器之外没有调用其他东西。当读取配置到'PythonHandler myscript'时,会发生下面的事情:
1.如果没有做过,假设预设目录已经存在于sys.path中。
2.尝试按名字导入模块myscript。注意子目录是不在sys.path中的,如果需要只能按照包名的方式调用。
3.在myscript中查找函数handler
4.调用这个函数并传递请求对象(关于请求对象的更多东西在后面)
5.在这里我们细致研究一下脚本:
from mod_python import apache
导入apache模块来获取apache的接口。大多数mod_python程序需要有这一行。
def handler(req):
这是处理器函数的声明。叫做'handler'是因为mod_python按照配置标志寻找函数。将配置标志转换成小写,然后去掉'python'即可。这样'PythonHandler'就成了'handler'。当然也可以用其他的名字,但是需要用'::'来分隔函数名,比如一个处理器函数叫'spam',那么配置标志就是'PythonHandler myscript::spam'
注意,处理器必须包含一个请求对象参数。请求对象提供了关于请求的大量信息,比如客户端的IP,请求头,URL等。向客户端发送信息也需要用到请求对象,这里是没有'response'对象的。
req.content_type="text/plain"
设置文档MIME类型为'text/plain'。缺省的是'text/html',但是如果处理器没有打算返回html,那么'text/plain'更恰当(appropriate)一些。特别注意:需要在req.write方法之前调用如上语句,因为一旦调用req.write,HTTP响应头就被发送出去了,其后的HTTP响应头的改变都会忽略。
req.write("Hello World!")
将会把字符串写入到客户端。
return apache.OK
告知apache,请求处理完成并成功。如果没有返回OK,则apache会自动返回为apache.HTTP_INTERNAL_SERVER_ERROR或return apache.HTTP_FORBIDDEN。当处理失败时会在错误日志中记录,并告知客户端一点错误信息。
小技巧:如果你注意到,会发现如果请求的URL不是指向myscript.py,而只是一个.py结尾的文件,哪怕是一个不存在的文件,也可以得到相同的结果。因为服务器只是将特定结尾的处理直接交给相关模块的,而并不要求那一定是一个存在的文件。
3.4更复杂的例子-认证
现在,你知道写一个处理器多么的简单,下面我们来显示一个更复杂的例子。
如果需要用密码来保护一个目录。比如登录名为'spam',密码为'eggs'。
首先需要告诉apache在需要认证时调用我们的认证处理器。加入PythonAuthenHandler标志到配置文件:
<Directory /mywebdir>
AddHandler mod_python .py
PythonHandler myscript
PythonAuthenHandler myscript
PythonDebug On
</Directory>
因为一个模块可以包含多个函数,所以也可以在一个模块中使用多个处理器。
然后是告知apache,使用Basic HTTP认证。仅限有效用户进入。配置如下:
<Directory /mywebdir>
AddHandler mod_python .py
PythonHandler myscript
PythonAuthenHandler myscript
PythonDebug On
AuthType Basic
AuthName "Restricted Area"
require valid-user
</Directory>
然后在myscript.py中写认证函数,一个简单认证函数如下:
from mod_python import apache
def authenhandler(req):
pw=req.get_basic_auth_pw()
user=req.user
if user=="spam" and pw=="eggs":
return apache.OK
else:
return apache.HTTP_UNAUTHORIZED
如下是逐行解释:
def authenhandler(req):
认证函数的声明,至于为什么叫这个名字,自己可以参考命名规则。
pw=req.get_basic_auth_pw()
这里获取密码。basic HTTP认证采用base64编码发送到服务器。这个函数返回的直接就是密码了。注意必须在获取用户名之前先获取密码。
user=req.user
获取用户名
if user=="spam" and pw=="eggs":
return apache.OK
对比用户名和密码的值,如果正确返回告知验证成功,并进入下一个阶段的处理。在本例里下一个阶段是handler()。
else:
return apache.HTTP_UNAUTHORIZED
告知apache验证失败。这通常会导致客户端浏览器弹出对话框要求用户名和密码。
实测:
发现我使用的httpd-2.0.55提示验证类型无效,一共两种验证类型,一种是Basic一种是Digest,都是不允许的。就是按照如上例子。
终于实验成功了,最上面的配置必须写在httpd.conf中才会有效,写在.htaccess中无效。无论是否指定了AllowOverride FileInfo都是。这样进入一个目录之后会弹出系统的对话框提示输入用户名密码。验证成功后只要浏览器不关闭就可以继续进入这个目录,但是关闭后就需要再次验证。
3.5你自己的404处理器
在某些时候,如果希望返回404(HTTP_NOT_FOUND)或者其他非200的结果到客户端,这里有个窍门。如果从你的处理器返回HTTP_NOT_FOUND, apache将会生成错误页。但是却未必是你喜欢的错误页。
这时,你只要设置req.status=apache.HTTP_NOT_FOUND,提交你的页面,然后返回apache.OK就可以了:
from mod_python import apache
def handler(req):
if req.filename[-17:]=='apache-error.html':
#放行apache的错误页
if req.filename[-18:]=='handler-error.html':
#使用自己的错误页
req.status=apache.HTTP_NOT_FOUND
pagebuffer='页面不存在'
else:
#使用文件内容
pagebuffer=open(req.filename,'r').read()
#输出错误页
req.write(pagebuffer)
return(apache.OK)
完成...
第四章 Python API
4.1多解释器
使用mod_python工作的时候应该理解,并不是使用Python在命令行下写脚本,而是用Python调用C语言API。
Python的C语言API提供了创建子解释器的能力。关于子解释器的更多信息详见Py_NewInterpreter()函数。在这里,每个子解释器拥有独立的命名空间,并与其他子解释器不可访问。子解释器非常适合于理解同一个服务器中处理各个请求的进程。
服务器启动mod_python的时候会启动一个主解释器。主解释器包含子解释器词典。刚开始词典是空的。对应每一个请求就会创建一个子解释器,并将引用存储在词典里。这个词典用解释器名作为键名,主解释器叫做'main_interpreter'。其他的解释器受到PythonInterp*的控制。缺省的行为是使用apache虚拟服务器名来命名(ServerName标志)。这意味着同一个虚拟服务器中的脚本在同一个子解释器中执行,但不再同一个虚拟服务器中的脚本因为不同的子解释器而无法互相访问。
PythonInterpPerDirectory和PythonInterpPerDirective标志改变(alter)命名惯例(convention)到要存取的绝对路径,或者在Python*Handler可以访问到的地方,分别的(respectively)。PythonInterpreter能够强制使用解释器名重载所有的命名惯例。
一旦创建,一个子解释器将会被接踵而来的请求重用,在apache停止之前不会被销毁。从调用req.interpreter可以获取当前的解释器名。
参考Python的C语言API:
http://www.python.org/doc/current/api/api.html
4.2请求(request)处理器
一个请求处理器是处理请求阶段细节的处理器。apache在处理请求的每一个阶段都对调用相应的处理器。对每一个阶段都会调用请求处理器,由apache的核心或者模块提供,比如mod_python由用户的Python函数提供。用Python书写的处理器只要遵守如下规则就与C写的没有区别:
处理器函数总是会传递请求对象的引用,一般是req变量。
每个处理器可以返回如下:
apache.OK:这个阶段处理无误
apache.DECLINED:这个阶段处理过后还需要队列中的其他模块处理
apache.HTTP_ERROR:发生了HTTP错误,如下值:
HTTP_CONTINUE 100
HTTP_SWITCHING_PROTOCOLS 101
HTTP_PROCESSING 102
HTTP_OK 200
HTTP_CREATED 201
HTTP_ACCEPTED 202
HTTP_NON_AUTHORITATIVE 203
HTTP_NO_CONTENT 205
HTTP_PARTIAL_CONTENT 206
HTTP_MULTI_STATUS 207
HTTP_MULTIPLE_CHOICES 300
HTTP_MOVED_PERMANENTLY 301
HTTP_MOVEDTEMPORARILY 302
HTTP_SEE_OTHER 303
HTTP_NOT_MODIFIED 304
HTTP_USE_PROXY 305
HTTP_TEMPORARY_REDIRECT 307
HTTP_BAD_REQUEST 400
HTTP_UNAUTHORIZED 401
HTTP_PAYMENT_REQUIRED 402
HTTP_FORBIDDEN 403
HTTP_NOT_FOUND 404
HTTP_METHOD_NOT_ALLOWED 405
HTTP_NOT_ACCEPTABLE 406
HTTP_PROXY_AUTHENTICATION_REQUIRED 407
HTTP_REQUEST_TIME_OUT 408
HTTP_CONFLICT 409
HTTP_GONE 410
HTTP_LENGTH_REQUIRED 411
HTTP_PRECONDITION_FAILED 412
HTTP_REQUEST_ENTITY_TOO_LARGE 413
HTTP_REQUEST_URI_TOO_LARGE 414
HTTP_UNSUPPORTED_MEDIA_TYPE 415
HTTP_RANGE_NOT_SATISFIABLE 416
HTTP_EXPECTATION_FAILED 417
HTTP_UNPROCESSABLE_ENTITY 422
HTTP_LOCKED 423
HTTP_FAILED_DEPENDENCY 424
HTTP_INTERNAL_SERVER_ERROR 500
HTTP_NOT_IMPLEMENTED 501
HTTP_BAD_GATEWAY 502
HTTP_SERVICE_UNAVAILABLE 503
HTTP_GATEWAY_TIME_OUT 504
HTTP_VERSION_NOT_SUPPORTED 505
HTTP_VARIANT_ALSO_VARIES 506
HTTP_INSUFFICIENT_STORAGE 507
HTTP_NOT_EXTENDED 510
可以通过抛出apache.SERVER_RETURN异常来返回错误码,并将HTTP错误码作为异常的参数。例如:
raise apache.SERVER_RETURN, apache.HTTP_FORBIDDEN
处理器可以通过req.write()方法将文档发送到客户端。
客户端提交的数据可以通过req.read()读取。
注意:Python*Handler所在的目录必须已经在sys.path中,如果没有在<Directory>配置段指定则不会在。
一个请求处理器的例子:
from mod_python import apache
def requesthandler(req):
req.content_type='text/plain'
req.write('Hello World!')
return apache.OK
4.3过滤(filter)处理器
过滤处理器可以改变服务器的输入和输出。分两种:输入和输出的过滤。现在mod_python仅提供请求级别(request-level)的过滤器,就是HTTP的请求和回应体可以被过滤。未来的apache将会提供连接级别(connection-level)的过滤器。
过滤处理器接收一个filter对象做参数,请求对象可经由此得出filter.req,但是所有的读写动作必须经由filter的方法。
过滤器在读到None时需要被关闭,代表流的结束。过滤器的返回值将会被忽略,但可用filter.pass_on()方法达到同样的效果。
过滤器必须一开始就注册为PythonInputFilter或PythonOutputFilter,然后添加到apache的配置标志Add/SetInputFilter或Add/SetOutputFilter。
如下例子把所有.py结尾文件用CAPITALIZE过滤器处理,如下是配置:
PythonOutputFilter capitalize CAPITALIZE
AddOutputFilter CAPTITALIZE .py
如下是文件capitalize.py的内容:
from mod_python import apache
def outputfilter(filter):
s=filter.read()
while s:
filter.write(s.upper())
s=filter.read()
if s is None:
filter.close()
书写过滤器时应该注意到过滤器随时都会被调用。过滤器无法控制数据的总量,也无法确定请求何时调用。比如对于一个请求,过滤器可能会执行一次或五次,并无法知晓调用何时结束和先后顺序。从一个read操作返回的EOS代表请求的结束。
过滤器也可能在子请求的递归调用中。为避免数据多次被改变,经常的确保并不在子请求中,依靠req.main变量值。
4.4连接(connection)处理器
连接处理器处理连接到服务器的TCP连接。不同于HTTP处理器,连接处理器接收connection对象做参数。连接处理器可以用于实现协议。如下例配置:
PythonConnectionHandler echo
实现文件echo.py:
from mod_python import apache
def connectionhandler(conn):
while 1:
conn.write(conn.readline())
return apache.OK
4.5apache-访问Apache内部
apache内部对Python的接口也恰好叫做apache,在mod_python包。提供了连接apache内部的重要功能,比如有用的函数,文档等(request对象也提供访问Apache内部的接口,但不在本节)。
apache模块仅可作为mod_python下的一个脚本来导入,因为它依赖于mod_python内建的_apache模块。最好按照如下导入:
from mod_python import apache
mod_python.apache模块定义了如下函数和对象,更多深入的信息查看Apache文档。
4.5.1函数
log_error(message[,level,server])
Apache函数ap_log_error()的接口,message是错误信息,level是如下值:
APLOG_EMERG
APLOG_ALERT
APLOG_CRIT
APLOG_ERR
APLOG_WARNING
APLOG_NOTICE
APLOG_INFO
APLOG_DEBUG
APLOG_NOERRNO
server是req.server对象的引用。如果没有指定则写入到缺省的日志,否则写入到对应虚拟服务器的日志中。当server没有指定时,日志级别也无效,日志级别是httpd在编译时指定的,经常为warn。
如果拥有请求对象的引用,最好用req.log_error,这样将会存入请求相关信息,如源IP等。
import_module(module_name[,autoreload=1,log=0,path=None])
mod_python的内部高级特性,用于在模块改变时自动重新载入模块。
module_name是模块名,可以包含点的包名。
autoreload标志查看包是否有所改变,如果改变则自动重新载入。
如果log为True则这个事件会记入日志。
path严格指定模块的位置。
例子:
from mod_python import apache
mymodule=apache.import_module('mymodule',log=1)
allow_methods([*args])
用于设置req.allowed。req.allowed是多个标志位的叠加,对应'Allow:'头。可以在返回HTTP_NOT_IMPLEMENTED错误之前设置。参数如下:
M_GET
M_PUT
M_POST
M_DELETE
M_CONNECT
M_OPTIONS
M_TRACE
M_PATCH
M_PROPFIND
M_PROPPATCH
M_MKCOL
M_COPY
M_MOVE
M_LOCK
M_UNLOCK
M_VERSION_CONTROL
M_CHECKOUT
M_UNCHECKOUT
M_CHECKIN
M_UPDATE
M_LABEL
M_REPORT
M_MKWORKSPACE
M_MKACTIVITY
M_BASELINE_CONTROL
M_MERGE
M_INVALID
exists_config_define(name)
确定Apache是否存在一个name配置。比如Apache定义了-DFOOBAR则如下测试为真:
apache.exists_config_define('FOOBAR')
register_cleanup(handler[,data])
注册一个清除行为,等同于req.register_cleanup()或reg.server.register_cleanup(),除非服务器或请求对象不需要。
config_tree()
返回服务器级(server-level)配置树。这些配置不包含.htaccess文件的标志。返回的是副本,修改对配置没有效果。
server_root()
返回配置标志'ServerRoot'的值。
make_table()
一个作废的函数,可用table代替。
mpm_query(code)
允许查询MPM参数变量来处理线程。返回值是如下三种常量之一:
AP_MPMQ_NOT_SUPPORTED=0
指MPM支持线程或子进程
AP_MPMQ_STATIC=1
指MPM正在使用静态守护线程
AP_MPMQ_DYNAMIC=2
指MPM正在使用动态守护线程
code参数是如下值:
AP_MPMQ_MAX_DAEMON_USED=1 最大允许使用的守护线程数量
AP_MPMQ_IS_THREADED=2 MPM是否允许使用线程
AP_MPMQ_IS_FORKED=3 MPM是否可用fork生成子进程
AP_MPMQ_HARD_LIMIT_DAEMONS=4 编译时允许的最大守护进程数量
AP_MPMQ_HARD_LIMIT_THREADS=5 编译时允许最大线程数量
AP_MPMQ_MAX_THREADS=6 threads/child配置允许的最大线程数量
AP_MPMQ_MIN_SPARE_DAEMONS=7 最小剩余守护数
AP_MPMQ_MIN_SPARE_THREADS=8 最小剩余线程数
AP_MPMQ_MAX_SPARE_DAEMONS=9 最大剩余守护数
AP_MPMQ_MAX_SPARE_THREADS=10 最大剩余线程数
AP_MPMQ_MAX_REQUESTS_DAEMON=11每个守护最大允许请求数量
AP_MPMQ_MAX_DAEMONS=12 配置允许的最大守护数量
例如:
if apache.mpm_query(apache.AP_MPMQ_IS_THREADED):
# do something
else:
# do something else
4.5.2表格对象(mp_table)
class table([mapping-or-sequence])
返回一个新的mp_table表格对象。mapping-or-sequence提供了表格的初始化数据。表格对象是对APR表格对象的包装(wrapper)。表格对象的行为(behave)非常像词典(还支持Python2.2以后的in操作符),但是有以下不同:
键和值都必须是字符串
键的查询不区分大小写
允许相同的键,即一个键对应一个列表的值
很多apache的信息存储在表格中,如req.headers_in和req.headers_out。mod_python提供的所有表格对象都是真正(actual)映射(mapping)到apache结构的,所以改变表格的值也会改变apache的表格。
除了(in addition to)像词典一样的行为(behavior)之外,表格对象还支持如下操作:
add(key,val)
这个方法允许使用相同的键,这对重复的头很有用,比如"Set-Cookie:"就需要。这个功能从3.0开始提供。
4.5.3请求对象
请求对象是映射到apache的request_rec结构的对象。当处理器被调用时,会传递唯一个的一个参数就是请求对象。可以为请求对象指定属性,用于在处理器之间通信。如下是方法:
add_common_vars()
间接调用ap_add_common_vars()函数。调用后,req.subprocess_env会包含大量的CGI信息。
add_handler(htype,handler[,dir])
允许动态注册处理器。htype是要添加的处理器的标志,如'PythonHandler',但不可以是过滤器(filter)或连接(connection)。handler是包含模块名或函数名的处理器函数。可选参数dir是需要添加到pythonpath环境变量的路径。如果未指定目录,而且有同类型的处理器,则继承路径,否则用前一路径。如果有PythonPath标志存在,则sys.path将会设置为它。
通过这种方式添加的处理器生存期为这个请求。为同一个类型添加多个处理器是很有用的,不过要注意避免死循环(infinite loop)。
动态注册处理器是一种很有用的技术,比如用于PythonAuthenHandler,可以为不同的认证级别指定不同的权限。如:
if manager:
req.add_handler("PythonHandler","menu::admin")
else:
req.add_handler("PythonHandler","menu::basic")
注意,如果传递了一个无效处理器,则会在寻找处理器时发生异常。
allow_methods(methods[,reset])
添加方法到req.allowed_methods列表。这个列表将会允许传递,在头部指定HTTP_METHOD_NOT_ALLOWED或HTTP_NOT_IMPLEMENTED返回到客户端。注意apache并不会对这些方法起作用。这个列表仅仅用于构造头部。实际的方法处理逻辑在处理器代码中提供。
methods是一个字符串列表,如果reset设置为1,则列表会被清空。
document_root()
返回DocumentRoot设置
get_basic_auth_pw()
返回简单认证时的密码字符串
get_config()
返回包含mod_python配置的表格对象的引用,用于当前请求,除了Python*Handler和PythonOption(可以通过req.get_options()获得)。表格包含键,和值列表。
get_remote_host([type,str_is_ip])
用于查询客户端的DNS名和IP地址,首次调用时查询DNS,其后的查询返回缓存数据。
可选参数type可为如下值:
apache.REMOTE_HOST:查询DNS名,当apache标志HostNameLookups为off时返回None,无法查询。
apache.REMOTE_NAME:缺省值,尽可能返回DNS名,否则返回IP地址字符串。
apache.REMOTE_NOLOOKUP:不执行DNS查询,直接返回IP地址。如果曾经执行过查询,则返回缓存的机器名。
apache.REMOTE_DOUBLE_REV:强制使用double-reverse查询,失败返回None。
如果str_is_ip设为None或未指定,则返回值为DNS名或IP地址的字符串。如果str_is_ip不为None,则返回一个(address,str_is_ip)元组。当str_is_ip为非零则address为IP地址字符串。失败返回None。
get_options()
返回PythonOptions标志的选项表格的引用。
internal_redirect(new_uri)
对请求进行内部重定向。new_uri是字符串。内部重定向将会创建一个新的请求对象,并执行所有的阶段。req.prev包含重定向之前的地址。
log_error(message[,level])
对apache函数ap_log_error()函数的接口。message是日志信息,level是日志级别标志,可为如下值:
APLOG_EMERG
APLOG_ALERT
APLOG_CRIT
APLOG_ERR
APLOG_WARNNING
APLOG_NOTICE
APLOG_INFO
APLOG_DEBUG
APLOG_NOERRNO
如果需要写入日志而不是通过请求对象,可用apache.log_error函数。
meets_conditions()
调用apache函数ap_meets_conditions()返回状态码。如果status是apache.OK,则响应正确。如果不是则简单的返回状态码status。注意req.headers_out将会被优先(prior)设置,并也会影响req.status的值,假如不是返回apache.OK。
例如:
...
r.headers_out['ETag']="1130794f-3774-4584-a4ea-0ab19e684268"
r.headers_out['Last-Modified']='Wed, 23 Feb 2005 00:00:00 GMT'
r.headers_out['Expires']='Mon, 18 Apr 2005 17:30:00 GMT'
status=r.meets_conditons()
if status!=apache.OK:
return status
... 处理响应内容 ...
requires()
返回包含require标志参数的元组。例如如下apache配置:
AuthType Basic
require user joe
require valid-user
函数将会返回('user joe','valid-user')
read([len])
从客户端读取最多len字节的数据,返回字符串数据。如果len为负数(negative)或者缺省(omitted),则读取所有的数据。这个函数也会受到apache配置标志Timeout的影响,如果读取时超时则会终端,并抛出IOError异常。
这个函数依赖于(rely的复数relies)客户端提供的Content-length头。如果未提供这个头,则缺省为0。不正确的Content-length将会导致函数试图读取更多的数据,并且在超时到达之前一直出于阻塞状态。
readline([len])
像read()一样读取到行尾。注意,同HTTP定义一样,大多数客户端的行结束是'\r\n',而不是简单的'\n'。
readlines([sizehint])
读取sizehint字节以内的所有行,并返回行列表。
register_cleanup(callable[,data])
注册一个清理行为。参数callable可以为任何可调用对象,可选参数data可以是任何对象,缺省为None。在每个请求结束的时候,而在请求对象被销毁之前,callable将会被调用,并且传递一个唯一的参数data。
推荐传递请求对象作为data,但是注意,在执行清理行为时,请求对象的处理已经完成,这时再对客户端进行写操作是无意义的(pointless)。
如果在执行清理时遇到(encounter)错误,将会被日志记录。除非直接影响请求处理,否则执行清理中的bug很难辨认(spot)。
如果在清理行为之前,服务器关闭了,则很可能不会被执行。
sendfile(path[,offset,len])
发送path文件到客户端,开始为offset,并发送len字节。offset缺省为0,而len缺省为-1,发送整个(entire)文件。
返回成功发送的字节数,或者在出错时抛出IOError异常。这个函数提供了向客户端发送文件的最有效(most efficient)的方式。
write(string[,flush=1])
将字符串立即(directly)写入客户端,然活清空(flush)缓存,除非flush是0。
flush()
将缓存数据写入客户端,并清空缓存。
set_content_length(len)
设置req.length和"Content-length"头。注意必须在任何发送数据之前设置,比如req.write()之前,否则是无意义的(meaningless)。
请求对象的成员:
connection
这个请求的连接对象。查看后面的详细介绍。只读。
server
请求所属的服务器对象。查看后面的详细介绍。只读。
next
如果是内部重定向,重定向到的请求对象。只读。
prev
如果是内部重定向,来自重定向的请求对象。只读。
main
如果这是一个子请求,指向主请求。只读。
the_request
包含请求的第一行的字符串。只读。
assbackwards
指定HTTP/0.9的简单请求。这意味着响应不包含任何头,仅包含信息主体。用于兼容古老的浏览器。也可以指定这个选项用于在重定向中避免(avoid)发送头。
proxyreq
代理请求。一个apache.PROXYREQ_*值。只读。
header_only
指定HEAD请求的布尔变量,反对GET。只读。
protocol
客户端提供的协议,或者是'HTTP/0.9'。同CGI SERVER_PROTOCOL。只读。
proto_num
整数,协议版本号,比如1.1对应1001。只读。
hostname
字符串,由URI或Host:设置,头。只读。
request_time
长整数,请求发生的时间。只读。
status_line
状态行,如'200 OK'。只读。
status
状态,apache.HTTP_*中的一个值。
method
包含方法的字符串,如'GET','HEAD','POST'。同CGI REQUEST_METHOD。只读。
method_number
方法号,整数。只读。
allowed
整数,允许方法的位逻辑。使用允许结构:头响应HTTP_METHOD_NOT_ALLOWED或HTTP_NOT_IMPLEMENTED。这个字段是apache内部使用的。需要设置则使用req.allow_methods()方法。只读。
allowed_xmethods
元组,允许的扩展(extension)方法。只读。
allowed_methods
元组,允许的方法,使用METHOD_NOT_ALLOWED的关系。这个成员无法被req.allow_methods()方法修改。只读。
sent_bodyct
整数,正文的字节数。只读。
bytes_sent
长整数,已经发送的字节数。只读。
mtime
长整数,资源的修改时间。只读。
chunked
传送大块数据时的编码,布尔值。只读。
range
字符串'Range:'头。只读。
clength
长整数,真正的正文长度。只读。
remaining
长整数,等待读取的数据字节数,只在一个读取操作中有效,只读。
read_length
长整数,已经读取的字节数,只读。
read_body
整数,请求体怎样被读取,只读。
read_chunked
布尔值,读取大块数据的编码。只读。
expecting_100
布尔值,是否客户端需要等待100(HTTP_CONTINUE)响应,只读。
headers_in
客户端发来的头的表格对象。
headers_out
需要发送给客户端的头的表格对象。
err_headers_out
当出错时需要发送的头的表格对象,用于出错时替换headers_out。
subprocess_env
包含CGI信息的表格对象。在需要信息之前需要先调用req.add_common_vars()方法来填充信息。
notes
与请求拥有共同生存期的对象,用于存储各种信息,可以在一个请求的各个处理器之间传递信息。如果确实需要在各个处理器之间传递信息,最好的方式是给请求对象添加成员。
phase
正在处理的阶段,比如'PythonHandler',只读。
interpreter
正在运行的子解释器名。
content_type
字符串,正文类型。mod_python维护一个内部标志(req._content_type_set)来保持content_type的设置。发布器按照如下方式使用这个标志:当content_type没有明确(explicitly)的设置时,它依靠输出的前几个字节来猜测(attempt)。
content_languages
元组。用字符串列表表示正文的语言。
handler
当前处理器的名字。由mod_mime设置,而不是mod_python处理器。大多数情况还是'mod_python',只读。
content_encoding
字符串,正文编码,只读。
vlist_validator
整数,有效变量的列表(如果为负数),只读。
user
获取验证的用户名。同CGI REMOTE_USER,只读。注意,req.get_basic_auth_pw()必须在此之前调用(be called prior)。
ap_auth_type
验证类型,同CGI AUTH_TYPE,只读。
no_cache
布尔值,如果为true则没有缓存,只读。
no_local_copy
布尔值,如果没有本地副本,只读。
unparsed_uri
URI没有经过任何转换,只读。
uri
URI的一部分路径,只读。
filename
请求的文件名字符串。
canonical_filename
字符串,真实的文件名(req.filename是有效的,如果她们不同)。只读。
path_info
字符串,在文件名之后,查询参数之前的部分。同CGI PATH_INFO。
args
字符串,同CGI QUERY_ARGS,只读。
finfo
元组,文件信息结构,类似于POSIX状态,URI指向的文件的信息(mode,ino,dev,nlink,uid,gid,size,atime,mtime,ctime,fname,name)。模块apache定义了FINFO_*变量的常量,可用于存取其中的元素,如:
fname=req.finfo[apache.FINFO_FNAME]
只读。
parsed_uri
元组,被分解开的URI:(scheme,hostinfo,user, password, hostname, port,path,query,fragment)。模块apache定义了URI*常量用于存取元组的元素,如:
fname=req.parsed_uri[apache.URI_PATH]
只读。
used_path_info
当前请求中被拒绝(reject)的path_info,只读。
eos_sent
布尔值,是否发送过EOS,只读。(EOS=end of stream)
4.5.4连接对象(mp_conn)
连接对象是apache的conn_rec结构的映射。
连接对象的方法:
read([length])
从客户端读取最多length字节的数据。在读到任何数据之前将会阻塞。如果length是-1,则读取直到套接字关闭,就是HTTP服务器代码的EXHAUSTIVE模式。仅可在连接处理器中使用。
注意从3.0.3版本开始,这个方法的行为有所改变,在此前是阻塞读取直到length个字节读取完成。
readline([length])
读取一行,最多到length个字节。
仅可在连接处理器中使用。
write(string)
向客户端写入字符串。仅可在连接处理器中使用。
连接对象的成员:
base_server
连接通过的虚拟主机,只读。
local_addr
服务器的地址元组(address,port),只读。
remote_addr
客户端地址元组(address,port),只读。
remote_ip
客户端的IP地址,同CGI REMOTE_ADDR,只读。
remote_host
字符串,客户端的DNS名称,如果没有检查DNS则返回None,如果没有名字则返回""。同CGI REMOTE_HOST,只读。
remote_logname
远程的名字,如果使用了RFC1413识别信号(ident)。同CGI REMOTE_IDENT,只读。
aborted
布尔值,如果连接被中断则为真,只读。
keepalive
整数,1代表连接保持到下一个请求,0代表未定,-1代表严重错误,只读。
double_reverse
整数,1代表执行反向DNS查询,0代表未启用,-1代表查询失败,只读。
keepalives
连接被使用过的次数,只读。
local_ip
本机的服务器IP地址,只读。
local_host
服务器的DNS名称,只读。
id
长整数,一个唯一的连接ID,只读。
notes
表格对象,包含各种与连接具有相同生命周期的信息。
4.5.5过滤器对象(mp_filter)
过滤器对象控制mod_python的输入输出,通常用于提取信息,获取信息并存入过滤器栈。
过滤器的方法:
pass_on()
不处理任何数据,全部放行。
read([length])
从临近过滤器至多读取length个字节的数据,返回字符串数据,如果读取流结束则返回None。过滤器在遇到EOS之后必须关闭。如果未指定length或为负数,则读取当前所有有效数据。
readline([length])
读取一行至多length个字节。
write(string)
将字符串写入临近的过滤器。
flush()
将缓存输出到FLUSH缓存桶。
close()
关闭过滤器,并发送EOS标志。之后关于这个过滤器的IO操作会抛出异常。
disable()
告知mod_python忽略处理器并让数据放行。在mod_python内部使用打印栈跟踪,防止进入死循环。
过滤器成员:
closed
布尔值,指示过滤器是否关闭,只读。
name
字符串,过滤器的注册名,只读。
req
请求对象的引用,只读。
is_input
布尔值,如果是一个输入过滤器则为True,只读。
handler
字符串,配置中的处理器名称,只读。
4.5.6服务器对象(mp_server)
请求对象映射到apache的request_rec结构。服务器结构描述了服务器(也可能是虚拟服务器)如何处理请求。
服务器方法:
get_config()
类似于req.get_config(),但返回指向server->module_config的配置向量。
register_cleanup(request,callable[,data])
注册一个清除行为。类似于req.register_cleanup(),除了在子结束时间以外(字句不通)。这个函数需要一个额外的参数-请求对象。
服务器成员:
defn_name
字符串,配置文件中服务器定义名,只读。
defn_line_number
整数,服务器定义所在配置文件的行号,只读。
server_admin
ServerAdmin标志的值,只读。
server_hostname
ServerName标志的值,同CGI SERVER_NAME,只读。
names
元组,ServerAlias标志的列表,不包含通配符,用wild_names分隔,只读。
wild_names
元组,ServerAlias标志的通配服务器名,只读。
port
整数,TCP/IP端口号,同CGI SERVER_ROOT。在apache2.0开始只是显示为0,可以查看req.connection.local_addr。只读。
error_fname
错误日志文件的文件名,只读。
loglevel
整数,日志级别,只读。
is_virtual
布尔值,如果是一个虚拟服务器则为True,只读。
timeout
整数,Timeout标志的值,只读。
keep_alive_timeout
整数,保持连接的超时时间,只读。
keep_alive_max
每个连接最大允许的请求数,只读。
keep_alive
保持连接?只读。
path
字符串,ServerPath标志的值,服务器路径,只读。
pathlen
整数,路径长度,只读。
limit_req_line
整数,HTTP请求行的长度限制,只读。
limit_req_fieldsize
整数,请求头的长度限制,只读。
limit_req_fields
整数,请求头的字段数量限制,只读。
4.6util-工具箱
util模块提供了类似于cgi模块的多种实用工具。util模块的实现也是非常有效率的,直接调用Apache的API,并且通过环境变量传递信息。
推荐的使用这个模块的方式:
from mod_python import util
参考CGI文档:
http://CGI-spec.golux.com
4.6.1FieldStorage类
通过FieldStorage类传递数据,这个类类似于标准模块cgi FieldStorage。
class FieldStorage(req[,keep_blank_values,strict_parsing])
这个类提供了处理从客户端提交的HTML。req是mod_python请求对象的实例。
可选参数keep_blank_values是一个标志选项,判定是否把从数据编码而得的URL中的空值作为空字符串处理。缺省值为False,代表忽略空值,好像他们没有出现过一样。
可选参数strict_parsing还没有实现。
初始化时,FieldStorage类读取所有从客户端提交的信息。当所有客户端信息处理完成时,只剩下一个对应一个请求的FieldStorage类的实例。你也可以尝试在FieldStorage实例化之前和之后访问客户端数据。
从客户端读取的数据将会被转换成Field对象,每项数据一个字段。从HTML提交的输入类型file,将会被以临时文件的形式稍后提交成Field对象的file属性。
FieldStorage类有一个映射对象接口,可以作为词典来处理。当使用映射时,键名是输入字段名,返回的词典值可能是如下的:
StringField的实例,包含输入值。仅限输入一个值的时候。StringField是str的子类提供了value属性来兼容cgi模块。
Field类的实例,如果输入是一个上传文件。
StringField或者/和Field对象的列表。当输入多个值时,比如HTML标签<select>的元素。
注意:不同于标准库cgi模块的FieldStorage类,一个Field对象只能返回上传文件。其他情况返回StringField的实例。这意味着不需要使用.value属性就可以存取字段值,在大多数时候。
除了普通的映射对象方法(指词典),FieldStorage对象还有如下属性:
list
这是Field对象的列表,对应每个输入。如果具有同名的多个输入,则列表中也会拥有多个对象。
FieldStorage类的方法:
getfirst(name[,default])
总是返回表单数据名name的一个值。如果没有对应的字段则返回default指定的值。缺省返回None如果未指定default。
getlist(name)
返回表单字段name的值列表。如果没有对应字段则返回空列表。即使只有一个值也会返回包含这个值的列表。
4.6.2Field类
class Field()
这个类用于FieldStorage的内部实现。每个Field类实例对应一个HTML表单的输入。
Field实例包含如下属性:
name
输入名
value
输入值。这个属性用于读取数据上传文件的数据。但是注意处理大文件,因为整个文件都会被读入内存。
file
类似文件对象,指向上传的临时文件TemporaryFile实例。(更多信息参考python标准tempfile模块中的TemporaryFile类)
简单的来说,它是一个StringIO对象,所以你可以使用这个属性读取字符串值来更好的代替value属性。
filename
客户端提供的文件名。
type
客户端提交的content-type内容类型。
type_options
真实的内容类型,从客户端提交头的content-type提供的。这是一个词典。
disposition
提交头的content-disposition的第一部分的值。
disposition_options
提交头的content-disposition的第二部分的值,词典。
参考:
RFC1867,HTML表单提交文件,"Form-based File Upload in HTML"
4.6.3其他函数
parse_qs(qs[,keep_blank_values,strict_parsing])
这个函数的功能等同于标准库cgi parse_qs,但是是用C语言写的,运行更快。
转换一个query字符串(URL中附加的提交数据)作为字符串参数。数据以词典返回。词典的键名是query变量的变量名,值是对应变量的值的列表。
可选参数keep_blank_values是一个标志变量指定URL编码中的空值是否作为空字符串处理。True指定空值转换为空白字符串。缺省值是False指定空值被忽略。
注意:strict_parsing参数尚未实现。
parse_qsl(qs[,keep_blank_values,strict_parsing])
这个函数功能等同于标准库cgi parse_qsl,但是是用C语言写的,速度更快。
转换一个query字符串,返回一个列表的数据,变量名和变量值对。
可选参数keep_blank_values和strict_parsing同上。
与上面函数的不同就是返回数据的格式。
redirect(req,location[,permanent=0,text=None])
这是一个可以方便的重定向浏览器到另外一个地址的函数。当permanent为True时,MOVED_PERMANENTLY状态被发送到客户端,或者是MOVED_TEMPORARILY。并发送一段简短的文本告知浏览器,文档已经被移走(当这个罕见的浏览器不支持重定向时);提示文本可以被text参数覆盖。
如果这个函数在响应头已经被发送之后调用,会触发IOError异常。
这个函数触发apache.SERVER_RETURN异常放弃之后的处理。如果不想这样,可以在redirect之外套一个try/except块来捕捉异常。
4.7Cookie-HTTP状态管理
Cookie模块提供了方便的(convenient)的方法来创建,分析,发送和接收HTTP Cookies,按照Netscape的定义。
注意:尽管RFC工作组描述了如何使用Cookie,但是实际上还是只有Netscape原始浏览器支持。而且,很多流行的浏览器还是兼容IETF标准的,而且即便声称RFC兼容的。因此,这个模块支持当前范例,而不是完全RFC兼容的。
在特殊情况下,Netscape与RFC的Cookie有很大的区别,比如路径和域名。Cookie模块忽略这种引入的属性,所以所以所有引入的cookie以Netscape风格的cookie告终,没有属性的定义。
参考:
客户端持久化状态-HTTP Cookies
http://wp.netscape.com/newref/std/cookie_spec.html
RFC 2109,"HTTP状态管理机制"
RFC 2964,"使用HTTP状态管理"
RFC 2965,"HTTP状态管理机制"
HTTP Cookies:标准,秘密与政见
http://arxiv.org/abs/cs.SE/0105018
4.7.1类定义
class Cookie(name,value[,attributes])
这个类用于构造一个单一的cookie名为name,值为value。在Netscape和RFC2109中定义的属性将作为关键字参数。
属性描述了cookie的属性,而他们的字符串将会成为cookie的一部分。Cookie类限定了属性名必须是有效值,如下是可用的属性名:name、 value、version、path、domain、secure、comment、expires、max_age 、commentURL、discard、port、__data__。
__data__属性是一个通用的词典可用于存储任意值,如果需要。在使用Cookie的子集时很重要。
expires属性。按照如下格式解释'Wdy, DD-Mon-YYYY HH:MM:S GMT' (按照每个Netscape cookie的定义),或者按照新纪元开始的秒数(自动转换为GMT时间字符串)。无效的expires值会抛出ValueError异常。
当转换到字符串时,一个Cookie将会被转换到Cookie或Set-Cookie头。
注意:不像Python标准库Cookie类,这个类指定单一cookie,等同于Python标准库中的Morsel。
parse(string)
这是一个类方法,用于从一个cookie字符串创建Cookie实例并传递到响应头的值。转换过程中,属性名将会转换为小写。
因为这是个类方法,所以必须通过类来调用。
这个方法返回Cookie实例的词典,不是单一的Cookie实例。
如下是获取单一Cookie实例的示例:
mycookies=Cookie.parse("spam=eggs; expires=Sat, \
14-Jun-2003 02:42:36 GMT")
spamcookie=mycookies["spam"]
注意:因为这个方法使用词典,所以不可能返回同名cookies。如果需要多个值在同一个cookie中,考虑使用MarshalCookie。
class SignedCookie(name,value,secret[,attributes])
这是Cookie的子类。这个类按照name和value自动创建一个经过HMAC (md5)签名过的Cookie,secret必须是非空字符串。
parse(string,secret)
这个方法同Cookie.parse(),但是cookie要被验证。如果签名的验证失败,则返回Cookie类。
注意:通常检查SignedCookie.parse()的返回类型。如果返回类型是Cookie(而不是SignedCookie),则签名验证失败:
#假设spam是一个签名过的cookie
if type(spam) is not Cookie.SignedCookie:
#签名验证失败的处理
class MarshalCookie(name,value,secret[,attributes])
这是SignedCookie的子类。允许value是任何的集结(marshallable)对象。Python核心(core)类型如字符串,整数,列表等,都是集结类型对象。完整的列表查看marshal模块文档。
转换过程中会检查签名,不正确的签名的cookie无法被集结(unmarshalled)。
4.7.2函数
add_cookie(req,cookie[,value,attributes])
向响应头方便的设置一个cookie。req是mod_python请求对象。如果cookie是一个Cookie类或其子类的实例。则cookie被设置,否则cookie参数必须是一个字符串,用于构造Cookie类的实例。cookie用作名字,value作为值,有效的Cookie属性作为关键字参数。
这个函数同时设置'Cache-Control: no-cache="set-cookie"'头来通知(inform)cookie值不需要缓存。
如下是使用这个函数的一种方法:
c=Cookie.Cookie('spam','eggs',expires=time.time()+300)
Cookie.add_cookie(req,c)
如下是另一个例子:
Cookie.add_cookie(req,'spam','eggs',expires=time.time()+\
300)
get_cookies(req[,Class,data])
从输入头获取(retrieving)cookie的方法。req是mod_python请求对象。Class是一个拥有parse()方法的用于转换成的cookie类,缺省是Cookie类。Data可以是任何关键字参数,将会被传递到parse()方法。对SignedCookie和MarshalCookie类等需要secret参数的parse()方法非常有用。
4.7.3例子
如下例子设置一个持续(expires)300秒的cookie:
from mod_python import Cookie,apache
import time
def handler(req):
cookie=Cookie.Cookie('eggs','spam')
cookie.expires=time.time()+300
Cookie.add_cookie(req,cookie)
req.write('此响应包含cookie!\n')
return apache.OK
如下例子检查输入的集结cookie并显示到客户端。如果输入没有cookie则设置一个cookie。这个例子使用'secret007'作为HMAC签名。
from mod_python import apache,Cookie
def handler(req):
cookies=Cookie.get_cookies(req,Cookie.MarshalCookie,\
secret='secret007')
if cookies.has_key('spam'):
spamcookie=cookies['spam']
req.write('Great, a spam cookie was found: %s\n'\
% str(spamcookie))
if type(spamcookie) is Cookie.MarshalCookie:
req.write('Here is what it looks like decoded'\
+':%s=%s\n'%(spamcookie.name,\
spamcookie.value))
else:
req.write('WARNING: The cookie found is not a \
MarshalCookie, it may have been tapered\
with!')
else:
#MarshalCookie允许值为任何集结对象
value={'egg_count':32, 'color':'white'}
Cookie.add_cookie(req,Cookie.MarshalCookie('spam',\
value,'secret007'))
req.write('Spam cookie not found, but we just\
set one!\n')
return apache.OK
4.8Session-对话管理
Session模块提供了跨越请求的持久化对象。
模块包含BaseSession类,但不可以直接使用。DbmSession类,使用dbm来存储对话。FileSession类,使用单独的文件存储对话。
BaseSession类也提供对话锁,支持进程和线程。锁定使用APR的global_mutexes函数。一定数量的锁在启动时预创建,互斥数用对话ID的hash()计算。因此就算两个对话具有不同的ID却有相同的哈希值,但仅有的联系(implication)是两个对话无法在同时被锁定。
4.8.1类
Session(req[,sid,secret,timeout,lock])
此函数拥有与BaseSession相同的参数。
这个函数返回一个缺省的对话实例。对话类可被用于PythonOption session value,这里的value可以是DbmSession、MemorySession或FileSession。自定义的对话类暂时还不被支持。
如果PythonOption session没有找到,则函数查询MPM和基于它的返回一个DbmSession或MemorySession实例。
MemorySession类将被用于MPM支持线程,但是不支持fork的情况,比如Windows,或者用于支持线程,fork,但是只允许一个进程的情况,比如MPM可以配置成这种。在其他所有的情况,使用DbmSession。
class BaseSession(req[,sid,secret,timeout,lock])
这个类意味着存储其他类并实现对话的存储机制。req是mod_python的请求对象的引用,是必须的。
BaseSession是dict类的子类。数据按照词典存取。
sid是一个可选的对话ID;如果提供则对话必须存在,忽略则会创建一个新的对话和新的sid。如果sid未提供,对象会在cookie中查找对话ID。如果找到了,但不是先前所指的,还会创建一个新的sid。判断是否是新创建的对话,调用is_new()方法。
Cookie有一个path属性用于比较DocumentRoot和目录在PythonHandler标志的作用中(病句,我自己也不懂)。比如,如果文档根目录是'/a/b/c',且PythonHandler指定为'/a/b/c/d/e',则路径会被设置为'/d/e'。也可以强制指定path使用ApplicationPath选项。即在服务器配置中的'PythonOption ApplicationPath /my/path选项。
当提供了secret参数时,BaseSession会使用SignedCookie来存储不可伪造(fake)的对话ID。缺省是简单的(plain)使用Cookie,假如没有被签名,则对话ID将会很难处理。
一个对话在超过timeout时间之后会失效而无法处理,缺省是30分钟。尝试装入期满(expired)的对话将会导致一个新的对话的产生。
lock参数缺省为1指定是否允许使用锁。当锁上时,只允许一个拥有相同对话ID的对话对象实例存在。
一个对话在刚创建时拥有"new"状态,而不通过cookie或sid参数来传递。
is_new()
如果返回1,则对话是刚刚创建的。在发生超时或不存在的sid时,也会新创建一个对话。使用这个方法来测试对话是否为新是很有用的:
sess=Session(req)
if sess.is_new():
#重定向到登录
util.redirect(req,'http://www.mysite.com/login')
id()
返回对话ID
created()
返回对话的创建时间,按照从新纪元(epoch)开始的秒数。
last_accessed()
返回上次存取时间,按照从新纪元开始的秒数。
timeout()
返回以秒为单位的超时间隔(timeout interval)。
set_timeout(secs)
设置超时时间为secs秒。
invalidate()
删除对话,并发送响应头要求删除客户端的对话相关cookie。
load()
从存储装入对话的值。
save()
将对话值写入存储。
delete()
从存储中删除对话。
init_lock()
初始化对话锁。无需每次都调用这个方法,仅在子类打算(intend)使用互斥锁(alternative lock)时使用。
lock()
锁住对话。如果对话已经被其他线程/进程锁住,则等待到锁被释放(release)时。锁是被自动控制的,而无需调用这个方法,缺省。这个方法也注册一个清理行为,在请求处理结束时解锁一个对话。
unlock()
解锁对话。同lock()当自动处理时(缺省),无需手工调用。
cleanup()
这个方法供子类实现对话的存储清理机制(比如删除超时对话等)。它将会被随机调用,调用的机会由CLEANUP_CHANGE变量,一个Session模块的成员来控制,缺省值为1000。这意味着被安排好(be ordered)的随机清理行为发生概率在1/1000的机会(chance)。子类实现这个方法之后,将不会按照时间强制的清理,但将会需要用req.register_cleanup注册清理行为来代替,执行请求之后的处理。
class DbmSession(req[,dbm,sid,secret,dbmtype,timeout,lock])
这个类提供了使用dbm文件来存储对话的方法,dbm存储速度很快,且大多数dbm实现使用内存映射文件来提供更高的存取速度,其响应性能接近使用共享内存。
dbm是dbm文件名,且这个文件必须可被httpd进程写入。这个文件在服务器进程停止之后不会被删除,一个好处是服务器重启后对话仍然存在(survive)。缺省时对话信息保存在'mp_sess.dbm'文件中。也可在配置标志中指定路径PythonOption session_directory标志。路径和文件名可以在如下设置中重载PythonOption session_dbm filename。
当使用Python的anydbm模块实现时,在多数系统中缺省使用dbhash模块。如果需要特并的指定dbm的实现,如gdbm等,可以传递dbmtype参数。
注意这个类的使用不是跨平台的。具有最好的跨平台兼容性(compatibility)的,还是使用Session()来创建对话。
class FileSession(req[,sid,secret,timeout,lock,
fast_cleanup,verify_cleanup])
这是在版本3.2.0开始实现的。
此类在文件中存储对话数据,是BaseSession的子类。
对话数据存储在特定文件中。这些文件在服务器停止时不会被删除,所以对话可以在服务器重启之间继续有效。对话文件保存在由tempfile.gettempdir()标准库函数返回的临时目录下的mp_sess目录下。另一个可选路径是使用PythonOption session_directory /path/to/dir 来设置,并且这个目录必须存在且可用apache进程读写。
过期的对话文件将会被清理机制按周期(periodically)删除。清除行为受到fast_cleanup和verify_cleanup参数控制,也可以用PythonOption session_grace_period和PythonOption session_cleanup_time_limit来控制。
fast_cleanup:布尔值,用于开启FileSession清理优化。缺省是True并将在清除大量的对话文件时减少清理时间。当fast_cleanup为True时,文件的修改时间用于决定(determine)被删除的候选名单(candidate)。如果(current_time - file_modification_time) > (timeout + grace_period),则文件将会加入删除候选名单。如果verify_cleanup为False,不需要深入的检查而直接删除。
如果fast_cleanup为False,对话文件将会被非酸洗(unpickled?)并且超时(timeout)被用于决定对话是否要加入删除候选名单。fast_cleanup=Fasle意味着(imply)verify_cleanup=True。
超时(timeout)被用于计算请求的对话中哪些到达了超时,以用于调用filesession_cleanup。如果对话对象没有相同的超时,或者手动设定了特定(particular)对话的超时,需要设置verify_cleanup=True。
fast_cleanup的值也可以用PythonOption session_fast_cleanup来设置。
verify_cleanup:布尔值,用于优化FileSession的清理行为。缺省为True。如果为True,则正在被考虑的对话文件将会参考超时值决定是否要删除。当为False时当前对话的超时值将被用于决定对话是否超期(expire)。在这种情况下,对话数据被读取,这将会在含有大量对话文件时提高性能,或者在每个对话存取大量数据时。这将会导致当前对话拥有一个独一无二的超时值时被删除(什么鬼话?)。这个值也可用PythonOption session_verify_cleanup来设置。
PythonOption session_cleanup_time_limit[value]:整数值秒数。缺省为2秒。对话的清理潜在的(potentially)耗费大量耗费CPU和硬盘读取时间,与对话文件数量和数据量有关。为了避免(avoid)服务器超负荷(overload),每次filesession_cleanup运行时都在达到session_cleanup_time_limit时进行。每次清理将会从上次清理的最后开始。设置这个值为0将会关闭这个功能,在每次调用filesession_cleanup时会完成这个功能。
PythonOption session_grace_period[value]:整数值秒数。缺省为240秒。用于在测试对话文件时增加超时时间。一个对话正在被其他请求读取时正巧发生清理行为的可能性很小。有可能在一个请求要存入对话文件时而另一个请求要立即删除。为了避免这种竞争状态,对话允许在确定清理之前保留一个grace_period周期。这个周期的时间比任何请求完成的时间都要长,但一般还是短于1秒,以确保对话在被删除之前不会出错。缺省值适合(sufficient)大多数应用。
class MemorySession(req[,sid,secret,timeout,lock])
这个类提供在全局词典中保存对话。提供更好的性能,但是需要多进程配置的支持,也会为每个对话耗费(consume)一定的内存。
注意使用这个类并不是跨平台的。为了跨平台,最好还是使用Session()函数来创建对话。
4.8.2例子
如下是记录点击次数的简单例子(demonstrate)。
from mod_python import Session
def handler(req):
session=Session.Session(req)
try:
session['hits']+=1
except:
session['hits']=1
session.save()
req.content_type='text/plain'
req.write('Hits: %d\n' % session['hits'])
return apache.OK
4.9psp-Python服务器端页面
psp模块提供一种从包含Python代码的文本(包含,但是不限于HTML文档),转换到适用于mod_python处理器的Python代码。提供一种类似于ASP,JSP等的动态内容嵌入技术。
psp的转换器使用C语言书写(使用flex),所以运行速度很快。
查看6.2节"PSP处理器"了解更多PSP的信息。
在文档内,Python代码需要被'<%'和'%>'括起来。Python表达式需要附上(enclosed)'<%='和'%>'。标志需要被'<%@'和'%>'括起来。作为代码一部分的注释需用'<%--'和'--%>'括起来。
如下是原始的PSP页面用于示例(demonstrate)同时包含代码和表达式的嵌入HTML文档的例子:
<html>
<%
import time
%>
Hello world, the time is: <%=time.strftime("%Y-%m-%d,\
%H:%M:%S")%>
</html>
在内部,PSP转换器翻译如上页面到如下的Python代码:
req.write("""<html>
""")
import time
req.write("""
Hello world, the time is: """)
req.write(str(time.strftime("%Y-%m-%d, %H:%M:%S")))
req.write("""</html>
""")
这些代码将会在处理器的返回中显示:'Hello world, the time is: '加上当前时间。
Python代码可以被用于输出部分页面条件(conditionally)循环。代码将被显示成锯齿状。在下一段以锯齿括起的Python代码开始时,循环将被结束。哪怕这段代码只是一段注释。
如下是例子:
<html>
<%
for n in range(3):
# this indent will persist
%>
<p>This paragraph will be
repeated 3 times.</p>
<%
# this line will cause the block to end
%>
This line will only be shown once.<br>
</html>
如下是内部自动生成的Python代码:
req.write("""<html>
""")
for n in range(3):
# this indent will persist
req.write("""
<p>This paragraph will be
repeated 3 times.</p>
""")
# this line will cause the block to end
req.write("""
This line will only be shown onece.<br>
</html>
""")
转换器也会聪明的在以冒号结尾的Python代码后,识别新的'<%%>'对来结束重置代码快,跳出循环。如下页面与如上的最终效果相同:
<html>
<%
for n in range(3):
%>
<p>This paragraph will be
repeated 3 times.</p>
<%
%>
This line will only be shown once.<br>
</html>
无论如上代码如何混淆(confuse),这样用注释来结束代码块还是相当推荐的好习惯。
现在支持的唯一一个标志是include,如下用法:
<%@ include file="/file/to/include"%>
函数parse()会被按照dir参数调用,并把文件转换到绝对路径。
class PSP(req[,filename,string,vars])
这个类声明(represent)了一个PSP对象。
req是请求对象,filename和string是可选参数指定PSP源代码。但是只有一个会生效,如果没有指定则用req.filename来指定filename。
vars是一个全局变量词典。传递run()方法覆盖这里的vars参数。
这个类被PSP处理器在内部调用,但也可以被模板工具使用。
当使用文件作为源时,代码对象将会返回缓存中的文件对象和文件修改时间。缓存是作用于全局的Python解释器。所以除非文件修改时间改变了,文件无需重新编译和解释。
缓存限制到512个页面,依赖于可用内存总量。如果与内存有关,可以选择使用dbm文件缓存。作者简单的测试表明,使用bsd db之后大约性能慢了20%。也需要测试anydbm在你的平台上的缺省实现对大小的限制是否合适。dbm缓存允许通过PSPDbmCache这个PythonOption,例如:
PythonOption PSPDbmCache ''/tmp/pspcache.dbm''
注意,dbm缓存在服务器重启时是不会被删除的。
不像使用文件,从字符串来的代码对象只会缓存在内存中。无需选项来缓存dbm文件。
run([vars])
这个方法将会执行代码(由对象初始化时转换和编译过的PSP代码)。可选参数vars是一个用于传送全局变量的词典。另外,PSP代码可以使用全局变量如req,psp,session和form。一个对话将被创建,并可以在PSP代码中使用session变量(PSP代码检查co_names代码对象来做决定)。记住在处理session和打开对话锁时会有一些阻塞。类似的,mod_python的FieldStorage对象将会实例化为form对象。PSPInstance类会传递psp对象作为实例。
display_code()
返回HTML格式的PSP原始代码。如下是PSP的模板机制:
模板文件:
<html>
<!-- This is a simple psp template called template.html-->
<h1>Hello, <%=what%>!</h1>
</html>
处理器代码如下:
from mod_python import apache,psp
def handler(req):
template=psp.PSP(req,filename='template.html')
template.run({'what':'world'})
return apache.OK
class PSPInstance()
PSP代码中全局变量psp对象所属的类。对象由内部负责实例化,所以__init__接口是没有文档的。
set_error_page(filename)
设置一个psp页面来处理抛出的异常。如果用绝对路径,将会添加到文档根目录,或者被假设为文件与当前页面在同一路径下。错误页会收到一个附加的变量exception,是一个三元素元组,由sys.exc_info()返回。
apply_data(object[,**kw])
这个方法调用可执行对象object,传递表单参数data作为关键字参数,并返回结果。
redirect(location[,permanent=0])
这个方法重定向浏览器到位置location。如果permanent是True,则MOVED_PERMANENTLY将会发送(不同于MOVED_TEMPORARILY)到客户端。
注意:重定向仅可在任何数据发送到客户端之前调用,调用它的Python代码必须在整个页面的顶端调用。否则会发生IOError异常。
例子:
<%
# note that the '<' above is the first byte of the page!
psp.redirect('http://www.modpython.org')
%>
另外,psp模块还提供如下函数:
parse(filename[,dir])
此函数打开文件filename,读取和转换后返回Python代码。
如果制定了dir参数,最终(ultimate)用于转换的文件名由dir和filename构成。并且include标志也会被指定为相关路径。注意这只是简单的串连,dir之后不会自动加入路径分隔符。
parsestring(string)
同上,只是转换字符串string中的psp代码到Python代码。
完成...
第五章 apache配置标记
5.1请求处理器
5.1.1Python*Handler标记语法
所有的请求处理器标记具有如下语法:
Python*Handler handler [handler ...] [|.ext [.ext ...]]
handler是一个可接收请求对象的可调用对象,.ext是文件扩展名。
多个处理器可以写在同一行,并按顺序调用,处理器列表中的每个处理器都会执行。如果序列中某个处理器返回apache.OK以外的东西则处理序列会被中止。
在处理器列表后可用"|"分割开其后的文件扩展名列表,这将会严格的按照扩展名来执行。这个功能用于穿越各个阶段的处理器工作。
handler有如下语法:
module[::object]
module是完整的模块名(允许含有句点的包符号(notation)),可选的object是模块内的对象。
对象也可以包含句点,并且从左至右分解。在判别(resolution)过程中,如果mod_python遇到(encounter)了<class>类型的对象,将会试图实例化并且传递一个请求对象作为唯一的参数。
如果没有指定对象,将会使用缺省的将处理器标志全部转换成小写,之后去除开头的"python"单词,成为调用的对象名。比如PythonAuthenHandler将是authenhandler。
例子:
PythonAuthzHandler mypackage.mymodule::checkallowed
更多关于处理器的信息,参考具体的处理器。
细节:选用"::"是为了提高性能。当使用Python模块内部对象时,首先导入模块。按照句点'.'简单的分隔,将会复杂的分析每一个可能的对象,包括包、模块和类等等。使用"::"符号(公认的(adminttedly)非Python风格符号),将会直接指定对象来获得较高的性能。
5.1.2PythonPostReadRequestHandler
语法:Python*Handler Syntax
上下文:server config, virtual host
Override:非None
模块:mod_python.c
这个处理器在读取请求但是没有处理任何阶段之前调用。用于判别提交头字段信息。
注意:当处理这个阶段的请求时,URI还没有被翻译成路径名,所以不可以在apache的<Directory>、<Location>、<File>配置标志和.htaccess文件中指定。只可以在主配置文件中指定,代码也是在主解释器中执行的。因为这个阶段发生在判别内容类型之前(比如不区分一个Python程序和.gif图片),所以Python例程将会处理整个服务器上所有的请求,所以应该优先(priority)考虑性能的问题。
下面的处理器被apache按照阶段处理。
5.1.3PythonTransHandler
语法:Python*Handler Syntax
上下文:server config, virtual host
Override:非None
模块:mod_python.c
这个处理器给出从URI翻译到实际(actual)文件名的机会(opportunity),发生在服务器缺省规则(如目录别名等)之前。
注意:此阶段发生时URI还没有转换成路径名,所以不可以用在apache的<Directory>、<Location>、<File>标志和.htaccess文件中。只可以放在主配置文件中,代码也是运行在主解释器中的。
5.1.4PythonHeaderParserHandler
语法:Python*Handler Syntax
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
这个处理器在请求头解析时给出一定的处理,早于其他处理序列。
5.1.5PythonInitHandler
语法:Python*Handler Syntax
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
这是在请求处理阶段第一个调用的处理器。在.htaccess和目录中都可以使用。
这个处理器是两个不同的处理器的别名。当写入主配置文件时而不在任何目录标签之中时,它代指PostReadRequestHandler。当在某个目录中时(不允许使用PostReadRequestHandler的地方),它代指PythonHeaderParserHandler。
这个主意是从mod_perl中借用的。
5.1.6PythonAccessHandler
语法:Python*Handler Syntax
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
这个例程用于检查特定模块的限制(module-specific restriction)对某些资源的访问。
比如,可以用于严格的限制某些IP地址的访问,可以返回HTTP_FORBIDDEN或者其他禁止访问。
5.1.7PythonAuthenHandler
语法:Python*Handler Syntax
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
用于检查用户请求的认证信息,可以查询数据库中的信息决定认证结果。
使用req.user获取(obtain)用户名。用req.get_basic_auth_pw()获取密码。
返回apache.OK代表认证通过。返回apache.HTTP_UNAUTHORIZED将会使大多数浏览器弹出密码提示框。返回apache.HTTP_FORBIDDEN将会告知浏览器无需再显示密码框并显示验证失败。HTTP_FORBIDDEN也可用于验证成功时,但是这个用户无权访问特定URL。
一个验证处理器的例子:
def authenhandler(req):
pw=req.get_basic_auth_pw()
user=req.user
if user=="spam" and pw=="eggs":
return apache.OK
else:
return apache.HTTP_UNAUTHORIZED
注意:req.get_basic_auth_pw()的调用必须优先于req.user值。因为在调用req.get_basic_auth.pw()之前apache不会解码验证信息。
5.1.8PythonAuthzHandler
语法:Python*Handler Syntax
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
这个模块在PythonAuthenHandler之后调用,用于检查特定用户是否有权访问特定资源。在PythonAuthenHandler之前执行会出错。(But more often than not it is done right in the PythonAuthenHandler)。
5.1.9PythonTypeHandler
语法:Python*Handler Syntax
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
这个例程用于确定(determine)和设置各种(various)文档类型信息,有如Content-Type(经过req->content_type),语言,及其他等(et cetera)。
5.1.10PythonFixupHandler
语法:Python*Handler Syntax
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
这个例程用于修正头字段。在所有内容处理器(content-handler)之前调用。
5.1.11PythonHandler
语法:Python*Handler Syntax
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
主要的请求处理器。很多应用只提供这一个处理器即可。
5.1.12PythonLogHandler
语法:Python*Handler Syntax
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
这个例程在特定模块写入日志时调用。
5.1.13PythonCleanupHandler
语法:Python*Handler Syntax
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
最后一个处理器,在apache销毁请求对象时调用。
不像其他处理器,这个处理器的返回值会被忽略。任何错误都被直接记录到日志,并且在即使PythonDebug为On时,也不会把错误发送到客户端。
这个处理器不能作为req.add_handler()函数的有效参数。动态注册清理行为使用req.register_cleanup()。
一旦清理开始了,就没有必要注册更多。在这个处理器中调用req.register_cleanup()是没有用的。
通过这个标志注册的清理行为运行在通过req.register_cleanup()注册的清理之后。
5.2过滤器
5.2.1PythonInputFilter
语法:PythonInputFilter handler name
上下文:server config
模块:mod_python.c
注册一个名为name的输入过滤器handler。handler是模块名,并可以使用"::"加上可调用对象。如果可调用对象省略(omit)了,则默认为"inputfilter"。name是过滤器的注册名,按照惯例(by convention)过滤器名应该在所有的cap(?)中。
激活过滤器需要用AddOutputFilter标志。
5.3连接处理器
5.3.1PythonConnectionHandler
语法:PythonConnectionHandler handler
上下文:server config
模块:mod_python.c
用handler来处理连接。handler将会传递唯一的连接对象作为参数。
handler是一个模块并允许使用"::"指定可调用对象。如果可调用对象省略则缺省为"connectionhandler"。
5.4其他标志
5.4.1PythonEnablePdb
语法:PythonEnablePdb {On,Off}
缺省:PythonEnablePdb Off
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
当为On时,mod_python将会在Python调试器pdb中运行,使用pdb.runcall()函数。
因为PDB是交互式工具,所以从命令行启动httpd加上参数-DONE_PROCESS来使用这个标志。当进入某个处理器时,将会从pdb提示符看到代码和测试变量。
5.4.2PythonDebug
语法:PythonDebug {On,Off}
缺省:PythonDebug Off
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
一般时,Python无法处理的错误将会写入日志。当指定PythonDebug On标志时,输出会发送到客户端(同日志一样),除非错误是写入时的IOError,那种情况还是写入到错误日志。
这个标志在开发时非常有用。但是不要在最终运行环境中使用,会暴露一些信息影响安全。
5.4.3PythonImport
语法:PythonImport module interpreter_name
上下文:server config
模块:mod_python.c
告知服务器给特定解释器导入特定模块。用于初始化任务,而非在请求处理时的事情,比如初始化数据库连接。
导入发生在子进程初始化(initialization)时,所以模块是在每个子进程产生(spawn)时真正的导入。
注意:在导入时,配置还没有被完全读取,所以其他标志包括PythonInterpreter等因为还没有导入而不会产生效果(effect)。出于这个限制(limitation),解释器必须知道,并必须对比名字(name)来确保并发的请求依赖于(rely)这个操作的执行。如果不确定所在解释器的名字,可以从请求对象的interpreter成员获得。
参考多解释器。
5.4.4PythonInterpPerDirectory
语法:PythonInterpPerDirectory {On,Off}
缺省:PythonInterpPerDirectory Off
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
指示(instruct)mod_python使用请求文件名的路径作为子解释器的名字,而不是使用服务器名字。这意味着不同目录将会执行不同的解释器,而不是象缺省的规则(policy)那样所有的脚本都在同一个虚拟服务器上的相同子解释器上运行。
例如,假设(assume)有个目录'/dir/subdir','dir'含有.htaccess文件并且指定了PythonHandler标记。'dir/subdir'并没有.htaccess文件。缺省时,在'/dir'和'dir/subdir'中执行的解释器相同,通过同一个虚拟服务器。含有PythonInterpPerDirectory标志时,将会启动两个不同的解释器,对应每个目录。
注意:在在请求处理的早期阶段,如PostReadRequestHandler和TransHandler,由于URI还没有被解析出来。所以即便是有PythonInterpPerDirectory On标志,所有的代码也都是在主解释器中执行。这也许并不是你想的,但是很不幸没有其他办法。
参考:
4.1节,多解释器
5.4.5PythonInterpPerDirective
语法:PythonInterpPerDirective {On,Off}
缺省:PythonInterpPerDirective Off
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
指定mod_python按照正在作用的Python*Handler标志来命名子解释器。
例如,假设目录'dir/subdir'。'dir'含有.htaccess文件并有PythonHandler标志。'dir/subdir'含有另外的.htaccess文件,并有另外的PythonHandler。缺省时,在'/dir'和'/dir/subdir'中的脚本使用相同的解释器来执行,并在相同的虚拟服务器中。打开了PythonInterpPerDirective标签后,每个标签使用不同的解释器。
参考:4.1节,多解释器
5.4.6PythonInterpreter
语法:PythonInterpreter name
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
强制mod_python给解释器命名为name,覆盖缺省的行为和PythonInterpPerDirectory和PythonInterpPerDirective标签的行为。
这个标签用于强制指定使用不同的子解释器。当用在DocumentRoot时,强制整个服务器使用同一个子解释器。
参考:4.1节,多解释器
5.4.7PythonHandlerModule
语法:PythonHandlerModule module
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
可用于在Python*Handler标签中任选其一(alternative)。指定的模块将会被搜索缺省的函数名,如果找到了则执行。
例如:
PythonAuthenHandler mymodule
PythonHandler mymodule
PythonLogHandler mymodule
可以被简写为:
PythonHandlerModule mymodule
5.4.8PythonAutoReload
语法:PythonAutoReload {On,Off}
缺省:PythonAutoReload On
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
如果设为Off,指定mod_python不需要检查模块文件的修改时间。
缺省时,mod_python检查模块的时间签并决定是否需要重新载入模块,当模块文件的时间签比导入或重新导入的时间签要新的时候会重新导入。这个确保更新的模块会重新导入,这样在每次更改的时候无需重启服务器。
禁用这个功能适用于发布环境,会节省很多时间并有微小的性能提升。
5.4.9PythonOptimize
语法:PythonOptimize {On,Off}
缺省:PythonOptimize Off
上下文:server config
模块:mod_python.c
允许Python优化,同Python -O选项。
5.4.10PythonOption
语法:PythonOption key [value]
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
指定键值对用于在req.get_options()中获得。用来传递配置信息非常有效。如果值被省略,则是用于从配置中移出对应键。
5.4.11PythonPath
语法:PythonPath path
上下文:server config, virtual host, directory, htaccess
Override:非None
模块:mod_python.c
用于设置PythonPath。这个路径必须使用Python的列表符号(notation),比如:
PythonPath "['/usr/local/lib/python2.0','/somedir/dir']"
这里指定的路径会替换路径(path),而不是添加。如果需要只是添加,则用如下:
PythonPath "sys.path+['/mydir']"
mod_python会尝试最小化PythonPath标签指定的目录数量,因为这是非常影响性能的,尤其是在'.htaccess'文件中指定时,每次解析都需要重新导入。mod_python将会记住PythonPath的参数,每次只是比对是否有更改,只是在不同的时候才重新导入。因为这样,在代码改变时不需要依赖于这种方法来重新装入PythonPath的值。
注意:这个标志不能用于安全的量度,因为Python Path的值可以很容易的在脚本中更改。
完成...
第六章 标准处理器
6.1发布处理器
发布处理器是一种快速开发应用程序,避免(avoid)直接书写处理器的好方法。收到Zope中ZPublisher的启发。
6.1.1简介
使用处理器需要书写如下配置:
<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
则如下路径显示"I am saying NOTHING"
http://www.mysite.com/hello.py/say
如下路径显示"I am saying hello"
http://www.mysite.com/hello.py/say?what=hello
6.1.2发布处理器规则(algorithm)
发布处理器从URI映射到Python变量或可执行对象,返回调用的返回值。
Traversal(?)
发布处理器定位和导入URI指定的模块。模块的位置按照req.filename属性指定。在到如之前,如果有扩展名则丢弃。
如果req.filename为空,则模块名缺省为index。
一旦模块成功导入,则URI剩余的部分成为query字段的开始(a.k.a also known as也叫做PATH_INFO),来在模块里查找对象。发布处理器切断路径,从左到右,每个元素一次的映射到Python对象。
如果URL里未指定path_info,则发布处理器使用缺省的'index'。如果最后一个元素是模块里的一个对象,且它领先与一个目录(也就是没有给出模块名),模块名也会默认到'index'。
当如下情况发生时,转换结束,并返回HTTP_NOT_FOUND到客户端:
任何一个横断(traverse?)对象名以下划线开头。使用下划线保护对象不被web存取。
模块冲突(encounter),发布的对象因为安全原因不可以成为模块。
如果路径中找不到对象,则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()"
然后:
http://www.mysite.com/index/index 显示"We are in index()"
http://www.mysite.com/index/ 显示"We are in index()"
http://www.mysite.com/index/hello 显示"We are in hello()"
http://www.mysite.com/hello 显示"We are in hello()"
http://www.mysite.com/spam 显示'404 Not Found'
如果找到了目标对象,且它是可调用对象而不是一个类,则发布处理器会提取一个对象预期的列表。这个列表包含从HTML表单提交数据中的字段。字段值和字段名以字符串匹配并传入可调用对象的参数。任何没有匹配的名字都被默认丢弃,除非目标对象有一个**kwargs风格的参数。未匹配参数传入这个词典中。
如果目标对象不是可调用的或者是一个类,则会返回它创建的字符串到客户端。
发布处理器提供了对模块和函数简单的存取控制。
在每个处理阶段,发布处理器按顺序检查__auth__和__access__属性,还有__auth_realm__属性。
如果__auth__是可调用的,将会被传入三个参数:一个请求对象,字符串包含用户名和密码。如果返回值是False,则将发送HTTP_UNAUTHORIZED到客户端,一般来说还会弹出一个输入密码的对话框。
如果__auth__是一个词典,则用户名和密码按照键值对在词典中尝试匹配。如果不匹配则返回HTTP_UNAUTHORIZED。注意,如果明文将密码存入源代码中是不安全的。
__auth__也可以是常量。在这种情况下,如果为False(也同None、0、""、等等)则返回HTTP_UNAUTHORIZED。
如果存在__auth_realm__字符串,它将会被发送到客户端作为认证区域的提示,通常还显示在密码提示框中。
如果__access__存在并可调用,则传入两个参数:请求对象和用户名字符串。如果返回值为False则发送HTTP_FORBIDDEN到客户端。
如果__access__是一个列表,则用户名将会被尝试匹配。如果用户名不再列表中,则返回HTTP_FORBIDDEN。
类似的__auth__、__access__也可以是常量。
在下例中,仅用户'eggs'使用密码'spam'可以访问函数hello:
__auth_realm__="Members only"
def __auth__(req,user,passwd):
if user=="eggs" and passwd=="spam":
return 1
else:
return 0
def __access__(req,user):
if user=="eggs":
return 1
else:
return 0
def hello(req):
return "hello"
这里还有一个具有相同功能,但使用类似技术:
__auth__realm__="Members only"
__auth__={"eggs":"spam"}
__access__=["eggs"]
def hello(req):
return "hello"
如果函数无法被指定属性,为了保护函数,也可以将__auth__和__access__函数放入这个函数中定义,如:
def sensitive(req): #受保护函数
def __auth__(req,user,password):
if user=="spam" and password=="eggs":
return 1
else:
return 0
#受保护函数的函数体
return 'sensitive information'
注意,这种技术也可用于__auth__或__access__是常量的情况,但是不可以是词典或列表。
__auth__和__access__机制独立于PythonAuthenHandler而存在。它可能使用,例如认证处理器。然后__access__列表用于已经验证过的用户访问具体函数。
注意:为了让mod_python可以访问__auth__,包含它的模块必须首先导入。因此,任何模块级(module-level)的代码在导入__auth__之前,即时__auth__为False,也是可以执行的。为了真正的保护模块,使用另外一种认证技术,比如apache的mod_auth模块或者用mod_python的PythonAuthenHandler处理器。
6.1.3表单数据
在处理参数结束后,发布处理器将会创建一个FieldStorage类的实例。实例的引用存入请求对象的form属性中。
每次请求只能拥有一个FieldStorage类的实例,在使用发布处理器的时候不可以自己实例化FieldStorage,只能使用Request.form代替。
6.2PSP处理器
PSP处理器是处理含有mod_python.psp模块中psp类的。要使用它,只需简单的在httpd的配置中加入:
AddHandler mod_python .psp
PythonHandler mod_python.psp
更多的PSP语法参见4.9节。
如果打开了服务器选项PythonDebug为On,则在URL的末尾添加下划线"_"会显示响应的PSP源代码和对应的Python代码。这对调试是非常有用的。
注意:开启调试选项时,远程用户同样有权查看PSP源码。
6.3CGI处理器
CGI处理器用来在mod_python中模拟CGI环境。
但是注意,在Python级别并不能真正的模拟出一个完整的CGI环境。stdin和stdout是由sys.stdin和sys.stdout来替换(substitute)的。环境变量也被替换为词典。一点类似(implication)是外部程序通过环境变量os.system交互,等。不要指望外部可以看到Python的环境变量,也不要指望读写标准输入输出象真正的CGI环境那样。
这个处理器是为了逐步(step stone)移植(migration)CGI遗留(legacy)下来的代码。不建议大量使用这种方式。因为CGI程序没有打算(intend)过在线程环境中使用(比如请求更换当前目录,就不是线程安全的。这就需要在多线程服务器程序中使用线程锁)。他的实现方式也远不如mod_python中的其他方式。
需要使用它,只需在.htaccess文件中加入如下内容:
SetHandler mod_python
PythonHandler mod_python.cgihandler
在版本2.7中,cgihandler会完全的重新载入来间接(indirectly)的重新导入模块。这是为了保护装入模块列表(sys.modules)来优先(prior)执行CGI脚本,然后在脚本执行后对比导入模块。除了一些__file__属性指向标准Python库的模块外,将会被在sys.modules中删除来强制Python在下次CGI脚本导入时再次装入他们。
如果你不希望如上的行为,可以编辑'cgihandler.py'文件注释掉###代码。
测试表明,cgihandler在多次文件上传时会发生内存泄漏(leak)。而且至今还没有完全解决这个问题。暂时的解决办法是设置apache的MaxRequestsPerChild到一个非零的值。
完成...
注:本文由gashero翻译,欢迎转载,转载前情确保同意英文原版的版权声明。因本人英语水平有限,文中有错误之处欢迎指出,意见发送到harry.python@gmail.com。本文不定期更新,请关注"河边的小屋"(http://blog.csdn.net/gashero)来查看最新翻译版本。翻译全程特别感谢"画鬓如霜"的支持,没有你的爱我无法完成这一切。也感谢python-chinese邮件列表上朋友的支持。