flask 是如何分发请求的?

news/2025/2/27 7:03:48

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

Python 的 WSGI 简单入门

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

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

python">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 中的定义的参数,下面是几个示例:

python">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 来注册。

基础示例:

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

等价于以下:

python">def index():
    pass

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

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

python">app.view_function['index'] = index

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

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

python">@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 函数,然后用它来执行对应路由的请求。

python">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 函数。

python">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 服务调用,只是更加复杂一些,下面就来看看简化的流程:

python">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 探究


http://www.niftyadmin.cn/n/5869692.html

相关文章

构建逻辑思维链(CoT)为金融AI消除幻觉(保险理赔篇)

在上一篇文章中&#xff0c;我们介绍了如何利用亚马逊云科技的Amazon Bedrock GuardRails自动推理检查为金融行业的AI应用提升准确性&#xff0c;消除幻觉。在本案例中&#xff0c;我们将探讨一个利用AI副主保险公司评估长期护理保险理赔审核的场景。 自动推理检查配置 在本方…

Zama fhEVM应用:摩根大通旗下 Kinexys 发布概念验证

1. 引言 Zama 全同态加密 (FHE) 技术在摩根大通的 Kinexys&#xff08;以前称为 Onyx&#xff09;中成功进行了概念验证。该概念验证是“EPIC 项目&#xff1a;通过链上企业隐私、身份和可组合性推动代币化金融”的一部分&#xff0c;在 Kinexys 数字资产沙盒&#xff08;以前…

性能测试丨JMeter 分布式加压机制

JMeter 的分布式加压机制允许在多台机器上同时运行测试&#xff0c;以模拟更高的负载。以下是其工作原理和配置步骤&#xff1a; 1. 分布式架构 主节点&#xff08;Controller&#xff09;&#xff1a;负责管理测试计划、分发任务和收集结果。从节点&#xff08;Slave&#x…

排序(数据结构篇)

排序 朴素快排的缺陷&#xff1a; 1.基准元素选择不当&#xff0c;递归层数会增加&#xff0c;时间复杂度变高 2.当有大量重复元素时&#xff0c;递归层数也会增加 如果有一个表达式 (x y) >> 1 它的意思就是先将整数x和y相加&#xff0c;然后将结果右移一位。 这实际…

【面试】Java 之 String 系列 -- String 为什么不可变?

在 Java 编程中&#xff0c;String 类是一个使用频率极高的类。而 String 对象具有不可变的特性&#xff0c;这一特性在 Java 设计中有着重要的意义。本文将深入探讨 String 不可变的含义、原因以及带来的好处。 一、String 不可变的含义 1. 概念解释 所谓 String 不可变&am…

为什么办公电脑需要使用企业级杀毒软件?--火绒企业版V2.0

首先&#xff0c;办公电脑处在一个网络连接和数据传输频繁的环境中&#xff0c;员工经常会接收和发送电子邮件、浏览网页、下载文件等&#xff0c;因此存在着各种网络安全威胁的风险。 其次&#xff0c;企业办公电脑作为业务运营的关键枢纽&#xff0c;存储着大量商业机密、客…

【Java企业生态系统的演进】从单体J2EE到云原生微服务

Java企业生态系统的演进&#xff1a;从单体J2EE到云原生微服务 目录标题 Java企业生态系统的演进&#xff1a;从单体J2EE到云原生微服务摘要1. 引言2. 整体框架演进&#xff1a;从原始Java到Spring Cloud2.1 原始Java阶段&#xff08;1995-1999&#xff09;2.2 J2EE阶段&#x…

总结一下Java中的Synchronized同步锁的常见面试题

部分内容来源&#xff1a;JavaGuide Synchronized是什么&#xff1f;有什么用 Synchronized是同步的意思&#xff0c;主要解决多个线程之间访问资源的同步性&#xff0c;是一个同步锁 我们会真的把我们的资源给锁住 保证被他修饰的资源或代码块在任意时刻只能有一个线程来执…