flask源码学习-路由的注册与请求处理的过程

Flask源码分析

本文环境python3.5.2,flask-1.0.2。

Flask的路由注册

此时编写的脚本内容如下,

from flask   import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello, World!'

继续分析一下app.route是如何将该路由将hello_world函数对应起来的,

此时查看app.route函数,

def route(self, rule, **options):
    """A decorator that is used to register a view function for a
    given URL rule.  This does the same thing as :meth:`add_url_rule`
    but is intended for decorator usage::

        @app.route('/')
        def index():
            return 'Hello World'

    For more information refer to :ref:`url-route-registrations`.

    :param rule: the URL rule as string
    :param endpoint: the endpoint for the registered URL rule.  Flask
                     itself assumes the name of the view function as
                     endpoint
    :param options: the options to be forwarded to the underlying
                    :class:`~werkzeug.routing.Rule` object.  A change
                    to Werkzeug is handling of method options.  methods
                    is a list of methods this rule should be limited
                    to (``GET``, ``POST`` etc.).  By default a rule
                    just listens for ``GET`` (and implicitly ``HEAD``).
                    Starting with Flask 0.6, ``OPTIONS`` is implicitly
                    added and handled by the standard request handling.
    """
    def decorator(f):
        endpoint = options.pop('endpoint', None)            # 获取传入参数中endpoint字段值
        self.add_url_rule(rule, endpoint, f, **options)     # 添加路由
        return f                                            # 返回传入函数
    return decorator      

由以上装饰器可知,该函数还是原函数直接返回,只是调用了add_url_rule方法将url添加,

@setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None,
                 provide_automatic_options=None, **options):
    """Connects a URL rule.  Works exactly like the :meth:`route`
    decorator.  If a view_func is provided it will be registered with the
    endpoint.

    Basically this example::

        @app.route('/')
        def index():
            pass

    Is equivalent to the following::

        def index():
            pass
        app.add_url_rule('/', 'index', index)

    If the view_func is not provided you will need to connect the endpoint
    to a view function like so::

        app.view_functions['index'] = index

    Internally :meth:`route` invokes :meth:`add_url_rule` so if you want
    to customize the behavior via subclassing you only need to change
    this method.

    For more information refer to :ref:`url-route-registrations`.

    .. versionchanged:: 0.2
       `view_func` parameter added.

    .. versionchanged:: 0.6
       ``OPTIONS`` is added automatically as method.

    :param rule: the URL rule as string
    :param endpoint: the endpoint for the registered URL rule.  Flask
                     itself assumes the name of the view function as
                     endpoint
    :param view_func: the function to call when serving a request to the
                      provided endpoint
    :param provide_automatic_options: controls whether the ``OPTIONS``
        method should be added automatically. This can also be controlled
        by setting the ``view_func.provide_automatic_options = False``
        before adding the rule.
    :param options: the options to be forwarded to the underlying
                    :class:`~werkzeug.routing.Rule` object.  A change
                    to Werkzeug is handling of method options.  methods
                    is a list of methods this rule should be limited
                    to (``GET``, ``POST`` etc.).  By default a rule
                    just listens for ``GET`` (and implicitly ``HEAD``).
                    Starting with Flask 0.6, ``OPTIONS`` is implicitly
                    added and handled by the standard request handling.
    """
    if endpoint is None:                                                        # 传入是否为空
        endpoint = _endpoint_from_view_func(view_func)                          # 获取函数名称
    options['endpoint'] = endpoint                                              # 将该函数名称设置到选项中
    methods = options.pop('methods', None)                                      # 从传入值中获取methods,即该方法的访问方法GET,POST等

    # if the methods are not given and the view_func object knows its
    # methods we can use that instead.  If neither exists, we go with
    # a tuple of only ``GET`` as default.
    if methods is None:                                                         # 如果为空
        methods = getattr(view_func, 'methods', None) or ('GET',)               # 获取传入方法的属性methods如果为空则默认为get方法
    if isinstance(methods, string_types):                                       # 判断methods是否是可迭代的
        raise TypeError('Allowed methods have to be iterables of strings, '
                        'for example: @app.route(..., methods=["POST"])')
    methods = set(item.upper() for item in methods)                             # 获取方法的大小集合

    # Methods that should always be added
    required_methods = set(getattr(view_func, 'required_methods', ()))          # 获取函数的required_methods属性默认为空

    # starting with Flask 0.8 the view_func object can disable and
    # force-enable the automatic options handling.
    if provide_automatic_options is None:                                       # 如果该值为空
        provide_automatic_options = getattr(view_func,
            'provide_automatic_options', None)                                  # 获取该传入函数对应的provide_automatic_options属性

    if provide_automatic_options is None:                                       # 如果为空
        if 'OPTIONS' not in methods:                                            # 该方法不再methods中
            provide_automatic_options = True                                    # 设置成True
            required_methods.add('OPTIONS')                                     # 添加该方法
        else:
            provide_automatic_options = False                                   # 设置为False

    # Add the required methods now.
    methods |= required_methods                                                 # 获取所有方法

    rule = self.url_rule_class(rule, methods=methods, **options)                # 初始化Rule实例 
    rule.provide_automatic_options = provide_automatic_options                  # 属性值

    self.url_map.add(rule)                                                      # 将该实例添加到url_map中
    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                               # 设置进字典中

