Mod_python - 将Python集成到Apache

Gregory Trubetskoy(grisha@modpython.org)

摘要
Mod_python[1]是一个Apache server[2]模块,它将Python解释器嵌入到Apache server,以提供一个访问Apache server内部的接口,在此环境下还为简单的应用开发提供一个基本框架。Mod_python的优点有通用和快速。

本文主要描述Mod_python的实现,还有它的体系和挑战。

本文对那些已经熟悉web应用开发的读者很有帮助,特别是熟悉Apache的读者,当然更适合熟悉Mod_python的读者。本文还包括一些C方面的知识,对理解Python内部机制也很有帮助。

目标
很简单,Mod_python是Python和Apache的集成。Apache是一种Web开发的利器,特别是即将到来的Apache v2.0,Apache v2.0不局限于HTTP协议,还能动行于其它任何一种协议,前提是Apache中需要包含相应的模块。Mod_python的目标是为Python开发者提供直接访问它的功能。

在Mod_python的设计过程中,很明确地指出速度将作为它的一个关键点,一味地识别它的存在是错误的。

至少现在,提供内联式的Python就像PHP[15]不是本项目的目标。这是因为和Apache集成也做了一些改善,然而在Python社区中关于怎样将Python代码嵌入到html中,大家仍还没有一个清晰的共识,只是有相当多的一些模块遍及社区,分道扬镳。

项目状况
Mod_python最初地版本是在2000年4月,为了替代早期的一个项目--Httpdapy[3](1998),而Httpdapy是Nsapy[4](1997)的替代品,那时Nsapy是apache的一个端口。Nsapy是基于Aaron Watters的<<Internet Programming with Python>>[5]书中的一个介绍嵌入示例。

Mod_python之所以被作为产品,是因为它的稳定性。这时最新的稳定版本是v2.7.6。这个版本只能用在Apache v1.3中。最近所有的开发工作都为了下一个主要的版本Mod_python v3.0,它能支持即将到来的Apache v2.0.

快速入门
Mod_python包括两个组成部分:一个Apache动态加载模块Mod_python.so(它也能静态地链接到apache中),另一个是Mod_python(一个Python包).

假设mod_python已经加载到Apache中,如下配置:
DocumentRoot /foo/bar
<Directory /foo/bar>
AddHandler python-program .py
PythonHandler hello
</Directory>
接着在/foo/bar目录下创建一个脚本文件,取名为hello.py,脚本内容如下:
from mod_python import apache

def handler(req):
req.send_http_header()
req.write("hello %s" % req.remote_host)
return apache.OK

再在浏览器输入http://yourdomain/somefile.py,结果显示"hello 1.2.3.4",其中"1.2.3.4"是客户端的IP地址。

基本上每个mod_python脚本都是以"from mod_python import apache"开始。是因为apache是Mod_python包中的一个模块,它提供了一个接口来访问Apache的常量(如OK)和很多有用函数。其中,还有请求对象req,它提供当前的请求信息、连接信息和一个更深层次的Apache函数,在这个演示中send_http_header()函数是发送HTTP头信息,还有write()函数是发送数据到客户端。

Apache模块,请求Phases和Mod_python
Apache是通过Phases来处理器请求。一个phase是很多小任务中的一个,这些小任务各自处理一个请求。譬如,有的phase是将一个URI映射到磁盘上一个文件,有的phase是用来进行验证,有的则是生成内容,等等。Apache v1.3有10个phase(把clean-ups也当作一个phase的话就有11个)。

Apache有一个关键的架构设置是它允许module来处理请求,但这样会增大Apache的处理行为。(这里讲的module不只是指Python模块,通常一个Apache模块是一个在Apache启动时加载的共享库或DLL,也可以静态地链接到服务器中)。

Mod_python是一个Apache模块。使它不同于其它Apache模块的原因是它本身不做任何事情,有一些用C编写的Apache模块所能完成的事情它也能完成。另一句话说,它委托phase处理器给用户写的Python代码。

如下为Apache请求处理器图:
Apache Request Process

