读Flask: 一次完整的Request和Response周期

Flask只是一个python web框架,框架和server之间的数据交流,都是基于PEP3333所规范的WSGI, server调用framwork的某个callable进行数据交流。 这个callable, 可以是定义了__call__方法的类,或者任何函数等。
而Flask应用的数据入口和出口(callable)就是Flask类实例的wsgi_app函数。

1
2
3
4
5
6
7
8
def wsgi_app(self, environ, start_response):
with self.request_context(environ):
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
response = self.make_response(rv)
response = self.process_response(response)
return response(environ, start_response)

wsgi_app接受从server发过来的environ环境变量,并且根据这个变量创建一个request context,然后在这个context下进行数据处理,最后返回数据到server。

一、_RequestContext

wsgi_app函数中的with self.request_context(environ)调用了_RequestContext这个类,这是一个定义了__enter____exit__的类。当使用with语法的时候,初始化类后,就会执行__enter__函数,with语块执行完毕后,就会执行__exit__函数。
下面是_RequestContext的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class _RequestContext(object):
def __init__(self, app, environ):
self.app = app
self.url_adapter = app.url_map.bind_to_environ(environ)
self.request = app.request_class(environ)
self.session = app.open_session(self.request)
self.g = _RequestGlobals()
self.flashes = None

def __enter__(self):
_request_ctx_stack.push(self)

def __exit__(self, exc_type, exc_value, tb):
if tb is None or not self.app.debug:
_request_ctx_stack.pop()

初始化_RequestContext:

  1. 为每个request context 绑定当前app实例;
  2. 并将环境变量于app实例的map绑定,获得一个map_adapter用于匹配rule和view_func;
  3. 根据环境变量创建一个request实例;
  4. 调用app的open_session函数,传入request实例,获取cookie,创建一个session;
  5. 创建变量g;
  6. 创建flash消息容器。

初始化完毕后,根据__enter__函数,把当前request_context放入一个FILO的stack中,request_context处理完毕后,如果没有错误,再从stack中删除这个request_context。
既然每次request都有一个request_context,那又为什么要把它们放入stack中呢?
官方文档的解释是方便内部转发。

二、preprocess_request

1
2
3
4
5
6
7
8
def wsgi_app(self, environ, start_response):
with self.request_context(environ):
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
response = self.make_response(rv)
response = self.process_response(response)
return response(environ, start_response)

由上可知preprocess_request总是最先被调用。
preprocess_request涉及的源码片段如下:

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
class Flask(object):
...
def before_request(self, f):
self.before_request_funcs.append(f)
return f

def preprocess_request(self):
for func in self.before_request_funcs:
rv = func()
if rv is not None:
return rv
```
首先,被before\_request装饰的view_func都会被添加到app的`before_request_funcs`容器中;
然后,`preprocess_request`遍历这个容器,执行里面的函数;
如果其中任何一个函数有返回值,则停止遍历,返回该值。

## 三、dispatch_request
```python
def wsgi_app(self, environ, start_response):
with self.request_context(environ):
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
response = self.make_response(rv)
response = self.process_response(response)
return response(environ, start_response)

由上可知,只有request_context没有返回值的时候,才会调用dispatch_request。说明preprocess_request的返回值可以越过正常的dispatch_request这一步。
dispatch_request涉及的源码片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Flask(object):
...
def match_request(self):
rv = _request_ctx_stack.top.url_adapter.match()
request.endpoint, request.view_args = rv
return rv

def dispatch_request(self):
try:
endpoint, values = self.match_request()
return self.view_functions[endpoint](**values)
except HTTPException as e:
handler = self.error_handlers.get(e.code)
if handler is None:
return e
return handler(e)
except Exception as e:
handler = self.error_handlers.get(500)
if self.debug or handler is None:
raise
return handler(e)
...

request = LocalProxy(lambda: _request_ctx_stack.top.request)

首先dispatch_request会调用app实例的match_request函数;

  • 从_request_ctx_stack中获取最新的request_context
  • 调用这个request_context的url_adapter,执行它的match函数,获得其返回值rv
  • 解析rv,分别存储到request_context的request实例的endpoint和view_args属性中
  • 返回rv
    然后从match_request的返回值中获得endpoint和values;
    根据endpoint在app实例的view_functions字典中索引到view_func函数对象,传入values作为参数。
    如果以上步骤报错,首先看看是不是HTTP类型的错误,如果是,根据HTTP错误的错误代码在app实例的error_handlers字典中索引error_handler函数,执行该函数;
    如果是其它类型的错误,调用500错误的error_handler函数, 如果是debug模式或者该error_handler函数没有找到,报错;否则,执行该函数。
    不管是调用的view_func还是error_handler,其结果都被返回。

四、make_response

1
2
3
4
5
6
7
8
def wsgi_app(self, environ, start_response):
with self.request_context(environ):
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
response = self.make_response(rv)
response = self.process_response(response)
return response(environ, start_response)

不管是从preprocess_request还是dispatch_request中获得了返回值,最后都调用app实例的make_response函数。
这个函数的作用主要是对返回值通过Response类进行包装。

五、process_response