其中,在调用self.url_map.add(rule)方法时,会执行如下操作,

def add(self, rulefactory):
    """Add a new rule or factory to the map and bind it.  Requires that the
    rule is not bound to another map.

    :param rulefactory: a :class:`Rule` or :class:`RuleFactory`
    """
    for rule in rulefactory.get_rules(self):                                    # 获取rule
        rule.bind(self)                                                         # 调用rule的bind方法
        self._rules.append(rule)                                                # 添加到_rules列表中
        self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
    self._remap = True                                                          # 设置是否需要重新排序标志

其中调用的rule的bind方法,就是将传入的url进行正则匹配的相关处理,该函数的执行流程相对繁琐,大家可以自行分析。
此时路由的注册工作就已经完成,接下来就分析请求到来后的处理。

Flask的请求处理

在上文文末,已经分析到当请求到来后的处理流程会调用Flask实例的self.wsgi_app(environ, start_response)方法,继续查看该方法,

def wsgi_app(self, environ, start_response):
    """The actual WSGI application. This is not implemented in
    :meth:`__call__` so that middlewares can be applied without
    losing a reference to the app object. Instead of doing this::

        app = MyMiddleware(app)

    It's a better idea to do this instead::

        app.wsgi_app = MyMiddleware(app.wsgi_app)

    Then you still have the original application object around and
    can continue to call methods on it.

    .. versionchanged:: 0.7
        Teardown events for the request and app contexts are called
        even if an unhandled error occurs. Other events may not be
        called depending on when an error occurs during dispatch.
        See :ref:`callbacks-and-errors`.

    :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)                     # 初始化请求上下文
    error = None
    try:
        try:
            ctx.push()                                      
            response = self.full_dispatch_request()         # 处理请求
        except Exception as e:
            error = e
            response = self.handle_exception(e)             # 如果处理出错则包装该错误
        except:
            error = sys.exc_info()[1]
            raise
        return response(environ, start_response)            # 返回数据
    finally:
        if self.should_ignore_error(error):
            error = None
        ctx.auto_pop(error)

此时,会先调用request_context方法,

def request_context(self, environ):
    """Create a :class:`~flask.ctx.RequestContext` representing a
    WSGI environment. Use a ``with`` block to push the context,
    which will make :data:`request` point at this request.

    See :doc:`/reqcontext`.

    Typically you should not call this from your own code. A request
    context is automatically pushed by the :meth:`wsgi_app` when
    handling a request. Use :meth:`test_request_context` to create
    an environment and context instead of this method.

    :param environ: a WSGI environment
    """
    return RequestContext(self, environ)

返回一个RequestContext实例,此时查看该类的初始化过程,

def __init__(self, app, environ, request=None):
    self.app = app                                              # 传入的app
    if request is None:                                         # 如果传入的request为空
        request = app.request_class(environ)                    # 调用app的request_class将environ做参数传入并实例化
    self.request = request                                      # 设置请求
    self.url_adapter = app.create_url_adapter(self.request)     # 调用app的create_url_adapter方法
    self.flashes = None
    self.session = None

    # Request contexts can be pushed multiple times and interleaved with
    # other request contexts.  Now only if the last level is popped we
    # get rid of them.  Additionally if an application context is missing
    # one is created implicitly so for each level we add this information
    self._implicit_app_ctx_stack = []

    # indicator if the context was preserved.  Next time another context
    # is pushed the preserved context is popped.
    self.preserved = False

    # remembers the exception for pop if there is one in case the context
    # preservation kicks in.
    self._preserved_exc = None

    # Functions that should be executed after the request on the response
    # object.  These will be called before the regular "after_request"
    # functions.
    self._after_request_functions = []

    self.match_request()                                        # 匹配请求

此时如果没有传入request则先初始化request,然后调用app的create_url_adapter方法,创建url_adapter来匹配request中的url,最后调用调用match_request方法去寻找最佳匹配的url,此时create_url_adapter如下,

def create_url_adapter(self, request):
    """Creates a URL adapter for the given request. The URL adapter
    is created at a point where the request context is not yet set
    up so the request is passed explicitly.

    .. versionadded:: 0.6

    .. versionchanged:: 0.9
       This can now also be called without a request object when the
       URL adapter is created for the application context.

    .. versionchanged:: 1.0
        :data:`SERVER_NAME` no longer implicitly enables subdomain
        matching. Use :attr:`subdomain_matching` instead.
    """
    if request is not None:                                             # 传入的request不为空空
        # If subdomain matching is disabled (the default), use the
        # default subdomain in all cases. This should be the default
        # in Werkzeug but it currently does not have that feature.
        subdomain = ((self.url_map.default_subdomain or None)
                     if not self.subdomain_matching else None)          # 子域名
        return self.url_map.bind_to_environ(
            request.environ,
            server_name=self.config['SERVER_NAME'],
            subdomain=subdomain)                                        # 由于调用Map的bind_to_environ方法
    # We need at the very least the server name to be set for this
    # to work.
    if self.config['SERVER_NAME'] is not None:
        return self.url_map.bind(
            self.config['SERVER_NAME'],
            script_name=self.config['APPLICATION_ROOT'],
            url_scheme=self.config['PREFERRED_URL_SCHEME'])

由于url_map是一个Map实例,此时调用了Map的bind_to_environ方法,

def bind_to_environ(self, environ, server_name=None, subdomain=None):
    ...
    ...
    return Map.bind(self, server_name, script_name,
                    subdomain, environ['wsgi.url_scheme'],
                    environ['REQUEST_METHOD'], path_info,
                    query_args=query_args)

在进过对environ的传入参数进行相关处理后,就调用了Map的bind方法,

def bind(self, server_name, script_name=None, subdomain=None,
         url_scheme='http', default_method='GET', path_info=None,
         query_args=None):
    """Return a new :class:`MapAdapter` with the details specified to the
    call.  Note that `script_name` will default to ``'/'`` if not further
    specified or `None`.  The `server_name` at least is a requirement
    because the HTTP RFC requires absolute URLs for redirects and so all
    redirect exceptions raised by Werkzeug will contain the full canonical
    URL.

    If no path_info is passed to :meth:`match` it will use the default path
    info passed to bind.  While this doesn't really make sense for
    manual bind calls, it's useful if you bind a map to a WSGI
    environment which already contains the path info.

    `subdomain` will default to the `default_subdomain` for this map if
    no defined. If there is no `default_subdomain` you cannot use the
    subdomain feature.

    .. versionadded:: 0.7
       `query_args` added

    .. versionadded:: 0.8
       `query_args` can now also be a string.
    """
    server_name = server_name.lower()
    if self.host_matching:
        if subdomain is not None:
            raise RuntimeError('host matching enabled and a '
                               'subdomain was provided')
    elif subdomain is None:
        subdomain = self.default_subdomain
    if script_name is None:
        script_name = '/'
    try:
        server_name = _encode_idna(server_name)
    except UnicodeError:
        raise BadHost()
    return MapAdapter(self, server_name, script_name, subdomain,
                      url_scheme, path_info, default_method, query_args)

该方法返回了一个MapAdapter实例,所以在RequestContext实例中的url_adapter就是一个MapAdapter实例。此时回到RequestContext在实例化的时候调用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                        # 设置request属性
    except HTTPException as e:
        self.request.routing_exception = e                      # 如出错则设置匹配不到路由

此时调用了MapAdapter实例的match方法,

def match(self, path_info=None, method=None, return_rule=False,
          query_args=None):
    """The usage is simple: you just pass the match method the current
    path info as well as the method (which defaults to `GET`).  The
    following things can then happen:

    - you receive a `NotFound` exception that indicates that no URL is
      matching.  A `NotFound` exception is also a WSGI application you
      can call to get a default page not found page (happens to be the
      same object as `werkzeug.exceptions.NotFound`)

    - you receive a `MethodNotAllowed` exception that indicates that there
      is a match for this URL but not for the current request method.
      This is useful for RESTful applications.

    - you receive a `RequestRedirect` exception with a `new_url`
      attribute.  This exception is used to notify you about a request
      Werkzeug requests from your WSGI application.  This is for example the
      case if you request ``/foo`` although the correct URL is ``/foo/``
      You can use the `RequestRedirect` instance as response-like object
      similar to all other subclasses of `HTTPException`.

    - you get a tuple in the form ``(endpoint, arguments)`` if there is
      a match (unless `return_rule` is True, in which case you get a tuple
      in the form ``(rule, arguments)``)

    If the path info is not passed to the match method the default path
    info of the map is used (defaults to the root URL if not defined
    explicitly).

    All of the exceptions raised are subclasses of `HTTPException` so they
    can be used as WSGI responses. They will all render generic error or
    redirect pages.

    Here is a small example for matching:

    >>> m = Map([
    ...     Rule('/', endpoint='index'),
    ...     Rule('/downloads/', endpoint='downloads/index'),
    ...     Rule('/downloads/<int:id>', endpoint='downloads/show')
    ... ])
    >>> urls = m.bind("example.com", "/")
    >>> urls.match("/", "GET")
    ('index', {})
    >>> urls.match("/downloads/42")
    ('downloads/show', {'id': 42})

    And here is what happens on redirect and missing URLs:

    >>> urls.match("/downloads")
    Traceback (most recent call last):
      ...
    RequestRedirect: http://example.com/downloads/
    >>> urls.match("/missing")
    Traceback (most recent call last):
      ...
    NotFound: 404 Not Found

    :param path_info: the path info to use for matching.  Overrides the
                      path info specified on binding.
    :param method: the HTTP method used for matching.  Overrides the
                   method specified on binding.
    :param return_rule: return the rule that matched instead of just the
                        endpoint (defaults to `False`).
    :param query_args: optional query arguments that are used for
                       automatic redirects as string or dictionary.  It's
                       currently not possible to use the query arguments
                       for URL matching.

    .. versionadded:: 0.6
       `return_rule` was added.

    .. versionadded:: 0.7
       `query_args` was added.

    .. versionchanged:: 0.8
       `query_args` can now also be a string.
    """
    self.map.update()                                                       # 更新数据,重新排序传入rules
    if path_info is None:                                                   # 如果传入为空
        path_info = self.path_info                                          # 则使用初始化时的path_info
    else:
        path_info = to_unicode(path_info, self.map.charset)
    if query_args is None:                                                  # 如果传入查询参数为空
        query_args = self.query_args                                        # 使用初始化值
    method = (method or self.default_method).upper()                        # 将方法转为大写

    path = u'%s|%s' % (
        self.map.host_matching and self.server_name or self.subdomain,
        path_info and '/%s' % path_info.lstrip('/')
    )

    have_match_for = set()
    for rule in self.map._rules:                                            # 遍历rules
        try:
            rv = rule.match(path, method)                                   # 调用rule的match方法
        except RequestSlash:
            raise RequestRedirect(self.make_redirect_url(
                url_quote(path_info, self.map.charset,
                          safe='/:|+') + '/', query_args))
        except RequestAliasRedirect as e:
            raise RequestRedirect(self.make_alias_redirect_url(
                path, rule.endpoint, e.matched_values, method, query_args))
        if rv is None:                                                      # 如果没有匹配上则直接下一个
            continue
        if rule.methods is not None and method not in rule.methods:         # 如果rule的方法不为空,并且当前使用的方法不再rule的方法列表中
            have_match_for.update(rule.methods)                             # 设置rule的方法列表
            continue                                                        # 继续下一个

        if self.map.redirect_defaults:                                      # 如果默认重定向有值
            redirect_url = self.get_default_redirect(rule, method, rv,
                                                     query_args)
            if redirect_url is not None:
                raise RequestRedirect(redirect_url)                         # 重定向

        if rule.redirect_to is not None:                                    # 如果rule重定向不为空
            if isinstance(rule.redirect_to, string_types): 
                def _handle_match(match):
                    value = rv[match.group(1)]
                    return rule._converters[match.group(1)].to_url(value)
                redirect_url = _simple_rule_re.sub(_handle_match,
                                                   rule.redirect_to)
            else:
                redirect_url = rule.redirect_to(self, **rv)
            raise RequestRedirect(str(url_join('%s://%s%s%s' % (
                self.url_scheme or 'http',
                self.subdomain and self.subdomain + '.' or '',
                self.server_name,
                self.script_name
            ), redirect_url)))                                              # 重定向

        if return_rule:                                                     # 如果需要返回rule则返回rule,和rv
            return rule, rv
        else:
            return rule.endpoint, rv                                        # 否则返回rule的endpoint和rv

    if have_match_for:
        raise MethodNotAllowed(valid_methods=list(have_match_for))          # 如果匹配上了但是方法不允许则报错
    raise NotFound()                                                        # 报404not found

其中调用的rule.match方法就是将在url注册时候进行的正则等处理进行相关匹配回来,该函数大家可自行分析。当url匹配完成后,此时继续执行wsgi_app函数中的ctx.push()函数,

def push(self):
    """Binds the request context to the current context."""
    # If an exception occurs in debug mode or if context preservation is
    # activated under exception situations exactly one context stays
    # on the stack.  The rationale is that you want to access that
    # information under debug situations.  However if someone forgets to
    # pop that context again we want to make sure that on the next push
    # it's invalidated, otherwise we run at risk that something leaks
    # memory.  This is usually only a problem in test suite since this
    # functionality is not active in production environments.
    top = _request_ctx_stack.top                                # _request_ctx_stack是个根据不同线程不同保存数据的栈
    if top is not None and top.preserved:                       # 如果不为空并且top.preserved为true则表示上一请求已经处理完成
        top.pop(top._preserved_exc)                             # 移除

    # Before we push the request context we have to ensure that there
    # is an application context.
    app_ctx = _app_ctx_stack.top                                # 获取_app_ctx_stack与线程相关的数据栈,获取栈顶
    if app_ctx is None or app_ctx.app != self.app:              # 如果为空或者当前的app上下文与当前上下文不同
        app_ctx = self.app.app_context()                        # 将app_ctx设置为当前app的上下文
        app_ctx.push()                                          # 将当前app_ctx入栈
        self._implicit_app_ctx_stack.append(app_ctx)            
    else:
        self._implicit_app_ctx_stack.append(None)

    if hasattr(sys, 'exc_clear'):                               
        sys.exc_clear()

    _request_ctx_stack.push(self)                               # 在_request_ctx_stack入栈

    # Open the session at the moment that the request context is available.
    # This allows a custom open_session method to use the request context.
    # Only open a new session if this is the first time the request was
    # pushed, otherwise stream_with_context loses the session.
    if self.session is None:                                    # 如果session为空
        session_interface = self.app.session_interface          # 获取SecureCookieSessionInterface实例
        self.session = session_interface.open_session(
            self.app, self.request
        )                                                       # 打开session

        if self.session is None:                                # 如果为空
            self.session = session_interface.make_null_session(self.app)  # 设置一个空的会话

其中_request_ctx_stack和_app_ctx_stack,

class LocalStack(object):

    def __init__(self):
        self._local = Local()

    def __release_local__(self):
        self._local.__release_local__()

    def _get__ident_func__(self):
        return self._local.__ident_func__

    def _set__ident_func__(self, value):
        object.__setattr__(self._local, '__ident_func__', value)
    __ident_func__ = property(_get__ident_func__, _set__ident_func__)
    del _get__ident_func__, _set__ident_func__

    def __call__(self):
        def _lookup():
            rv = self.top
            if rv is None:
                raise RuntimeError('object unbound')
            return rv
        return LocalProxy(_lookup)

    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None

从该类可看出,该类的操作都是基于初始化时传入的Local()来操作的,该类如下,

class Local(object):
    __slots__ = ('__storage__', '__ident_func__')

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)

    def __iter__(self):
        return iter(self.__storage__.items())

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

该这的操作都是根据线程id设置相关的操作值,保证了各个线程之间的数据独立不干扰。
在push函数中上下文处理完成后,然后就是session的相关操作,flask默认通过加密的形式将所有的数据添加到cookie中,然后再下次请求的时候将cookie中的数据进行解密获取上次的数据,session的具体分析如有机会后续会分析,

此刻,回到wsgi_app函数中,接下将要执行response = self.full_dispatch_request() ,

def full_dispatch_request(self):
    """Dispatches the request and on top of that performs request
    pre and postprocessing as well as HTTP exception catching and
    error handling.

    .. versionadded:: 0.7
    """
    self.try_trigger_before_first_request_functions()                   # 根据_got_first_request标志判断执行相关配置函数
    try:
        request_started.send(self) 
        rv = self.preprocess_request()                                  # 处理请求url
        if rv is None:                                                  # 如果返回值为空
            rv = self.dispatch_request()                                # 调用该方法处理
    except Exception as e:
        rv = self.handle_user_exception(e)                              # 如果执行出错则带哦用该方法
    return self.finalize_request(rv)                                    # 结束请求

此时首先查看self.try_trigger_before_first_request_functions方法的执行过程,

def try_trigger_before_first_request_functions(self):
    """Called before each request and will ensure that it triggers
    the :attr:`before_first_request_funcs` and only exactly once per
    application instance (which means process usually).

    :internal:
    """
    if self._got_first_request:                             # 检查该标志
        return
    with self._before_request_lock:                         # 加锁
        if self._got_first_request:                         # 再次检查
            return
        for func in self.before_first_request_funcs:        # 获取在列表中的函数并依次执行
            func()
        self._got_first_request = True                      # 将该标志置为True,确保只执行依次

执行了第一次执行时,需要执行的相关的回调方法,然后继续调用self.preprocess_request函数,

def preprocess_request(self):
    """Called before the request is dispatched. Calls
    :attr:`url_value_preprocessors` registered with the app and the
    current blueprint (if any). Then calls :attr:`before_request_funcs`
    registered with the app and the blueprint.

    If any :meth:`before_request` handler returns a non-None value, the
    value is handled as if it was the return value from the view, and
    further request handling is stopped.
    """

    bp = _request_ctx_stack.top.request.blueprint                       # 获取request属性的blueprint

    funcs = self.url_value_preprocessors.get(None, ())                  # 获取注册在dispatched之前的函数
    if bp is not None and bp in self.url_value_preprocessors:           # 检查bp是否为空,并且该函数对应的key是否存在字典中
        funcs = chain(funcs, self.url_value_preprocessors[bp])          # 加入函数列表中
    for func in funcs:
        func(request.endpoint, request.view_args)                       # 依次遍历执行该函数

    funcs = self.before_request_funcs.get(None, ())                     # 获取在执行request之前的函数列表
    if bp is not None and bp in self.before_request_funcs:              
        funcs = chain(funcs, self.before_request_funcs[bp])
    for func in funcs:                                                  # 依次遍历
        rv = func()                                                     # 执行
        if rv is not None:                                              # 如果执行结果不为空
            return rv                                                   # 则直接返回执行结果

该函数处理的类似于Django框架中的中间件功能类似,在处理请求之前执行相关注册的函数,此时继续分析self.dispatch_request()函数,

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`.

    .. versionchanged:: 0.7
       This no longer does the exception handling, this code was
       moved to the new :meth:`full_dispatch_request`.
    """
    req = _request_ctx_stack.top.request                                # 获取request
    if req.routing_exception is not None:                               # 如果路由匹配出错
        self.raise_routing_exception(req)                               # 抛出错误
    rule = req.url_rule                                                 # 获取匹配成功的rule实例
    # if we provide automatic options for this URL and the
    # request came with the OPTIONS method, reply automatically
    if getattr(rule, 'provide_automatic_options', False) \
       and req.method == 'OPTIONS':                                     # 如果provide_automatic_options设置为True并且方法为OPTIONS
        return self.make_default_options_response()                     # 调用默认回复
    # otherwise dispatch to the handler for that endpoint
    return self.view_functions[rule.endpoint](**req.view_args)          # 调用在路由注册时候添加的函数

