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暂时不读源码,但需要理解调用模块的大致作用。