1
2
3
4
5
6
7
8
def wsgi_app(self, environ, start_response):
with self.request_context(environ):
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
response = self.make_response(rv)
response = self.process_response(response)
return response(environ, start_response)

获得一个Response实例后,再将这个实例传入process_response函数,进行最后的处理。

1
2
3
4
5
6
7
def process_response(self, response):
session = _request_ctx_stack.top.session
if session is not None:
self.save_session(session, response)
for handler in self.after_request_funcs:
response = handler(response)
return response

首先检查request_contextsession是否为空,如果不是,调用app实例的save_session函数,把session保存到response实例中;
然后遍历app的after_request_funcs,调用这些函数处理response;

  • 这里也说明每个after_request_func至少接受一个response作为参数,返回一个response。
    最后返回response。

六、return response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def wsgi_app(self, environ, start_response):

with self.request_context(environ):

rv = self.preprocess_request()

if rv is None:

rv = self.dispatch_request()

response = self.make_response(rv)

response = self.process_response(response)

return response(environ, start_response)

最后一个语句return response(environ, start_response)是WSGI规范的语法,表明数据处理完毕,开始返回。
以上就是一次完整的request和response周期,简单概括起来就是如下:

  1. 传入环境变量创建request_context;
  2. 遍历before_request_funcs,尝试获取返回值;
  3. dispatch_request,dispatch_request做的事情就多了,但都是在request_context下进行的:
  • match_request从request_context中获得rv,解析rv获得endpoint和args,记录到request_context的request实例中,返回rv
  • rv中获得endpointargs,匹配到view_func,执行view_func(**args),获得返回值
  • 报错,则匹配到error_handler,获得返回值
  1. 对第2步或者第3步的返回值通过make_response包装成Response类的实例response
  2. process_response检查cookie和保存cookie到response,再遍历after_request_funcs,对response进行最后的处理;
  3. 返回response

七、一些常用的变量或者方法

render_templateurl_for, flashcurrent_apprequest是常用的方法以及变量,下面是对它们源码的分析。

7.1 url_for

1
2
def url_for(endpoint, **values):
return _request_ctx_stack.top.url_adapter.build(endpoint, values)

调用request_context的url_adapter,执行它的build函数。
build函数和match函数不同,它接受endpoint和values参数,返回构建的url,而match返回endpoint和values。

7.2 flash

1
2
3
4
5
6
7
8
9
10
def flash(message):
session['_flashes'] = (session.get('_flashes', [])) + [message]

def get_flashed_messages():
flashes = _request_ctx_stack.top.flashes
if flashes is None:
_request_ctx_stack.top.flashes = flashes = \
session.pop('_flashes', [])
return flashes
session = LocalProxy(lambda: _request_ctx_stack.top.session)

flash(msg)的时候,msg其实是被存储到session中的;
当获取msg的时候,首先尝试从request_context中获取flashes, 如果为空,则从session中pop一个(获取并删除),再返回。

7.3 current_app和request
current_app其实就是request_context所绑定的app实例:

1
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)

request则是在初始化request_context的时候根据环境变量而实例化的一个Request类:

1
2
3
4
5
6
7
class _RequestContext(object):
def __init__(self, app, environ):
self.app = app
...
self.request = app.request_class(environ)
...
request = LocalProxy(lambda: _request_ctx_stack.top.request)

7.4 render_template
render_template调用了jinja2,但是有一些request这样的变量在template中也适用,是因为flask把这些变量设置成了默认的template_context。

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
def render_template(template_name, **context):
current_app.update_template_context(context)
return current_app.jinja_env.get_template(template_name).render(context)

def _default_template_ctx_processor():
"""Default template context processor. Injects `request`,
`session` and `g`.
"""
reqctx = _request_ctx_stack.top
return dict(
request=reqctx.request,
session=reqctx.session,
g=reqctx.g
)

class Flask(object):
def __init__(self, package_name):
...
self.template_context_processors = [_default_template_ctx_processor]
...
...
def update_template_context(self, context):
reqctx = _request_ctx_stack.top
for func in self.template_context_processors:
context.update(func())
...

一般情况下,render_template的语法是:

1
render_template('index.html', title=title, name=name, about=about)

其中'index.html'template_nametitle=title, name=name, about=about合并为context字典;
在调用jinja2渲染模板之前,Flask首先调用request_context下的current_app,执行其update_template_context函数,传入context字典;
app实例的update_template_context会遍历app实例的template_context_processors,将其中每一个函数的返回值与context字典合并;
而app实例默认的template_context_processor就是_default_template_ctx_processor,它会返回一个包含了request_context下的request, sessiong的字典。
所以不管如何,jinja2都会得到一个包含了request, sessiong的template_context。
最后渲染的模板:

1
return current_app.jinja_env.get_template(template_name).render(context)

八、局限以及后续方向

局限:

  1. 版本是Flask 0.1
  2. Werkzeug提供的Map, Rule, Request, Response, SecureCookie类是非常重要的类,但目前还是个黑匣子

后续方向:
继续往Flask更新的版本读,Werkzeug暂时不读源码,但需要理解调用模块的大致作用。