Flask只是一个python web框架,框架和server之间的数据交流,都是基于PEP3333所规范的WSGI, server调用framwork的某个callable进行数据交流。 这个callable, 可以是定义了__call__
方法的类,或者任何函数等。
而Flask应用的数据入口和出口(callable)就是Flask类实例的wsgi_app
函数。
1 | def wsgi_app(self, 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 | class _RequestContext(object): |
初始化_RequestContext:
- 为每个request context 绑定当前app实例;
- 并将环境变量于app实例的map绑定,获得一个map_adapter用于匹配rule和view_func;
- 根据环境变量创建一个request实例;
- 调用app的open_session函数,传入request实例,获取cookie,创建一个session;
- 创建变量g;
- 创建flash消息容器。
初始化完毕后,根据__enter__
函数,把当前request_context放入一个FILO的stack中,request_context处理完毕后,如果没有错误,再从stack中删除这个request_context。
既然每次request都有一个request_context,那又为什么要把它们放入stack中呢?
官方文档的解释是方便内部转发。
二、preprocess_request
1 | def wsgi_app(self, environ, start_response): |
由上可知preprocess_request
总是最先被调用。
preprocess_request
涉及的源码片段如下:
1 | class Flask(object): |
由上可知,只有request_context
没有返回值的时候,才会调用dispatch_request
。说明preprocess_request
的返回值可以越过正常的dispatch_request
这一步。
dispatch_request
涉及的源码片段如下:
1 | class Flask(object): |
首先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 | def wsgi_app(self, environ, start_response): |
不管是从preprocess_request
还是dispatch_request
中获得了返回值,最后都调用app实例的make_response
函数。
这个函数的作用主要是对返回值通过Response类进行包装。
五、process_response
1 | def wsgi_app(self, environ, start_response): |
获得一个Response实例后,再将这个实例传入process_response
函数,进行最后的处理。
1 | def process_response(self, response): |
首先检查request_context
的session
是否为空,如果不是,调用app实例的save_session
函数,把session
保存到response实例中;
然后遍历app的after_request_funcs
,调用这些函数处理response;
- 这里也说明每个after_request_func至少接受一个response作为参数,返回一个response。
最后返回response。
六、return response
1 | def wsgi_app(self, environ, start_response): |
最后一个语句return response(environ, start_response)
是WSGI规范的语法,表明数据处理完毕,开始返回。
以上就是一次完整的request和response周期,简单概括起来就是如下:
- 传入环境变量创建request_context;
- 遍历before_request_funcs,尝试获取返回值;
- dispatch_request,dispatch_request做的事情就多了,但都是在request_context下进行的:
match_request
从request_context中获得rv
,解析rv
获得endpoint和args,记录到request_context的request
实例中,返回rv
- 从
rv
中获得endpoint
和args
,匹配到view_func,执行view_func(**args)
,获得返回值 - 报错,则匹配到error_handler,获得返回值
- 对第2步或者第3步的返回值通过
make_response
包装成Response
类的实例response
; process_response
检查cookie和保存cookie到response
,再遍历after_request_funcs,对response
进行最后的处理;- 返回
response
七、一些常用的变量或者方法
render_template
, url_for
, flash
和current_app
, request
是常用的方法以及变量,下面是对它们源码的分析。
7.1 url_for
1 | def url_for(endpoint, **values): |
调用request_context的url_adapter,执行它的build
函数。
build
函数和match
函数不同,它接受endpoint和values参数,返回构建的url,而match
返回endpoint和values。
7.2 flash
1 | def flash(message): |
当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 | class _RequestContext(object): |
7.4 render_template
render_template
调用了jinja2,但是有一些request
这样的变量在template中也适用,是因为flask把这些变量设置成了默认的template_context。
1 | def render_template(template_name, **context): |
一般情况下,render_template
的语法是:
1 | render_template('index.html', title=title, name=name, about=about) |
其中'index.html'
是template_name
, title=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
, session
和g
的字典。
所以不管如何,jinja2都会得到一个包含了request
, session
和g
的template_context。
最后渲染的模板:
1 | return current_app.jinja_env.get_template(template_name).render(context) |
八、局限以及后续方向
局限:
- 版本是Flask 0.1
- Werkzeug提供的
Map
,Rule
,Request
,Response
,SecureCookie
类是非常重要的类,但目前还是个黑匣子
后续方向:
继续往Flask更新的版本读,Werkzeug暂时不读源码,但需要理解调用模块的大致作用。