文章出处:http://www.jb51.net/article/65307.htm
本文主要分析的是web.py库的application.py这个模块中的代码。总的来说,这个模块主要实现了WSGI兼容的接口,以便应用程序能够被WSGI应用服务器调用。WSGI是Web Server Gateway Interface的缩写,具体细节可以查看WSGI的WIKI页面
接口的使用
使用web.py自带的HTTP Server
下面这个例子来自官方文档的Hello World,这个代码一般是应用入口的代码:
1
2
3
4
5
6
7
8
9
10
11
|
import
web
urls
=
(
"/.*"
,
"hello"
)
app
=
web.application(urls,
globals
())
class
hello:
def
GET(
self
):
return
'Hello, world!'
if
__name__
=
=
"__main__"
:
app.run()
|
上面的例子描述了一个web.py应用最基本的组成元素:
其中,app.run()的调用是初始化各种WCGI接口,并启动一个内置的HTTP服务器和这些接口对接,代码如下:
1
2
|
def
run(
self
,
*
middleware):
return
wsgi.runwsgi(
self
.wsgifunc(
*
middleware))
|
与WSGI应用服务器对接
如果你的应用要与WSGI应用服务器对接,比如uWSGI,gunicorn等,那么应用入口的代码就要换一种写法了:
1
2
3
4
5
6
7
8
9
|
import
web
class
hello:
def
GET(
self
):
return
'Hello, world!'
urls
=
(
"/.*"
,
"hello"
)
app
=
web.application(urls,
globals
())
application
=
app.wsgifunc()
|
在这种场景下,应用的代码不需要启动HTTP服务器,而是实现一个WSGI兼容的接口供WSGI服务器调用。web.py框架为我们实现了这样的接口,你只需要调用application = app.wsgifunc()就可以了,这里所得到的application变量就是WSGI接口(后面分析完代码你就会知道了)。
WSGI接口的实现分析
分析主要围绕着下面两行代码进行:
1
2
|
app
=
web.application(urls,
globals
())
application
=
app.wsgifunc()
|
web.application实例化
初始化这个实例需要传递两个参数:URL路由元组和globals()的结果。
另外,还可以传递第三个变量:autoreload,用来指定是否需要自动重新导入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相关功能的代码略去了。其他的代码主要作了如下几个事情:
初始化URL路由映射关系
1
2
|
def
init_mapping(
self
, mapping):
self
.mapping
=
list
(utils.group(mapping,
2
))
|
这个函数还调用了一个工具函数,效果是这样的:
1
2
3
|
urls
=
(
"/"
,
"Index"
,
"/hello/(.*)"
,
"Hello"
,
"/world"
,
"World"
)
|
如果用户初始化时传递的元组是这样的,那么调用init_mapping之后:
1
2
3
|
self
.mapping
=
[[
"/"
,
"Index"
],
[
"/hello/(.*)"
,
"Hello"
],
[
"/world"
,
"World"
]]
|
后面框架在进行URL路由时,就会遍历这个列表。
添加处理器
1
2
|
self
.add_processor(loadhook(
self
._load))
self
.add_processor(unloadhook(
self
._unload))
|
这两行代码添加了两个处理器:self._load和self._unload,而且还对这两个函数进行了装饰。处理器的是用在HTTP请求处理前后的,它不是真正用来处理一个HTTP请求,但是可以用来作一些额外的工作,比如官方教程里面有提到的给子应用添加session的做法,就是使用了处理器:
1
2
3
4
|
def
session_hook():
web.ctx.session
=
session
app.add_processor(web.loadhook(session_hook))
|
处理器的定义和使用都是比较复杂的,后面专门讲。
wsgifunc函数
wsgifunc的执行结果是返回一个WSGI兼容的函数,并且该函数内部实现了URL路由等功能。
1
2
3
4
5
6
7
|
def
wsgifunc(
self
,
*
middleware):
"""Returns a WSGI-compatible function for this application."""
...
for
m
in
middleware:
wsgi
=
m(wsgi)
return
wsgi
|
除开内部函数的定义,wsgifunc的定义就是这么简单,如果没有实现任何中间件,那么就是直接返回其内部定义的wsgi函数。
wsgi函数
该函数实现了WSGI兼容接口,同时也实现了URL路由等功能。
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
|
def
wsgi(env, start_resp):
# clear threadlocal to avoid inteference of previous requests
self
._cleanup()
self
.load(env)
try
:
# allow uppercase methods only
if
web.ctx.method.upper() !
=
web.ctx.method:
raise
web.nomethod()
result
=
self
.handle_with_processors()
if
is_generator(result):
result
=
peep(result)
else
:
result
=
[result]
except
web.HTTPError, e:
result
=
[e.data]
result
=
web.safestr(
iter
(result))
status, headers
=
web.ctx.status, web.ctx.headers
start_resp(status, headers)
def
cleanup():
self
._cleanup()
yield
''
# force this function to be a generator
return
itertools.chain(result, cleanup())
for
m
in
middleware:
wsgi
=
m(wsgi)
return
wsgi
|
下面来仔细分析一下这个函数:
1
2
|
self
._cleanup()
self
.load(env)
|
self._cleanup()内部调用utils.ThreadedDict.clear_all(),清除所有的thread local数据,避免内存泄露(因为web.py框架的很多数据都会保存在thread local变量中)。
self.load(env)使用env中的参数初始化web.ctx变量,这些变量涵盖了当前请求的信息,我们在应用中有可能会使用到,比如web.ctx.fullpath。
1
2
3
4
5
6
7
8
9
10
11
12
|
try
:
# allow uppercase methods only
if
web.ctx.method.upper() !
=
web.ctx.method:
raise
web.nomethod()
result
=
self
.handle_with_processors()
if
is_generator(result):
result
=
peep(result)
else
:
result
=
[result]
except
web.HTTPError, e:
result
=
[e.data]
|
这一段主要是调用self.handle_with_processors(),这个函数会对请求的URL进行路由,找到合适的类或子应用来处理该请求,也会调用添加的处理器来做一些其他工作(关于处理器的部分,后面专门讲)。对于处理的返回结果,可能有三种方式:
-
1
2
3
4
5
6
7
8
9
10
|
result
=
web.safestr(
iter
(result))
status, headers
=
web.ctx.status, web.ctx.headers
start_resp(status, headers)
def
cleanup():
self
._cleanup()
yield
''
# force this function to be a generator
return
itertools.chain(result, cleanup())
|
接下来的这段代码,会对前面返回的列表result进行字符串化处理,得到HTTP Response的body部分。然后根据WSGI的规范作如下两个事情:
现在你可以看到,之前我们提到的application = app.wsgifunc()就是将wsgi函数赋值给application变量,这样应用服务器就可以采用WSGI标准和我们的应用对接了。
处理HTTP请求
前面分析的代码已经说明了web.py框架如何实现WSGI兼容接口的,即我们已经知道了HTTP请求到达框架以及从框架返回给应用服务器的流程。那么框架内部是如何调用我们的应用代码来实现一个请求的处理的呢?这个就需要详细分析刚才忽略掉的处理器的添加和调用过程。
loadhook和unloadhook装饰器
这两个函数是真实处理器的函数的装饰器函数(虽然他的使用不是采用装饰器的@操作符),装饰后得到的处理器分别对应请求处理之前(loadhook)和请求处理之后(unloadhook)。
loadhook
1
2
3
4
5
6
|
def
loadhook(h):
def
processor(handler):
h()
return
handler()
return
processor
|
这个函数返回一个函数processor,它会确保先调用你提供的处理器函数h,然后再调用后续的操作函数handler。
unloadhook
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
|
def
unloadhook(h):
def
processor(handler):
try
:
result
=
handler()
is_generator
=
result
and
hasattr
(result,
'next'
)
except
:
# run the hook even when handler raises some exception
h()
raise
if
is_generator:
return
wrap(result)
else
:
h()
return
result
def
wrap(result):
def
next
():
try
:
return
result.
next
()
except
:
# call the hook at the and of iterator
h()
raise
result
=
iter
(result)
while
True
:
yield
next
()
return
processor
|
这个函数也返回一个processor,它会先调用参数传递进来的handler,然后再调用你提供的处理器函数。
handle_with_processors函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def
handle_with_processors(
self
):
def
process(processors):
try
:
if
processors:
p, processors
=
processors[
0
], processors[
1
:]
return
p(
lambda
: process(processors))
else
:
return
self
.handle()
except
web.HTTPError:
raise
except
(KeyboardInterrupt, SystemExit):
raise
except
:
print
>> web.debug, traceback.format_exc()
raise
self
.internalerror()
# processors must be applied in the resvere order. (??)
return
process(
self
.processors)
|
这个函数挺复杂的,最核心的部分采用了递归实现(我感觉不递归应该也能实现同样的功能)。为了说明清晰,采用实例说明。
前面有提到,初始化application实例的时候,会添加两个处理器到self.processors:
1
2
|
self
.add_processor(loadhook(
self
._load))
self
.add_processor(unloadhook(
self
._unload))
|
所以,现在的self.processors是下面这个样子的:
1
|
self
.processors
=
[loadhook(
self
._load), unloadhook(
self
._unload)]
|
# 为了方便后续说明,我们缩写一下:
1
|
self
.processors
=
[load_processor, unload_processor]
|
当框架开始执行handle_with_processors的时候,是逐个执行这些处理器的。我们还是来看代码分解,首先简化一下handle_with_processors函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
def
handle_with_processors(
self
):
def
process(processors):
try
:
if
processors:
# 位置2
p, processors
=
processors[
0
], processors[
1
:]
return
p(
lambda
: process(processors))
# 位置3
else
:
return
self
.handle()
# 位置4
except
web.HTTPError:
raise
...
# processors must be applied in the resvere order. (??)
return
process(
self
.processors)
# 位置1
|
以上面的例子来说,目前有两个处理器:
1
|
self
.processors
=
[load_processor, unload_processor]
|
从位置1进入代码后,在位置2会判断还有处理器要执行,会走到位置3,此时要执行代码是这样的:
1
|
return
load_processor(
lambda
: process([unload_processor]))
|
load_processor函数是一个经过loadhook装饰的函数,因此其定义在执行时是这样的:
1
2
3
|
def
load_processor(
lambda
: process([unload_processor])):
self
._load()
return
process([unload_processor])
# 就是参数的lambda函数
|
会先执行self._load(),然后再继续执行process函数,依旧会走到位置3,此时要执行的代码是这样的:
1
|
return
unload_processor(
lambda
: process([]))
|
unload_processor函数是一个经过unloadhook装饰的函数,因此其定义在执行时是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
def
unload_processor(
lambda
: process([])):
try
:
result
=
process([])
# 参数传递进来的lambda函数
is_generator
=
result
and
hasattr
(result,
'next'
)
except
:
# run the hook even when handler raises some exception
self
._unload()
raise
if
is_generator:
return
wrap(result)
else
:
self
._unload()
return
result
|
现在会先执行process([])函数,并且走到位置4(调用self.handle()的地方),从而得到应用的处理结果,然后再调用本处理器的处理函数self._unload()。
总结一下执行的顺序:
1
2
3
|
self
._load()
self
.handle()
self
._unload()
|
如果还有更多的处理器,也是按照这种方法执行下去,对于loadhook装饰的处理器,先添加的先执行,对于unloadhook装饰的处理器,后添加的先执行。
handle函数
讲了这么多,才讲到真正要调用我们写的代码的地方。在所有的load处理器执行完之后,就会执行self.handle()函数,其内部会调用我们写的应用代码。比如返回个hello, world之类的。self.handle的定义如下:
1
2
3
|
def
handle(
self
):
fn, args
=
self
._match(
self
.mapping, web.ctx.path)
return
self
._delegate(fn,
self
.fvars, args)
|
这个函数就很好理解了,第一行调用的self._match是进行路由功能,找到对应的类或者子应用,第二行的self._delegate就是调用这个类或者传递请求到子应用。
_match函数
_match函数的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def
_match(
self
, mapping, value):
for
pat, what
in
mapping:
if
isinstance
(what, application):
# 位置1
if
value.startswith(pat):
f
=
lambda
:
self
._delegate_sub_application(pat, what)
return
f,
None
else
:
continue
elif
isinstance
(what,
basestring
):
# 位置2
what, result
=
utils.re_subm(
'^'
+
pat
+
'$'
, what, value)
|