每个Apache模块为任何一个请求处理phase提供一个处理函数,每个处理函数有4种可能的返回值:
1.DECLINED表示此模块拒绝处理这个phase,Apache接着处理模块列表中的下一模块;
2.OK表示此phase已经处理完成了,Apache接着处理模块列表中的下一模块,再不让任何模块处理这个phase;
3.返回出错(HTTP[7]出错常量),使Apache产生一个出错页面并跳到日志phase;
4.DONE表示整个请求已经完成,Apache跳到日志phase。

DECLINED返回值其实有点含糊,因为很多模块实际上执行完成后返回DECLINED,接着其它的模块来处理此phase.下面这个示例说明一个处理是如何使用DECLINED返回值,这个处理中的所有请求都包含一个不相关的头信息。

from mod_python import apache

def fixup(req):
req.headers_out["X-Grok-this"] = "Python-Psychobabble"
return apache.DECLINED

通过这点可能比较清楚地说明,此{functionality}不同于CGI环境。与CGI相比,mod_python并不很重要,因为CGI的范围比较窄。其中,有一个不同点就是CGI的设计初衷就是为了动态生成内容,而不是一个mod_python脚本需求。譬如,可以认为一个mod_python脚本为整个服务器实现了一个自定义的日志机制,并没有扮演一个生成内容的角色。

Apache对象
Apache请求处理利用了一些重要的C结构,用以通过mod_python来获取。

request_rec - 请求记录

request_rec可能是最大的、使用频率最高的结构。它包括所有与处理一个请求相关的信息(总共大约有50个成员)。

Mod_python提供一个内置类型mp_request,封装了request_rec。mp_request类型不能直接地使用。相反,每个mod_handler都能获得一个Request实例引用,Request是一个python类,它封装了mp_request(封装了request_rec)。这样mod_python用户能附加他们自己的属性在这个Request实例上,再通过不同的phase来维护状态。

Request类提供方法来发送头信息和数据到客房端。

conn_rec - 连接记录
conn_rec包括全部的连接信息。它是一个从request_rec分离出来的结构,因为HTTP[7]允许同一个连接服务于多个请求。

可以通过mod_python中的内置类型mp_con来访问连接记录,总是可以通过Request对象的connection成员来获取,即req.connection。

server_rec - 服务器记录
server_rec包括全部的虚拟服务器信息,像服务器名、IP、端口、等等。可以通过Request对象的server成员来获取,即req.server。

ap_table - Apache表
在Apache中,所有的键/值对(像RFC822[8]头信息)都是存放在表中。一个表和python中的字典(dictionary)很相似,不同的是键/值对可以不是字符串,键查找有点慢,并且表可以复制键。
实质上,Apache表是不同于python字典的关键是没有使用哈稀法,而是很简单地顺序查找(尽管有人建议在Apache v2.0中使用哈稀法)。

Mod_python提供mp_table对象来封表,使用起来和python的字典非常相似。如果没有复制键,mp_table将返回一个列表。mp_table还提供了一个add方法来完成添加。

下面的代码来演示是如何使用的:
from mod_python import apache

def handler(req):
t = apache.make_table()
t["Set-Cookie"] = "Foo: bar;"
t.add("Set-Cookie") = "Bar: foo;"
s = t["Set-Cookie"] # s is ["Foo: bar;", "Bar: foo;"]
return apache.DECLINED

子解释器
Python C API通过函数Py_NewInterprer()来初始化一个子解释器。下面的一段是摘自<<Python/C API参考手册[6]>>,主要描述这个函数:
创建一个新的子解释器。是在一个完成分开的环境下执行python代码的。尤其是这个新的解释器在所有已经导入的模块中有一个分开的、独立的版本,包括基础的模块像__builtin__、__main__和sys。这个已经加载的模块列表(sys.modules)和模块查找路径(sys.path)也是分开的。只是这个新的环境没有sys.argv变量了。此外,它还有新的标准I/O流文件对象sys.stdin、sys.stdout和sys.stderr(然而这些相同的底层FILE结构和C库中提到的相似)。

python的重要设置并不能通过它自己获取,因此大部分python用户没有意识到这点。但是对于mod_python来说利用这个功能是很有意义的,一个Apache进程能同时负责任何一些不相关的应用程序。在默认情况下,mod_python为每个虚拟服务器创建一个子解释器,也可以修改这种方式。

