0%

webpy源码浅析

周末看了关于Aaron Swartz的一些东西:一部电影 互联网之子 和一个web框架 webpy,这里写一下webpy的源码的简单分析。

webpy安装

webpy安装使用python的软件包管理工具(easy_install或者pip)安装就可以了。

1
sudo easy_install webpy

hello world

官网的一个简单demo, code.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import web

urls = (
'/(.*)', 'hello'
)

app = web.application(urls, globals())

calss hello:
def GET(self, name):
if not name:
name = 'World'
return 'Hello, ' + name + '!'

if __name__ == '__main__':
app.run()

命令行启动

1
python code.py

这时候用浏览器打开 localhost:8080,就看到了Hello World。

源码解读

我们针对上面的code.py过一下源代码。源代码主要是在web文件夹下,包括application.py, browser.py, db.py等。

urls是路由表,表示对于目录’/(.*)’的处理都对应到hello,hello下面可以定义HTTP的方法,包括GET,POST等。另外,细心一点地话会发现 路由表支持正则表达式。

1
2
app = web.application(urls, globals())
app.run()

参数urls为路由表,globals()为python内置函数,主要作用是传递全局变量。我们看一下application类。

1
2
3
4
5
6
7
8
9
10
11
12
13
class application:
def __init__(self, mapping=(), fvars={}, autoreload=None)
if autoreload is None:
autoreload = web.config.get('debug', False)
self.init_mapping(mapping)
self.fvars = fvars
self.processors = []

self.add_processor(loadhook(self._load))
self.add_processor(unloadhook(self._unload))

if autoreload:
...

autoreload表示是否需要重新import程序,每一个wsgi调用application都会检查一下,具体细节先不说了。init_mapping(mapping)这个函数直觉上应该就是将url路由表载入,我们来验证一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def init_mapping(self, mapping):
self.mapping = list(utils.group(mappping, 2))
class utils:
def group(seq, size):
def take(seq, n):
for i in xrange(n):
yield seq.next()
if not hasattr(seq, 'next'):
seq = iter(seq)
while True:
x = list(take(seq, size))
if x:
yield x
else
break

显然这里将mapping进行了两两分组,实现代码我看了一会才明白。

processors是干嘛用的呢?其实是为了自定义一些操作。代码里的loadhook()是处理request之前要进行的操作,unloadhook()是处理完request之后要进行的操作,这里不妨说下_load和_unload是干嘛的?_load的作用是载入request的一些状态,比如一些环境变量,实现这里就先不展开了。_unload那么就卸载了。

下面看一下app.run()。这里代码跳转比较多,先全部列出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class application:
def run(self, *middleware):
return wsgi.runwsgi(self.wsgifunc(*middleware))
def wsgifunc(self, *middleware):
...
for m in middleware:
wsgi = m(wsgi)
return wsgi
wsgi.runwsgi:
def runwsgi(func):
...
server_addr = validip(listget(sys.argv, 1, ''))
if os.environ.hash_key('PORT'):
server_addr = ('0.0.0.0', intget(os.environ['PORT']))
return http.server.runsimple(func, server_addr)

httpserver:
def runsimple(func, server_address=("0.0.0.0", 8080)):
global server
func = StaticMiddleware(func)
func = LogMiddleware(func)

server = WSGIServer(server_address, func)
...
try:
server.start()
except (KeyboadInterrupt, SystemExit):
server.stop()
server = None
def WSGIServer(server_address, wsgi_app):
import wsgiserver
...
server = wsgiserver.CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")

app.run最后,也是最主要的工作是起一个CherryPy WSGI server 监听前面传过来的端口,对应的响应函数为前面传过来的func。CherryPy是一种WSGI server,还有一些别的,所以这个地方是可以重载的。而app.run到httpserver.runsimple中间的代码主要用来生成可以供CherryPy server调用的应用程序,以及生成对应的端口地址。

Webpy号称匕首,小巧而不失锋利。最可惜的是Aaron Swartz离开了,可惜了。之后会继续写一些webpy源码的文章。