flask 是如何分发请求的?

发布于:2025-02-27 ⋅ 阅读:(16) ⋅ 点赞:(0)

这篇博客会涉及一些 WSGI 的知识,不了解的可以看这篇博客,简单了解一下。

Python 的 WSGI 简单入门

一、请求在 flask 中的处理过程

我们先来看一下 werkzeug.routing 包下 Map 和 Rule 方法的使用,这里给出一个官方的示例(我进行了一点修改并增加了简单的运行代码):

from werkzeug.routing import Map, Rule, Subdomain, NotFound, RequestRedirect

url_map = Map([
    Rule('/', endpoint='blog/index'),
    Rule('/<int:year>', endpoint='blog/archive'),
    Rule('/<int:year>/<int:month>/', endpoint='blog/archive'),
    Rule('/<int:year>/<int:month>/<int:day>', endpoint='blog/archive'),
    Rule('/<int:year>/<int:month>/<int:day>/<slug>', endpoint='blog/show_post'),
    Rule('/about', endpoint='blog/about_me'),
    Rule('/feeds', endpoint='blog/feeds'),
    Rule('/feeds/<feed_name>.rss', endpoint='blog/show_feed')
])

def application(environ, start_response):
    urls = url_map.bind_to_environ(environ)
    try:
        endpoint, args = urls.match()
    except HTTPException as e:
        return e(environ, start_response)
    start_response('200 OK', [('Content-Type', 'text/plain')])
    rsp = f'Rule points to {endpoint!r} with arguments {args!r}'
    return [rsp.encode('utf-8')] # 直接返回字符串会报错,这里进行一次转换


# provide a basic wsgi for test!
if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    with make_server('', 8000, application) as httpd:
        print("Listening on port 8000....")
        httpd.serve_forever()

flask 的底层也是依赖于 Map 和 Rule,所以我们使用 @route 或者 add_url_rule 最终的目的也是构建类似上面的 url_map 对象,只不过它更加易用。有趣的是,这里并没有 view_func 函数,所以我们是统一返回了 200 OK,不过 urls.match 的参数也表明了我们得到的是 endpoint,这是我们通过它来查找到对应 view_func 的关键信息。

要注意这里的 urls.match() 的返回值中这个 args 是指 url 中的定义的参数,下面是几个示例:

urls = m.bind("example.com", "/")
urls.match("/", "GET")
# ('index', {})
urls.match("/downloads/42")
# ('downloads/show', {'id': 42})

1.1 add_url_rule 方法 和 @route 装饰器

add_url_rule: Connects a URL rule. Works exactly like the :meth:route decorator. If a view_func is provided it will be registered with the endpoint.
连接 URL 规则。其工作原理与 route 装饰器完全相同。如果提供了 view_func 函数,它会被用 endpoint 来注册。

基础示例:

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

等价于以下:

def index():
    pass

app.add_url_rule('/', 'index', index)

如果没有提供 view_func 函数,需要手动绑定 endpointview_func 函数。

app.view_function['index'] = index

在内部,route 方法会调用 add_url_rule 方法。

下面我们来看源码,这里我进行了删减,对于我认为不重要的部分去掉,我认为这样可以节约理解设计思路的脑力。

@setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None,
                    provide_automatic_options=None, **options):
    # 这里上下省略部分代码,只保留我认为关键的代码
    if endpoint is None:
        endpoint = _endpoint_from_view_func(view_func)
    rule = self.url_rule_class(rule, methods=methods, **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 为空,则会使用 view_func 函数的名字,接着使用 add_url_rule 函数的参数创建 Rule 对象,将其加入 self.url_map 中,这是一个 Map 对象。然后会将 endpoint 作为键, view_func 作为值,存入 self.view_functions 中,它是一个 dict 对象。

也就是说我们最终得到了下面两个对象,它们是 Flask 类的两个实例属性。还记得上面的 urls.match 方法吗?当我们获取到 endpoint 后,就可以它为键在 slef.view_functions 中去索引对应的 view_func 函数,然后用它来执行对应路由的请求。

class Flask(_PackageBoundObject):

    def __init__(
        self,
        import_name,
        static_url_path=None,
        static_folder='static',
        static_host=None,
        host_matching=False,
        subdomain_matching=False,
        template_folder='templates',
        instance_path=None,
        instance_relative_config=False,
        root_path=None
    ):
        #: The :class:`~werkzeug.routing.Map` for this instance.
        self.url_map = Map()

        #: A dictionary of all view functions registered. The keys will 
        #: be function names which are also used to generate URLs and 
        #: the values are the function objects themselves.
        #: To register a view function, use the :meth:`route` decorator.
        self.view_functions = {}

route 只是一个方便的装饰器函数,本质上还是调用 add_url_rule 函数。

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'
    """
    def decorator(f):
        endpoint = options.pop('endpoint', None)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
    return decorator

2.1 Flask 中的请求处理过程

我们创建的 Flask 的实例,最终也是类似于上面的 application 被 wsgi 服务调用,只是更加复杂一些,下面就来看看简化的流程:

class Flask(_PackageBoundObject):

    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)

    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.
        """
        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)                          # 弹出,防止积压,造成资源泄漏

    
    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()
        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 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                         # 获取到 url 对象
        # 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':
            return self.make_default_options_response()             # 从 view_function 中找到endpoint对应的
        # otherwise dispatch to the handler for that endpoint       # view_func 函数,通过视图参数调用它并返回结果,
        return self.view_functions[rule.endpoint](**req.view_args)  # 注意这里返回的并非响应对象。


    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 对象,这里就不往下继续了,下面
            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

总结:flask 实例通过请求的 URL 来查找对应的 endpoint,再通过它来查找到对应的视图函数 view_func,然后传入视图函数的参数进行请求处理。在调用视图函数之前,它已经把请求上下文推入了,所以我们在视图函数中可以自由的使用它们,这就是 flask 处理一个请求的大致过程。

关于请求上下文中的全局变量,也就是 request 这些的使用,可以阅读这篇博客:

对 flask 框架中的全局变量 request 探究