当一个子解释器一旦被创建,它的一个引用就会被保存在一个Python字典中,这个字典是mod_python内部的。

在phase处理过程中,先执行用户Python代码,mod_python需要决定使用哪一个解释器。一般地,解释器和虚拟服务器同名,可以通过req->server->server_hostname这个Apache变量来获取。如果PythonInterpPerDirectory设置为On时,解释器的名字就是正在访问的目录名(通过req->filename获取),同时也将PythonInterpPerDirective也设置为On,Python*Handler应会指定此目录,可能是一些父目录。PythonInterpreter也可以强制指定此解释器的名字。

解释器一旦被取名后,我们就可以从这个解释器目录找到它的名字,倘若存在,我们就杀掉它,否则就会自动创建一个新子解释器。

Mod_python内部的phase处理
Apache控制mod_python进行处理请求,需要完成如下一系列的操作:
*通过查看当前有效地指定来决定使用解释器,可能是服务器名或者是目录名;
*获取或创建一个子解释器;
*获取或创建一个回调对象,这个回调对象是一个python对象;
*创建一个mp_request对象;(由于性能原因,mp_conn和mp_server两个对象是按需创建的,如果用户代码中没有使用它们就不永远不会被创建)
*调用Callback.Dispatch()来传递一个mp_request引用和正在处理的phase;
*(这以后的所有处理用python优于C)
*创建一个封装了mp_request的Request对象;
*启动sys.path,准备这个目录将被访问,如果已经启动了就可以不用启动;
*导入配置中的python模块,如果后来修改了只需reload一下就行了;
*在模块内部定位处理函数或对象;
*调用用户函数或对用户对象将其引用传递给Request对象;
*将返回值传递回mod_python;
*(从这开始需要改python为C了);
*Mod_python传递回返回值并开始控制Apache.

内存管理和回收
在长期运行的过程中,内存管理永远都一个挑战。必须很小心地释放这些请求处理过程中分配的内存,不管什么错误发生。

为了处理这个问题,Apache提供内存池。Apache有大量的API来完成分配内存、操作字符串和列表、等等,其中每个函数拥有一个池指针。譬如,Apache模块分配内存不是用malloc(),而是使用ap_palloc()并附上一个池指针。通过这种方式分配的内存,在池销毁时就会马上被释放掉。Apache在不同的生命期创建几个池,各个模块也能创建自己的池。请求池可能是用得频繁,主要是在每个请求开始时创建和结束时销毁。

遭糕的是,Python解释器不使用Apache池。但在很大程序上,mod_python程序员还是比较支持Python的引用记数和垃圾回收机制。在很情况下,它只是工作比较稳定的。就是在这些情况下,你必须知道,Apache每处理几百个请求时就会反复利用自己,主要是通过配置MaxRequestPerChild来指定。

Apache在销毁池之前通过一些API来完成内存回收,一般是通过调用一个C函数ap_register_cleanup()来注册内存回收,这个函数有3个参数:一个池指针、一个函数指针和一个指向任意数据的void指针。公在池销毁之前,则是指针。Mod_python在内部使用内存回收来销毁mp_request和mp_tables。

Mod_python用户通过Request.register_cleanup()和request.server.register_cleanup()来完成内存回收。前者在每个请求之后运行,后者则是在服务器退出时运行。

标准处理器
作为一个敏捷的读者可能会注意到,mod_python(或者Apache更合适)使一个处理器和一个目录(SetHandler)或者一个文件类型(AddHandler)建立关联,而不是与一个特定的文件建立关联。在本文开始的示例中,访问目录"/foo/bar"中的哪一个文件确实一点关系都没有。因为只要是以.py结尾,总是会执行相同的hello处理并产生相同的结果。其实在URI提及到的这个文件甚至可以不存在。

很显然,有一个问题就是"为什么不能在同一个目录里面访问多个mod_python脚本?"(或者"那样没有用!")。答案是mod_python在它和应用程序之间需要一个中间过渡层。这个层(处理器)超出了用户的想象,因为mod_python中包含两个功能处理器(标准处理器)。

