Flask 源码分析-基本工作流程

Posted 大路上的小强

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flask 源码分析-基本工作流程相关的知识,希望对你有一定的参考价值。

本文主要分析了 Flask 的基本工作流程,由于处理 URL 请求与路由规则强相关,因此先介绍了Flask是怎样建立路由规则,然后详细分析了 Flask 的基本工作流程。

建立路由规则

客户端把请求发给 Web 服务器,Web 服务器再把请求发送给程序实例,程序实例需要知道每个 URL 请求运行哪些代码,所以保存了一个URL到处理函数的映射关系,处理 URL 和函数之间关系的程序称为路由。
Flask 建立路由规则的方法一般通过 @route 装饰器对视图函数进行装饰,例如:

app = Flask(__name__)@app.route('/')def index():    return 'Hello world!'

该方法也可以写成

app = Flask(__name__)def index():	return 'Hello world!'app.add_url_rule('/', 'hello', hello)

建立路由规则时,Flask 究竟做了什么,我们来看看 route 函数:

def route(self, rule, **options):    def decorator(f):
        endpoint = options.pop('endpoint', None)        self.add_url_rule(rule, endpoint, f, **options)        return f    return decorator

route 函数是一个装饰器,获取参数 options 中的 endpoint 后,调用 add_url_rule 添加路由规则,返回被装饰的函数,这也验证了上面两种方法等价的说法。add_url_rule 是怎样添加路由规则的?

def add_url_rule(self, rule, endpoint=None, view_func=None, **options):    ...
    rule = self.url_rule_class(rule, methods=methods, **options)
    rule.provide_automatic_options = provide_automatic_options    self.url_map.add(rule)    if view_func is not None:
        old_func = self.view_functions.get(endpoint)        if old_func is not None and old_func != view_func:            raise AssertionError('View function mapping is overwriting an '
                                 'existing endpoint function: %s' % endpoint)        self.view_functions[endpoint] = view_func

这段代码省略了获取 endpoint  methods 的部分,它主要做了两件事,一是根据 URL、methods endpoint 创建 Rule 对象,将其添加到变量 url_map 中,Rule 对象是 werkzeug.routing:Rule 类的对象,url_map  werkzeug.routeing:Map 类的对象,这两个类的具体作用会在后面介绍。add_url_rule 函数做的第二件事是将视图函数添加到字典 view_functions 中,对应的键为endpoint,这样路由规则就创建完成。
根据上面的处理过程,我们可以猜想一下,当客户端发来 URL 请求,Flask 是怎样处理的:先根据请求中的 urlmethod  url_map 中的 rule 进行匹配,若能匹配上,就能获取 rule 对应的endpoint,然后查找字典 view_funciton,得到前面添加的视图函数,视图函数对请求进行处理,返回结果。

工作流程

当服务器收到 http 请求,调用 Flask 应用时,实际上是调用的 Flask 的 __call__ 方法,从 __call__方法的源码可以看出,最终是调用了 wsgi_app 方法并传入 environ  start_responseenviron 为请求头的所有信息,start_response则是 Flask应用 处理完之后需要调用的函数,参数是状态码、响应头部还有错误信息。。

def __call__(self, environ, start_response):    """Shortcut for :attr:`wsgi_app`."""
    return self.wsgi_app(environ, start_response)

wsgi_app 的定义如下,基本上包含了整个流程的功能

def wsgi_app(self, environ, start_response):    """    :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    """
    ctx = self.request_context(environ)
    ctx.push()
    error = None
    try:        try:
            response = self.full_dispatch_request()        except Exception as e:
            error = e
            response = self.handle_exception(e)        return response(environ, start_response)    finally:        if self.should_ignore_error(error):
            error = None
        ctx.auto_pop(error)

1. 创建request上下文

ctx = self.request_context(environ)语句生成了一个 request 上下文,具体做了什么,我们来看看 RequestContext 的初始化函数,

class RequestContext(object):    """The request context contains all request relevant information.  It is    created at the beginning of the request and pushed to the    `_request_ctx_stack` and removed at the end of it.  It will create the    URL adapter and request object for the WSGI environment provided.    """

    def __init__(self, app, environ, request=None):        self.app = app        if request is None:
            request = app.request_class(environ)        self.request = request        self.url_adapter = app.create_url_adapter(self.request)        self.flashes = None
        self.session = None
        self.match_request()        ...

    def match_request(self):        """Can be overridden by a subclass to hook into the matching        of the request.        """
        try:
            url_rule, self.request.view_args = \                self.url_adapter.match(return_rule=True)            self.request.url_rule = url_rule        except HTTPException as e:            self.request.routing_exception = e

在初始化的时候,首先创建了 request 对象,会调用 app.create_url_adapter 方法,把 app url_map 绑定到 request  WSGI environ 变量上,创建 MapAdapter 对象,最后会调用match_request 方法,返回匹配到的 url_rule 以及 url 中的参数。 request 上下文的具体实现以及栈结构实现较复杂,在后续文章中会专门分析。

2. 分发请求

这里省略了 full_dispatch_reques 函数中请求预处理,错误处理的流程,重点分析dispatch_reques 函数。request 上下文栈结构弹出当前的 request 对象,取出先前匹配到的url_rule,根据 rule.endpoint 取出字典 view_functions 中的视图函数,完成对本次 URL 请求的处理。

def dispatch_request(self):    """Does the request dispatching.  Matches the URL and returns the    return value of the view or error handler.  This does not have to    be a response object.  In order to convert the return value to a    proper response object, call :func:`make_response`.    """
    req = _request_ctx_stack.top.request    if req.routing_exception is not None:        self.raise_routing_exception(req)
    rule = req.url_rule    if getattr(rule, 'provide_automatic_options', False) \       and req.method == 'OPTIONS':        return self.make_default_options_response()    return self.view_functions[rule.endpoint](**req.view_args)

3. 生成响应

分发请求完成后,已经取得了返回值,再看下一步是如何做,回到 full_dispatch_request,它调用了 finalize_request。该函数通过 make_response函数,将刚才取得的 rv 生成响应,重新赋值responseself.process_response(response)语句主要处理 after_request,比如关闭数据库连接。

def full_dispatch_request(self):    self.try_trigger_before_first_request_functions()    try:
        request_started.send(self)
        rv = self.preprocess_request()        if rv is None:
            rv = self.dispatch_request()    except Exception as e:
        rv = self.handle_user_exception(e)    return self.finalize_request(rv)def finalize_request(self, rv, from_error_handler=False):
    response = self.make_response(rv)    try:
        response = self.process_response(response)
        request_finished.send(self, response=response)    except Exception:        if not from_error_handler:            raise
        self.logger.exception('Request finalizing failed with an '
                              'error while handling an error')    return response

最后,在wsgi_app 函数中,将从 full_dispatch_request 返回的 response加上参数environ,start_response 并返回给服务器。


以上是关于Flask 源码分析-基本工作流程的主要内容,如果未能解决你的问题,请参考以下文章

flask 请求处理流程及其源代码分析sessioncookie生成源码

Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段

Flask源码流程分析

flask 登录功能流程源码分析