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功能并传入environstart_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版本应用的运行步骤就解读完毕。