application.py
__all__ = [
"application",
"auto_application",
"subdir_application",
"subdomain_application",
"loadhook",
"unloadhook",
"autodelegate",
]
__all__
是一个字符串列表,里面包含要对外暴露的接口。即可以利用模块名直接调用且只能调用__all__
中包含的类或者方法。
application类
下面是类的构造方法头
def __init__(self, mapping=(), fvars={}, autoreload=None):
一般会这样使用:
app = web.application(urls, global())
先看一下构造器的最后一个参数autoreload
(自动加载),默认值为None,
if autoreload is None:
autoreload = web.config.get("debug", False)
这段代码说明,当没有设置autoreload参数时,autoreload = web.config.debug
,可能为True,也可能为False。如果没有设置web.config.debug,那autoreload = False,即不启动自动加载。
何为自动加载?
也就是在项目启动后,如果修改了代码,无需重新启动项目,而是程序内部自动加载,从而直接达到预期效果。这在调试代码时很有用。
构造器的第二个参数mapping=()
代表urls路由元组,通过init_mapping()
函数来初始化url路由映射关系,这个函数内部调用了utils工具类的group()函数
self.init_mapping(mapping)
...
...
def init_mapping(self, mapping):
self.mapping = list(utils.group(mapping, 2))
...
...
def group(seq, size):
return (seq[i : i + size] for i in range(0, len(seq), size))
看看group()是怎么实现的,再看看下面的例子就明白了:
urls = (
'/', 'toindex',
'/index', 'index',
'/favicon.ico', 'ico',
)
=>
self.mapping = [
['/', 'toindex'],
['/index', 'index'],
['/favicon.ico', 'ico'],
]
后面框架在进行url路由时,就会遍历这个二维列表。
构造器的第三个参数fvars={}
代表一个字典,比如globals()
,locals()
,分别提供当前范围的全局变量或局部变量。
构造器接着初始化了一个processors空列表,在add_processor()
中每个处理器都会被添加到该列表中
self.processors = []
...
def add_processor(self, processor):
self.processors.append(processor)
构造器里还通过add_processor()
添加了两个处理器self._load
和self._unload
,分别在HTTP请求处理前后进行处理,并非真正用来处理HTTP请求,而是做一些额外的工作,官方对于处理器也做了详细解读,可自行查阅。
self.add_processor(loadhook(self._load))
self.add_processor(unloadhook(self._unload))
看看其它的类方法:
add_mapping()
用于添加url路由映射。
def add_mapping(self, pattern, classname):
self.mapping.append((pattern, classname))
request()
通过指定路径和方法向应用发出请求,返回值reponse将是一个带有data, status和headers、header_items的storage
对象。一个storage对象类似于一个字典,但是有两种调用方式:obj.foo
和obj['foo']
def request(
self,
localpart="/",
method="GET",
data=None,
host="0.0.0.0:8080",
headers=None,
https=False,
**kw
):
...
...
response = web.storage()
def start_response(status, headers):
response.status = status
response.headers = dict(headers)
response.header_items = headers
data = self.wsgifunc()(env, start_response)
response.data = b"".join(data)
return response
可以看到data是调用了wsgifunc()
产生的,其执行结果是返回一个WSGI兼容的函数,并且该函数内部实现了url路由等功能。其中还包含peep
和wsgi
两个内置函数,如果没有实现任何中间件(即*middleware
为空),那就直接返回内置的wsgi
函数,否则要将wsgi转成中间件类型再返回。很明显这里是直接返回内置wsgi并调用。
def wsgifunc(self, *middleware):
def peep(iterator):
...
def wsgi(env, start_resp):
...
for m in middleware:
wsgi = m(wsgi)
return wsgi
peep()
函数通过进行一次迭代来窥视一个迭代器,并返回一个等效的迭代器。返回itertools.chain([firstchunk], iterator)
,chain对象的__next__()
方法会按顺序直到消耗完所有迭代器的可迭代对象。
wsgi()
函数实现了wsgi兼容接口,同时也实现了url路由等功能,其中:
self._cleanup()
内部调用了utils.ThreadedDict.clear_all()
,表示清除所有的threadlocal数据,避免内存泄漏(web.py框架的很多数据都保存在threadlocal变量中)
self.load(env)
使用env中的参数初始化了web.ctx
变量,我们在应用中可能会用到,比如web.ctx.fullpath
handle_with_processors ()
会对请求的url进行路由,找到合适的类或子应用来处理该请求,对于处理的返回结果,可能有三种方式:
- 返回一个可迭代对象,则通过
peep()
进行安全迭代处理。 - 返回其他值,则创建一个列表对象来存放
- 如果抛出了一个HTTPError异常(比如我们使用raise web.OK(“hello, world”)这种方式来返回结果时),则将异常中的数据e.data封装成一个列表
build_result(result)
会将result中的元素都转为bytes类型。
接下来,根据WSGI规范,调用start_resp函数,并将result结果转换成一个迭代器。application = app.wsgifunc()
就是将wsgi函数赋值给application变量,这样应用服务器就可以采用WSGI标准和我们的应用对接了。
def wsgi(env, start_resp):
# clear threadlocal to avoid inteference of previous requests
self._cleanup()
self.load(env)
try:
# 请求方法字母必须是大写
if web.ctx.method.upper() != web.ctx.method:
raise web.nomethod()
result = self.handle_with_processors()
if result and hasattr(result, "__next__"):
result = peep(result)
else:
result = [result]
except web.HTTPError as e:
result = [e.data]
def build_result(result):
for r in result:
if isinstance(r, bytes):
yield r
else:
yield str(r).encode("utf-8")
result = build_result(result)
status, headers = web.ctx.status, web.ctx.headers
start_resp(status, headers)
def cleanup():
self._cleanup()
yield b"" # force this function to be a generator
return itertools.chain(result, cleanup())