转载自:http://my.oschina.net/u/138488/blog/169092
2 找到源码分析的切入点, 以下是webpy官网提供的helloworld程序代码
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()
|
很明显 application.run()就是程序的启动入口了。打开application.py看代码果然很多,不过飘过其他的代码先不看找到run()函数其代码为:
1
2
|
def
run(
self
,
*
middleware):
return
wsgi.runwsgi(
self
.wsgifunc(
*
middleware))
|
好吧它又调转到了wsgi.py的runwsgi()函数了其代码为(该代码可以基本飘过只看最后一行)
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
34
35
36
|
def
runwsgi(func):
"""
Runs a WSGI-compatible `func` using FCGI, SCGI, or a simple web server,
as appropriate based on context and `sys.argv`.
"""
if
os.environ.has_key(
'SERVER_SOFTWARE'
):
# cgi
os.environ[
'FCGI_FORCE_CGI'
]
=
'Y'
if
(os.environ.has_key(
'PHP_FCGI_CHILDREN'
)
#lighttpd fastcgi
or
os.environ.has_key(
'SERVER_SOFTWARE'
)):
return
runfcgi(func,
None
)
if
'fcgi'
in
sys.argv
or
'fastcgi'
in
sys.argv:
args
=
sys.argv[
1
:]
if
'fastcgi'
in
args: args.remove(
'fastcgi'
)
elif
'fcgi'
in
args: args.remove(
'fcgi'
)
if
args:
return
runfcgi(func, validaddr(args[
0
]))
else
:
return
runfcgi(func,
None
)
if
'scgi'
in
sys.argv:
args
=
sys.argv[
1
:]
args.remove(
'scgi'
)
if
args:
return
runscgi(func, validaddr(args[
0
]))
else
:
return
runscgi(func)
server_addr
=
validip(listget(sys.argv,
1
, ''))
if
os.environ.has_key(
'PORT'
):
# e.g. Heroku
server_addr
=
(
'0.0.0.0'
, intget(os.environ[
'PORT'
]))
return
httpserver.runsimple(func, server_addr)
|
上面这段代码主要的意思就是通过传人的命令行参数进行不同的初始化,最后传人到httpservice.runsimple()函数了 并且将启动的application(func)也传了进去一下为httpservice.runsimple()源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
def
runsimple(func, server_address
=
(
"0.0.0.0"
,
8080
)):
global
serv<span><
/
span>er
func
=
StaticMiddleware(func)
func
=
LogMiddleware(func)
server
=
WSGIServer(server_address, func)
if
server.ssl_adapter:
print
"https://%s:%d/"
%
server_address
else
:
print
"http://%s:%d/"
%
server_address
try
:
server.start()
except
(KeyboardInterrupt, SystemExit):
server.stop()
server
=
None
|
看到这段代码我就豁然开朗了原理8080端口默认是在这里啊!
再看下面的代码
func = StaticMiddleware(func)
func = LogMiddleware(func)
它们分别是加入了两个路由第一个是我们静态资源文件夹路由"/static/",另外一个是日志路由(所谓路由可以理解为一个拦截器,路由拦截自定url路径的数据)不明白的话还是看代码比较清楚 以StaticMiddleware为例他们都在httpservice.py文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class
StaticMiddleware:
"""WSGI middleware for serving static files."""
def
__init__(
self
, app, prefix
=
'/static/'
):
#保存下一个路由
self
.app
=
app
#保存自己将要拦截的路径
self
.prefix
=
prefix
def
__call__(
self
, environ, start_response):
#当用户请求是wsgi将依次调用路由器的这个函数
path
=
environ.get(
'PATH_INFO'
, '')
path
=
self
.normpath(path)
#判断一下如果是我要拦截的路径"/static/"
if
path.startswith(
self
.prefix):
return
StaticApp(environ, start_response)
else
:
#如果不是就交由下一个路由处理
return
self
.app(environ, start_response)
|
这个地方其实挺关键因为webpy的application本身也可以理解为一个路由(这样你也就可以路径application中其实还可以添加下一层application了)webpy使用子应用http://webpy.org/cookbook/subapp.zh-cn
读到这里你可能会有点不明白了 def __call__(self, environ, start_response):这传入的两个参数 environ, start_response是怎么来的?有什么意义?
其实这是python的wsgi协议里面定义的东东了你可以通过这篇文章了解wsgi的部分知识http://linluxiang.iteye.com/blog/799163
如果你看明白了wsgi的知识也就自然明白了runsimple()函数中server.start()是个什么了,它其实就是启动wsgiserver服务
通过以上的学习你就可以写自己的路由了(仿照StaticMiddleware哦),其实webpy在application.run()就定义了路由接口,开发者可以写自己的路由然后传人run()函数 启动的时候会一步一步的传递到wgsi中。如下代码
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
34
35
|
'''
Created on 2013-10-15
@author: liangqianwu
'''
#_*_ coding:utf-8_*_
import
web
urls
=
(
"/.*"
,
"hello"
)
app
=
web.application(urls,
globals
())
class
hello:
def
GET(
self
):
return
'Hello, world!'
def
my(environ, start_response):
status
=
'200 OK'
output
=
'this is my Middleware!'
response_headers
=
[(
'Content-type'
,
'text/plain'
)]
write
=
start_response(status, response_headers)
write(
'Hello '
)
return
[output]
class
myMiddleware:
"""WSGI middleware for serving static files."""
def
__init__(
self
, app, prefix
=
'/my'
):
self
.app
=
app
self
.prefix
=
prefix
def
__call__(
self
, environ, start_response):
path
=
environ.get(
'PATH_INFO'
, '')
if
path.startswith(
self
.prefix):
return
my(environ, start_response)
else
:
return
self
.app(environ, start_response)
if
__name__
=
=
"__main__"
:
app.run(myMiddleware)
|
我们在运用webpy时有两个特性可能注意到了,一个是子应用(在上一篇文章已经提及到),另外是"应用处理器"和“钩子”Wbepy-coobook资料里面提及到钩子的使用方法,我们可以先明白它们的使用方法后面一步一步分析其实现原理。
首先我们进入appliction.py的 def __init__(self, mapping=(), fvars={}, autoreload=None):方法webpy初始化就基本从这里开始了
1
2
3
4
5
6
7
8
9
10
11
|
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
=
[]
#将appliction类自身的_load,_unload函数加入应用处理器
self
.add_processor(loadhook(
self
._load))
self
.add_processor(unloadhook(
self
._unload))
if
autoreload:
|
这里我们可以猜想到webpy里面很大一部分功能可能都是通过"processor"实现的,实际上在分析以下session.py代码以后就可以发现web.py的session处理就是通过加入一个processor实现的。processor可以理解为一个处理链条当请求到来时一步一步通过这个链条里面的processor处理。
下一步我们来了解当请求到来是webpy的执行过程,通过上一篇文章了解我们可以确定webpy其实是一个wsgi应用,webpy通过application.py 的run方法启动wsgi服务,并且传人自己的处理函数以供wsgi在有请求是回调处理。
1
2
|
def
run(
self
,
*
middleware):
return
wsgi.runwsgi(
self
.wsgifunc(
*
middleware))
|
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
def
wsgifunc(
self
,
*
middleware):
"""Returns a WSGI-compatible function for this application."""
def
peep(iterator):
"""Peeps into an iterator by doing an iteration
and returns an equivalent iterator.
"""
# wsgi requires the headers first
# so we need to do an iteration
# and save the result for later
try
:
firstchunk
=
iterator.
next
()
except
StopIteration:
firstchunk
=
''
return
itertools.chain([firstchunk], iterator)
def
is_generator(x):
return
x
and
hasattr
(x,
'next'
)
#本appliction处理回调函数,当请求到来时最先调用这个回调函数
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())
#将用户传人的应用处理器(上一篇文章最后实现的wsgi应用)加入到列表中
for
m
in
middleware:
wsgi
=
m(wsgi)
return
wsgi
|
分析以上代码 请求到来时将首先执行 def wsgifunc(self, *middleware):函数中def wsgi(env, start_resp):函数的代码,def wsgi(env, start_resp):又调用appliction.py的def handle_with_processors(self):函数递归调用执行"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)
|
通过以上代码分析如果processors被递归执行完毕以后便执行self.handle()函数代码如下
1
2
3
4
5
|
def
handle(
self
):
#匹配与url对应的执行函数
fn, args
=
self
._match(
self
.mapping, web.ctx.path)
#执行查找到的函数
return
self
._delegate(fn,
self
.fvars, args)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
def
_delegate(
self
, f, fvars, args
=
[]):
def
handle_class(
cls
):
meth
=
web.ctx.method
if
meth
=
=
'HEAD'
and
not
hasattr
(
cls
, meth):
meth
=
'GET'
if
not
hasattr
(
cls
, meth):
raise
web.nomethod(
cls
)
tocall
=
getattr
(
cls
(), meth)
return
tocall(
*
args)
def
is_class(o):
return
isinstance
(o, (types.ClassType,
type
))
if
f
is
None
:
#没有匹配
raise
web.notfound()
elif
isinstance
(f, application):
#如果找到的是一个appliction(子应用)
return
f.handle_with_processors()
elif
is_class(f):
return
handle_class(f)
elif
isinstance
(f,
basestring
):
if
f.startswith(
'redirect '
):
|