本文主要分析了 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

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

工作流程

当服务器收到 http 请求,调用 Flask 应用时,实际上是调用的 Flask 的 __call__ 方法,从 __call__ 方法的源码可以看出,最终是调用了 wsgi_app 方法并传入 environstart_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 方法,把 appurl_map 绑定到 requestWSGI 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 并返回给服务器。