CGI处理器(mod_python.cgihandler)
CGI处理器是为那些用户可以将自己的代码与mod_python一起使用,它启动了一个伪CGI环境来运行用户程序。这样就碰到两个有趣的实现挑战。
第一个问题:CGI处理器一般是通过标准os.environ对象来启动CGI环境。因为不管什么原因(python bug?),比较频繁的环境操作导致内存泄露(每个请求大约产生1Kb的内存泄露)。通过使用一个特殊的字典来替代os.environ。在大部分情况下,能稳定运行,但对于那些使用环境脚本来和随后调用的程序来通信是一个问题,特别是一些想把数据库服务器信息放在一个环境变量中的数据库接口。

第二个问题:cgihandler使用import/reload来运行一个模块,主模块导入间接的模块在首次调用后变得noops。这样对于那些希望间接地导入的模块中的高级别代码能在每次调用时运行是一个问题。 为了解决这个问题,现在cgihandler在导入用户脚本之前和之后对sys.module变量进行验证,最后就从sys.modules中清除最近的模块,这样就引起那些模块下次又被导入。

后来但不是最后,CGI规范[14]强烈推荐服务器将当前设置到脚本所在目录。这样如果修改当前目录就会线程不安全,因此cgihandler在多线程环境下设计了一个线程锁(如win32),这种多线程环境就会强制服务器在每一个时刻只处理一个cgihandler。
通过上述问题,cgihandler不推荐做为开发环境,它只是预留的一个接口,目的是有些用户仍还这方面的需求,只是必要的时候小心使用。

Publisher Handler(mod_python.publisher)
Publisher Handler可能是用mod_python编写web应用程序的最好方式,它的功能是模仿Zope[10]的一个组件ZPublisher开发出来的。
主要思想是将一个URI映射到一个模块中的一些对象,URI中的"/"与在Python中"."具有相同的意思。因此像"http://somedomain/somedir/module/object/method"这个URI,somedir是一个目录,module是这个目录下的模块,object是这个模块里面的对象,method是这个对象的方法,这样method方法将返回值发送到客户端。

下面是一个"hello world"示例:

def hello(req, who="nobody"):

return "Hello, %s!" % who

包含这段代码的文件名为myapp.py,我们在浏览器中键入"http://somedomain/somedir/myapp/hello",将会显示"Hello,nobody!",因此,如果加上参数"http://somedomain/somedir/myapp/hello?who=John",将会显示"Hello,John!"。

注意,第一个参数是Request对象,这意味着当使用publisher handler时,可以利用所有的高级mod_python功能。

调试
调试mod_python应用程序可能有点麻烦。Mod_python通过设置PythonEnablePdb来提供Python调试器(pdb),但它的可用性是有限的,因为这个调试器是一个用标准输入输出的交互工具,因此只能让Apache在foreground模式下才能使用(在Apache v1.3下通过-X打开,Apache v2.0下则通过-DONE_PROCESS)。

Mod_python保存所有的跟踪信息到服务器日志中,如果将PythonDebug设置为On(默认为Off),mod_python就会将这些跟踪信息发送到客户端。

程序员喜欢用print语句作为调试工具。

线程
Mod_python是线程安全的,在win32下运行稳定,在这里Apache是多线程的。

那些使用扩展模块的应用程序也务必线程安全。譬如,在windows中,许多数据库访问驱动不是线程安全的,因此需要使用一些线程锁来阻止两个线程同时运行驱动程序。

令人没有想到是,Python解释器本身不完全线程安全,如果运行多线程就需要包含一个线程锁,每10个字节码指令就释放掉一个线程锁,释放以让其它的线程运行。

关于设计与实现
Mod_Perl
那些熟悉mod_perl[10]将会发现mod_python的一些功能和mod_perl相似,譬如,Apache配置中的名字中,除了Perl与Python这两个单词不同外,其它的完全一样。

如果不认为mod_python与mod_perl的功能是特意做成相似的,特别是在Apache配置中,很显然是错的。它们接下来的版本中不会再有共同点,主要是因为它的Perl和Python的解释器有相当大的区别。