此时就执行了,hello_world函数并返回相应结果,然后回到full_dispatch_request函数继续执行self.finalize_request(rv) 函数,

def finalize_request(self, rv, from_error_handler=False):
    """Given the return value from a view function this finalizes
    the request by converting it into a response and invoking the
    postprocessing functions.  This is invoked for both normal
    request dispatching as well as error handlers.

    Because this means that it might be called as a result of a
    failure a special safe mode is available which can be enabled
    with the `from_error_handler` flag.  If enabled, failures in
    response processing will be logged and otherwise ignored.

    :internal:
    """
    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                                                     # 返回结果

继续查看make_response函数,

def make_response(self, rv):
    """Convert the return value from a view function to an instance of
    :attr:`response_class`.

    :param rv: the return value from the view function. The view function
        must return a response. Returning ``None``, or the view ending
        without returning, is not allowed. The following types are allowed
        for ``view_rv``:

        ``str`` (``unicode`` in Python 2)
            A response object is created with the string encoded to UTF-8
            as the body.

        ``bytes`` (``str`` in Python 2)
            A response object is created with the bytes as the body.

        ``tuple``
            Either ``(body, status, headers)``, ``(body, status)``, or
            ``(body, headers)``, where ``body`` is any of the other types
            allowed here, ``status`` is a string or an integer, and
            ``headers`` is a dictionary or a list of ``(key, value)``
            tuples. If ``body`` is a :attr:`response_class` instance,
            ``status`` overwrites the exiting value and ``headers`` are
            extended.

        :attr:`response_class`
            The object is returned unchanged.

        other :class:`~werkzeug.wrappers.Response` class
            The object is coerced to :attr:`response_class`.

        :func:`callable`
            The function is called as a WSGI application. The result is
            used to create a response object.

    .. versionchanged:: 0.9
       Previously a tuple was interpreted as the arguments for the
       response object.
    """

    status = headers = None                                                     # 状态码和头部信息置空

    # unpack tuple returns
    if isinstance(rv, tuple):                                                   # 检查是否是元组
        len_rv = len(rv)                                                        # 获取元组长度

        # a 3-tuple is unpacked directly
        if len_rv == 3:                                                         # 如果是三个值
            rv, status, headers = rv                                            # 直接解析为返回结果,状态码,头部信息
        # decide if a 2-tuple has status or headers
        elif len_rv == 2:                                                       # 如果长度是2
            if isinstance(rv[1], (Headers, dict, tuple, list)):                 # 检查rv[1]的类型
                rv, headers = rv                                                # 将rv解析为返回值,头部数据
            else:
                rv, status = rv                                                 # 否则解析成返回值,状态码
        # other sized tuples are not allowed
        else:
            raise TypeError(
                'The view function did not return a valid response tuple.'
                ' The tuple must have the form (body, status, headers),'
                ' (body, status), or (body, headers).'
            )

    # the body must not be None
    if rv is None:                                                              # 如果返回数据为空则报错
        raise TypeError(
            'The view function did not return a valid response. The'
            ' function either returned None or ended without a return'
            ' statement.'
        )

    # make sure the body is an instance of the response class
    if not isinstance(rv, self.response_class):                                 # 如果rv不是Response类
        if isinstance(rv, (text_type, bytes, bytearray)):                       # 判断rv类型
            # let the response class set the status and headers instead of
            # waiting to do it manually, so that the class can handle any
            # special logic
            rv = self.response_class(rv, status=status, headers=headers)        # 传入参数获得Response实例
            status = headers = None                                             # 将头部和返回码置空
        else:
            # evaluate a WSGI callable, or coerce a different response
            # class to the correct type
            try:
                rv = self.response_class.force_type(rv, request.environ)        # 强制将相关数据转换为Response实例
            except TypeError as e:
                new_error = TypeError(
                    '{e}\nThe view function did not return a valid'
                    ' response. The return type must be a string, tuple,'
                    ' Response instance, or WSGI callable, but it was a'
                    ' {rv.__class__.__name__}.'.format(e=e, rv=rv)
                )
                reraise(TypeError, new_error, sys.exc_info()[2])

    # prefer the status if it was provided
    if status is not None:                                                      # 如果状态码不为空
        if isinstance(status, (text_type, bytes, bytearray)):                   # 判断状态码类型
            rv.status = status                                                  # 直接设置状态码
        else:
            rv.status_code = status                                             # 设置状态值

    # extend existing headers with provided headers
    if headers:
        rv.headers.extend(headers)                                              # 如果headers还有值则直接添加到rv.headers中

    return rv                                                                   # 返回response实例

