Flask源码剖析(1)
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
如上是一个最简单的创建Flask应用的服务端程序。
当用户的gunicorn收到了http请求,去调用app的时候,实际上是调用了Flask的__call__
方法,因此__call__
方法怎么写,决定了整个流程从何处开始。
接下来看看__call__
的源码:
def __call__(self, environ, start_response):
"""The WSGI server calls the Flask application object as the WSGI application. This calls :meth:`wsgi_app` which can be wrapped to applying middleware."""
return self.wsgi_app(environ, start_response)
WSGI服务器将Flask对象调用为WSGI应用程序。当http请求从server发送过来的时候,它会使用__call__
函数,最终实际上是调用了wsgi_app
功能并传入environ
和start_response
那么,wsgi_app
函数的功能又是什么?接下来是其源码:
def wsgi_app(self, environ, start_response):
"""The actual WSGI application. This is not implemented in `__call__` so that middlewares can be applied: :param environ: a WSGI environment :param start_response: a callable accepting a status code, a list of headers and an optional exception context to start the 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应用程序。在方法__call__
中并没有实现,因此可以应用中间件而不会丢失对app对象的引用
一行行往下看,首先,函数构造了一个RequestContext
对象。这里有几个概念是需要了解的:
**上下文管理协议:**包含方法__enter__()
和__exit__()
,支持该协议的对象要实现这两个方法。
上下文管理器:支持上下文管理协议的对象,这种对象实现了__enter__()
和__exit__()
方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。
语句体(with-body):with 语句包裹起来的代码块,在执行语句体之前会调用上下文管
理器的__enter__()
方法,执行完语句体之后会执行__exit__()
方法。
所以,接下来来看看_RequestContext
类的函数以及定义:
class _RequestContext(object):
"""请求上下文包含所有跟请求相关的信息。它在请求开始时创建,并推入_request_ctx_stack,在请求结束的时候弹出。它将为提供的WSGI环境创建URL适配器和请求对象。 """
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):
# do not pop the request stack if we are in debug mode and an
# exception happened. This will allow the debugger to still
# access the request object in the interactive shell.
if tb is None or not self.app.debug:
_request_ctx_stack.pop()
注释中说到,请求上下文中包含着和请求相关的信息,在请求开始的时候进行创建,并在with语句执行一开始执行__enter__()
方法,将对象推入保存请求上下文的栈结构中,然后在with语句块执行结束时,视情况决定是否将其弹出。
接下来来看看with语句块中的执行:
rv = self.preprocess_request()
查看preprocess_request()
:
def preprocess_request(self):
"""在实际请求分派之前调用,并将调用每一个before_request修饰函数。如果这些函数中的任何一个返回一个值,那么它的处理方式就像它是视图中的返回值一样,然后停止进一步的请求处理。 """
for func in self.before_request_funcs:
rv = func()
if rv is not None:
return rv
然后接下来来看看下一行:
if rv is None:
rv = self.dispatch_request()
可以看到,如果before_request修饰函数没有返回值的话,则执行dispatch_request()
函数:
def dispatch_request(self):
# 执行请求分派,匹配URL并返回视图函数或错误处理程序的返回值。
try:
endpoint, values = self.match_request()
return self.view_functions[endpoint](**values)
except HTTPException, e:
handler = self.error_handlers.get(e.code)
if handler is None:
return e
return handler(e)
except Exception, e:
handler = self.error_handlers.get(500)
if self.debug or handler is None:
raise
return handler(e)
分配请求的函数通过匹配函数进行匹配,而match_request()
函数的作用就是去除_request_ctx_stack
中的顶部的请求上下文对象,并通过里面的url_adapter进行匹配,并将端点和视图参数返回,然后再在Flask属性中的view_functions
属性中寻找视图函数。
然后再回到wsgi_app()
函数中,dispatch_request()
函数执行完毕后,对request的处理也告一段落,接下来就是构造生成response的过程:
response = self.make_response(rv)
response = self.process_response(response)
return response(environ, start_response)
make_response()
函数是一个构造返回值对象的函数:
def make_response(self, rv):
"""将rv从视图函数转换为实数的一个步骤,响应对象是一个response_class的实例. The following types are allowd for `rv`: ======================= =========================================== :attr:`response_class` 直接返回 :class:`str` 使用字符串作为主体创建响应对象 :class:`unicode` 使用编码为utf-8的字符串作为主体创建响应对象 :class:`tuple` 使用元组的内容作为参数创建响应对象 a WSGI function 该函数作为WSGI应用程序调用,并作为响应对象缓冲 ======================= =========================================== :param rv: 视图函数的返回值 """
# 判断rv的类型
if isinstance(rv, self.response_class):
return rv
if isinstance(rv, basestring):
return self.response_class(rv)
if isinstance(rv, tuple):
return self.response_class(*rv)
return self.response_class.force_type(rv, request.environ)
force_type
方法的作用是转换,如果Flask碰到了一个不是其期望的响应对象,就会使用该方法进行转换。举个例子:
利用Flask创建API接口时经常会遇到一个问题,API接口通常返回的时JSON数据,这就要求使用jsonify()
函数将Python字典类型转换成JSON数据,并且还要在响应对象中将内容类型设置为JSON内容类型
@app.route('/data)
def get_data():
return jsonify({'foo':'bar'})
问题时,如果每个返回JSON数据的路由都需要这样处理,那么对接口数量众多的API来说,就要大量重复调用jsonify()
函数。从代码可读性角度来说,这种方式是不可取的。所以,一般这种情况下我们都会按照这种情况来处理:
@app.route('/data')
def get_data():
return {'foo':'bar'}
在Flask中,字典并不是其所期望的类型,在这种情况下,Flask会使用相应类的force_type
类方法,强制转换未知类型。
那么,构造响应类对象结束之后,下一句的执行:
response = self.process_response(response)
来查看一下process_response()
的源码:
def process_response(self, response):
"""可以被重写,以便在将响应对象发送到WSGI服务器前修改它,默认情况下,调用after_request修饰函数 :return: 新的响应对象或者相同的对象 """
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
首先,查看存放请求上下文的栈顶中的上下文对象,查看里面是否有session需要存放,如果有,则放入save_session
函数中进行存放的操作。然后查看Flask类对象的after_request_funcs
属性中是否有函数要对响应对象进行处理,如果没有,直接返回原对象。
至此,一个最简单的Flask0.1版本应用的运行步骤就解读完毕。