它们因为相似也有诸多原因。首先,没有意义来重新构建 - mod_perl已经碰到并解决了许多问题,而且结果仅能应用在mod_python。其次,自两个项目有相同的目标以来,除了语言选择的不同外,将它们的外观保持一致是很有意义的,特别表现在Apache的配置中。通常是系统管理员来完成Apache的相关配置工作,而不一个程序员,如果合二为一的话就会系统管理的工作变得更容易。

Python vs C
在一个web应用程序环境中快速和低成本是相当的重要。许多人却不认为这有多重要一直到他们的站点扩展到另一个卷(就是所谓的"/.effect"),而一味的追求点击率,他们确实得到了高点击率,以致于他们的站点负荷过重,最后没有一个人能访问站点了。

在这一点上,C总是优于Python。如果mod_python的作者有更多的时间,使用C来实现mod_python的部分将会点更大的比重。起初计划使用C来实现那些Python所不能实现的部分,但由于时间原因而没有写高品质的C代码。

SWIG
SWIG[13]被当作一种工作来提供映射到Apache的C结构(像request_rec)。SWIG有很多问题。而SWIG的主要优点是它的速度快,通过它很容易地创建访问C库的接口。C代码没有必要打算易读,而且SWIG需要在一个已经优美而又复杂的构建环境中编译。尽管如此,像mod_python这样长期的项目来说,质量比生命线更重要,SWIG似乎不是一个正确的选择。

前景与Apache v2.0
像前面提到,当今开发的主要问题是和Apache v2.0兼容。Apache v2.0在架构上和以前的版本(像v1.3)有比较大的区别,事到如今想要写出在v1.3和v2.0两个版本中都运行的代码,将不是一件容易的事,也不再实用了。由于主要的API都改名了,代码中都混有#ifdef这样的语句是很有可能的。因此mod_python的下一个主要版本将只支持Apache v2.0。

Apache v2.0实际上综合了两个包,一个服务器本身,另一个是底层库,即Apache Portable Runtime(ARP)[12]。APR库的设计目的只是为了提供邮件方面的 一些通用功能和抽象OS细节(加上"Portable")。mod_python未来的版本将提供访问APR的大部分或者全部接口。

在v2.0中另一个大的改进是过滤器和连接处理器的介绍。mod_python v3.0 alpha版本已经支持过滤器。连接是在HTTP底层运行。使用一个连接处理器可以实现各种不同的协议,如FTP。现在mod_python v3.0 alpha版本不支持连接处理器,但支持是计划之中的事了。

参考目录
[1] Mod_python. http://www.modpython.org/
[2] Apache Http Server. http://httpd.apache.org/
[3] Httpdapy. http://www.ispol.com/home/grisha/httpdapy
[4] Nsapy. http://www.ispol.com/home/grisha/nsapy
[5] Aaron Watters, Guido van Rossum, James C. Ahlstrom, Internet Programming with Python, M&T Books, 1996.
[6] Guido van Rossum, Fred L. Drake, Jr, Python/C API Reference Manual, PythonLabs. http://www.python.org/doc/current/api/.
[7] R. Fielding, UC Irvine, J. Gettys, J. Mogul, DEC, H. Frystyk, T. Berners-Lee, MIT/LCS, "Hyper Text Transfer Protocol -- HTTP/1.1", RFC 2068, IETF January 1997. http://www.ietf.org/rfc/rfc2068.txt?number=2068
[9] Crocker, D., "Standard for the Format of ARPA Internet Text Messages", STD 11, RFC 822, UDEL, August 1982. http://www.ietf.org/rfc/rfc822.txt
[10] Zope http://www.zope.org/
[11] Mod_perl, Apache/Perl Integration. http://perl.apache.org/
[12] Apache Portable Runtime. http://apr.apache.org/
[13] Simplified Wrapper and Interface Generator. http://www.swig.org/
[14] Ken A L Coar, The WWW Common Gateway Interface Version 1.1. http://cgi-spec.golux.com/draft-coar-cgi-v11-03.txt
[15] PHP. http://www.php.net/

[原文地址:http://www.modpython.org/python10/]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值