flask 是如何分发请求的?

这篇博客会涉及一些 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 探究

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/978527.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络————(一)HTTP讲解

基础内容分类 从TCP/IP协议栈为依托&#xff0c;由上至下、从应用层到基础设施介绍协议。 1.应用层&#xff1a; HTTP/1.1 Websocket HTTP/2.0 2.应用层的安全基础设施 LTS/SSL 3.传输层 TCP 4.网络层及数据链路层 IP层和以太网 HTTP协议 网络页面形成基本 流程&#xff1a…

源码压缩包泄露

##解题思路 因为网站的文件都放在www下面&#xff0c;所以直接访问/www.zip就可以得到网页的源码压缩包 在fl000g.txt这个文件中看到一个flag{flag_here}不像是真的flag&#xff0c;尝试提交ctfshow{flag_here}&#xff0c;果然提交失败 打开文件属性之类的&#xff0c;也没有…

组态软件在物联网中的应用

随着物联网的快速发展&#xff0c;组态软件在物联网中的应用也越来越广泛。组态软件是一种用于创建和管理物联网系统的可视化工具&#xff0c;它能够将传感器、设备和网络连接起来&#xff0c;实现数据的采集、分析和可视化。本文将探讨组态软件在物联网中的应用&#xff0c;并…

Java+SpringBoot+Vue+数据可视化的音乐推荐与可视化平台(程序+论文+讲解+安装+调试+售后)

感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望帮助更多的人。 系统介绍 在互联网技术以日新月异之势迅猛发展的浪潮下&#xff0c;5G 通信技术的普及、云计算能力…

(论文)PartialSpoof 数据库和检测话语中嵌入的短假语音片段的对策

The PartialSpoof Database and Countermeasures for the Detection of Short Fake Speech Segments Embedded in an Utterance 摘要 自动说话人验证容易受到各种作和欺骗&#xff0c;例如文本到语音合成、语音转换、重放、篡改、对抗性攻击等。我们考虑一种称为“部分欺骗”…

Leaflet介绍及使用示例

一、Leaflet介绍 Leaflet是一个开源的JavaScript库&#xff0c;专门用于构建交互式的地图应用程序。它以其轻量级、高性能和易于使用的API而著称&#xff0c;方便开发者在网页中集成地图功能。Leaflet支持多种地图提供商的瓦片图层&#xff0c;如OpenStreetMap、Mapbox等&…

【笔记】redis回忆录(未完 重头过一遍)

了解 redis在linux上运行 没有window版本 有也是微软自己搞的 &#xff08;一&#xff09;安装与修改配置 1.在linux虚拟机上 安装gcc依赖 然后再usr/local/src解压在官网下载好的redis安装包 直接拖进去 tar -zxvf 安装包名字 tab键补齐 解压成功 进入软件 并执行编译命令…

使用 Apache Dubbo 释放 DeepSeek R1 的全部潜力

作者&#xff1a;陈子康&#xff0c;Apache Dubbo Contributor 2025年1月20日&#xff0c;国产大模型公司深度求索&#xff08;DeepSeek&#xff09;正式发布了大语言模型 DeepSeek-R1&#xff0c;并同步开源其模型权重。通过大规模强化学习技术&#xff0c;DeepSeek-R1 显著提…

Unity TMPro显示中文字体

TMP默认的字体只能显示英语&#xff0c;那么怎么显示中文呢 1、找到支持中文的字体文件 在c盘搜索Fonts文件夹有很多支持中文的字体文件 我这里选择雅黑 PS.双击打开发现里面有粗体细体普通三个版本&#xff0c;也可以只导入一个版本进去 2、将其拖入到unity Assets里面 3…

【MySQL篇】数据库基础

目录 1&#xff0c;什么是数据库&#xff1f; 2&#xff0c;主流数据库 3&#xff0c;MySQL介绍 1&#xff0c;MySQL架构 2&#xff0c;SQL分类 3&#xff0c;MySQL存储引擎 1&#xff0c;什么是数据库&#xff1f; 数据库&#xff08;Database&#xff0c;简称DB&#xf…