该函数就是根据传入的需要返回的数据的类型,包装成一个Response的实例,然后返回该实例,该函数执行完成后,返回finalize_request函数继续执行process_response函数,

def process_response(self, response):
    """Can be overridden in order to modify the response object
    before it's sent to the WSGI server.  By default this will
    call all the :meth:`after_request` decorated functions.

    .. versionchanged:: 0.5
       As of Flask 0.5 the functions registered for after request
       execution are called in reverse order of registration.

    :param response: a :attr:`response_class` object.
    :return: a new response object or the same, has to be an
             instance of :attr:`response_class`.
    """
    ctx = _request_ctx_stack.top                                        # 获取当前请求上下文
    bp = ctx.request.blueprint                                          # 获取设置的blueprint
    funcs = ctx._after_request_functions                                # 获取上下文中该函数列表
    if bp is not None and bp in self.after_request_funcs:               # 检查该函数是否在列表中
        funcs = chain(funcs, reversed(self.after_request_funcs[bp]))    # 添加到funcs中
    if None in self.after_request_funcs:
        funcs = chain(funcs, reversed(self.after_request_funcs[None]))
    for handler in funcs:                                               # 依次遍历函数列表
        response = handler(response)                                    # 将response作为参数,依次执行函数
    if not self.session_interface.is_null_session(ctx.session):         # 判断是否是null的session
        self.session_interface.save_session(self, ctx.session, response)    # 如果不是则保存相应数据到cookie中,等待下次访问时带上来
    return response    

在返回response之前,获取在返回结果之前需要执行的相关函数列表,然后依次执行,然后判断session是否需要保存,如要保存则存在cookie中,然后返回response,此刻full_dispatch_request函数就执行完。

返回到wsgi_app函数中执行,response(environ, start_response) 将处理返回的数据返回回去。当数据返回成功后,还将目前的上下文自动弹出,至此,一个响应的完成请求就完成了。

本文总结

本文主要讲述了flask框架的helloworld程序的路由的注册过程,并分析了在请求到来后的路由的匹配,相关注册函数的注册的流程,执行完成后并将相关结果返回的大概流程。

猜你喜欢

转载自blog.csdn.net/qq_33339479/article/details/81042351