Linux 日志系统·

目录 一、前言 二、实现一个简单的日志 1.可变参数 2.日志等级 3.日志时间 4.打印每一条参数 5.合并两个缓冲区 6.封装日志函数 三、完整代码 一、前言 当我们写一个函数&#xff0c;例如打开文件open&#xff0c;当我们打开失败的时候&#xff0c;会调用perror看到错误…

【PromptCoder】使用 package.json 生成 cursorrules

【PromptCoder】使用 package.json 生成 cursorrules 在当今快节奏的开发世界中&#xff0c;效率和准确性至关重要。开发者们不断寻找能够优化工作流程、帮助他们更快编写高质量代码的工具。Cursor 作为一款 AI 驱动的代码编辑器&#xff0c;正在彻底改变我们的编程方式。但如…

【VUE】vue-i18n: Uncaught SyntaxError: Not available in legacy mode

报错&#xff1a; 解决方法&#xff1a; 找到 createI18n 并加上 legacy: false,

2025年SCI一区智能优化算法:混沌进化优化算法(Chaotic Evolution Optimization, CEO),提供MATLAB代码

一、混沌进化优化算法 https://github.com/ITyuanshou/MATLABCode 1. 算法简介 混沌进化优化算法&#xff08;Chaotic Evolution Optimization, CEO&#xff09;是2025年提出的一种受混沌动力学启发的新型元启发式算法。该算法的主要灵感来源于二维离散忆阻映射的混沌进化过…

网络安全之日志审计 网络安全审计制度

一、代码审计安全 代码编写安全: 程序的两大根本:变量与函数 漏洞形成的条件:可以控制的变量“一切输入都是有害的 ” 变量到达有利用价值的函数&#xff08;危险函数&#xff09;“一切进入函数的变量是有害的” 漏洞的利用效果取决于最终函数的功能&#xff0c;变量进入…

VScode+stfp插件,实现文件远程同步保存【2025实操有效】

目录 1 痛点2 准备工作3 操作步骤3.1 第一步&#xff0c;下载STFP插件3.2 第二步&#xff0c;修改配置文件3.3 第三步&#xff0c;测试是否成功 4 后记 1 痛点 我一直用vscode远程连接服务器&#xff0c;传代码文件等到服务器上面&#xff0c;突然有一次服务器那边尽心维修&am…

Java高频面试之SE-23

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天又来了&#xff01;哈哈哈哈哈嗝&#x1f436; Java 中的 Stream 是 Java 8 引入的一种全新的数据处理方式&#xff0c;它基于函数式编程思想&#xff0c;提供了一种高效、简洁且灵活的方式来…

python-leetcode-乘积最大子数组

152. 乘积最大子数组 - 力扣&#xff08;LeetCode&#xff09; class Solution:def maxProduct(self, nums: List[int]) -> int:if not nums:return 0max_prod nums[0]min_prod nums[0]result nums[0]for i in range(1, len(nums)):if nums[i] < 0:max_prod, min_prod…

NavVis VLX三维扫描:高层建筑数字化的革新力量【沪敖3D】

在三维激光扫描领域&#xff0c;楼梯结构因其复杂的空间形态和连续垂直移动的实际需求&#xff0c;一直是技术难点之一。利用NavVis VLX穿戴式移动扫描系统成功完成一栋34层建筑的高效扫描&#xff0c;其中楼梯部分的数据一遍成形且无任何分层或形变。本文将深入分析该项目的技…

docker安装register私库

一、使用自己的私库 1、安装register私库 docker pull registry运行 docker run -d -v /data/registry:/var/lib/registry -p 5000:5000 --name registry registry:2参数-v /data/registry:/var/lib/registry&#xff0c;挂载目录 2、拉取需要的镜像 docker pull hello-w…