Flask源码解读之路由部分

从django的rest framwork过渡到flask框架的时候,经常会想flask的路由部分是怎么走的,这篇博客将一步步展示从启动程序到请求来路径和函数是怎么去匹配的。

1.首先是启动flask程序,python解释器就会从上到下加载我们的app

@app.route(‘/home‘,endpoint=‘index‘)
def home():
    return render_template(‘dist/index.html‘)

2.在函数上的装饰器即会运行,我们进入装饰器中查看运行内容

    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

3.运行装饰器,我们将路径rule和参数传入了装饰器中,在装饰器闭包中我们实际是执行了self.add_url_rule(rule, endpoint, f, **options)这个方法,其实映射规则和保存rule对象也是在这个方法中,然后我们进入add_url_rule方法中查看其中做了什么操作

    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)
        options["endpoint"] = endpoint
        methods = options.pop("methods", None)
        if methods is None:
            methods = getattr(view_func, "methods", None) or ("GET",)
        if isinstance(methods, string_types):
            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", ()))

        # 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
            )

        if provide_automatic_options is None:
            if "OPTIONS" not in methods:
                provide_automatic_options = True
                required_methods.add("OPTIONS")
            else:
                provide_automatic_options = False

        methods |= required_methods
# 这步时创建rule对象,里面包含路径方法,endpoint,可接受的方法等
        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options
#前面都是关于其他参数是怎么修改和操作的,接下来的才是路由映射关系,下面这步是把rule对象放进一个特殊的字典中的_rules为键的列表中,这里可以进入方法中查看
        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
                )# 这步就是把endpoint和函数放入一个字典中做映射如{“index”:home}# 那其实到这里我们就可以大概猜想出寻找路由的过程了
            self.view_functions[endpoint] = view_func

4.其实到这步我们就可以大概猜想出寻找路由的过程了,肯定当请求到来,我们会先从请求中获取其路径比如:/home,然后我们根据路径去之前特殊字典self.url_map的_rule列表中去匹配rule对象,当匹配到rule对象后我们取出其endpoint的值,再然后根据这个endpoint的值去self.view_functions这个字典中取出函数home,然后传入请求的参数进函数中运行函数。根据这个猜想我们去源码中验证一下。

5.我么找到当请求到时走app的__call__方法我们一步一步看怎么走的

    def __call__(self, environ, start_response):
    #直接返回wsgi_app我们去看看这个方法
        return self.wsgi_app(environ, start_response)

  

    def wsgi_app(self, environ, start_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:  # noqa: B001
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)

  6.我们进入request_context中这里返回的是一个RequestContext对象,里面封装了request和session,还有其中还用了设计模式的适配器模式,返回了一个self.url_adapter对象底层即MapAdapter对象这个对象封装之前哦我们哪个特殊的字典对象url_map,我们看代码是怎么走的

class RequestContext(object):
#这是刚开进入的请求对象
    def __init__(self, app, environ, request=None, session=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = None
        try:
#在请求对象中用适配器模式,返回得url对象封装了之前得url_map
            self.url_adapter = app.create_url_adapter(self.request)
        except HTTPException as e:
            self.request.routing_exception = e
        self.flashes = None
        self.session = session

        self._implicit_app_ctx_stack = []

        self.preserved = False

        self._preserved_exc = None

        self._after_request_functions = []

7.然后我们回到wsgi_app函数中,查看ctx.push()中对路由信息做了怎样的操作

  

    def push(self):

        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            app_ctx.push()
            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)

        if self.session is None:
            session_interface = self.app.session_interface
            # 对session中的值解密打开并放入一个特殊的字典中,如果没有值就生成一个空字典
            #SecureCookieSessionInterface是session_interface
            self.session = session_interface.open_session(self.app, self.request)

            if self.session is None:
                self.session = session_interface.make_null_session(self.app)
# 这一步就是我们对路由信息进行匹配,看里面发生什么
        if self.url_adapter is not None:
            self.match_request()

  

    def match_request(self):
        try:
#这一步匹配路由规则和参数信息我么进入这个函数看看是怎么匹配的
            result = self.url_adapter.match(return_rule=True)#这一步记住这个request,url_rule这一步的赋值就是把rule对象赋给它,和把请求的参数给了view_args
            self.request.url_rule, self.request.view_args = result
        except HTTPException as e:
            self.request.routing_exception = e

  

    def match(self, path_info=None, method=None, return_rule=False, query_args=None):
        self.map.update()
        if path_info is None:
            path_info = self.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()
        # 找出匹配的rule,请求的rule
        for rule in self.map._rules:
            try:
#这里是通过正则匹配,然后返回rv即参数,看是否匹配到。如果匹配到,后续返回rule对象
                rv = rule.match(path, method)
            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:
                have_match_for.update(rule.methods)
                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:
                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 + "." if self.subdomain else "",
                                self.server_name,
                                self.script_name,
                            ),
                            redirect_url,
                        )
                    )
                )
#这个参数默认是为True的所以这里返回和rule对象和rv即参数,接下来这里和我们猜想的越来越接近了,肯定会通过这个rule对象取它的endpoint然后去匹配对应的函数然后执行
            if return_rule:
                return rule, rv
            else:
                return rule.endpoint, rv

        if have_match_for:
            raise MethodNotAllowed(valid_methods=list(have_match_for))
        raise NotFound()

  

8.然后我们回到wsgi_app函数中,我们进入到执行请求的步骤即response = self.full_dispatch_request()这个函数中,我们查看里面发生了什么

    def full_dispatch_request(self):

        # 第一次请求的钩子函数执行
        self.try_trigger_before_first_request_functions()
        try:
            # flask信号
            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):
  #首先这一步从请求上下文中取出requestcontext对象,这就是我们前面提到的里面有request对象,session对象,有url_apdate对象,然后这里取是request对象
        req = _request_ctx_stack.top.request
        if req.routing_exception is not None:
            self.raise_routing_exception(req)# 果然这一步即取出了request对象的url_rule属性,即前面我们赋值了的rule对象,这个对象就可以取出rule的各种信息属性
        rule = req.url_rule
        if (
            getattr(rule, "provide_automatic_options", False)
            and req.method == "OPTIONS"
        ):
            return self.make_default_options_response()
        # 这里view_functions自带你就是程序刚启动时的endpoint和函数对应的大字典,然后字典操作,根据请求的rule.endpoint得到函数并且加了括号和参数就运行了这个函数,然后返回结果,至此整个流程就结束了
        return self.view_functions[rule.endpoint](**req.view_args)

  9.至此,所有得流程即结束了,从路由规则得建立,到请求到来如何取进行匹配,然后执行函数操作。参考和转载麻烦加个链接,码字不易。

原文地址:https://www.cnblogs.com/lifei01/p/12580420.html

时间: 2024-08-30 05:59:21

Flask源码解读之路由部分的相关文章

Python Web Flask源码解读(四)——全局变量

关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https://github.com/hylinux1024 微信公众号:终身开发者(angrycode) Flask中全局变量有current_app.request.g和session.不过需要注意的是虽然标题是写着全局变量,但实际上这些变量都跟当前请求的上下文环境有关,下面一起来看看. current_ap

Flask源码复习之路由

构建路由规则 一个 web 应用不同的路径会有不同的处理函数,路由就是根据请求的 URL 找到对应处理函数的过程. 在执行查找之前,需要有一个规则列表,它存储了 url 和处理函数的对应关系.最容易想到的解决方案就是定义一个字典,key 是 url,value 是对应的处理函数.如果 url 都是静态的(url 路径都是实现确定的,没有变量和正则匹配),那么路由的过程就是从字典中通过 url 这个 key ,找到并返回对应的 value:如果没有找到,就报 404 错误.而对于动态路由,还需要更

HttpClient 4.3连接池参数配置及源码解读

目前所在公司使用HttpClient 4.3.3版本发送Rest请求,调用接口.最近出现了调用查询接口服务慢的生产问题,在排查整个调用链可能存在的问题时(从客户端发起Http请求->ESB->服务端处理请求,查询数据并返回),发现原本的HttpClient连接池中的一些参数配置可能存在问题,如defaultMaxPerRoute.一些timeout时间的设置等,虽不能确定是由于此连接池导致接口查询慢,但确实存在可优化的地方,故花时间做一些研究.本文主要涉及HttpClient连接池.请求的参数

【Spark】SparkContext源码解读

SparkContext的初始化 SparkContext是应用启动时创建的Spark上下文对象,是进行Spark应用开发的主要接口,是Spark上层应用与底层实现的中转站(SparkContext负责给executors发送task). SparkContext在初始化过程中,主要涉及一下内容: SparkEnv DAGScheduler TaskScheduler SchedulerBackend SparkUI 生成SparkConf SparkContext的构造函数中最重要的入参是Sp

自动化WiFI钓鱼工具——WiFiPhisher源码解读

工具介绍 开源无线安全工具Wifiphisher是基于MIT许可模式的开源软件,运行于Kali Linux之上. github.com/sophron/wifiphisher 它能够对WPA加密的AP无线热点实施自动化钓鱼攻击,获取密码账户.由于利用了社工原理实施中间人攻击,Wifiphisher在实施攻击时无需进行暴力破解. 此外安利一个我们正在开发的项目,基于wifiphisher的校园网钓鱼工具,希望有小伙伴来一起玩耍:-P github.com/noScripts/Campus-Fake

Flask源码解析(理解working outside of application context)

from flask import Flask, current_app app = Flask(__name__) a = current_app d = current_app.config['DEBUG'] 首先从这段代码看起,代码运行的结果就是 RuntimeError: Working outside of application context. 此时本地代理未绑定,不是我们想要的核心flask对象.代码报错. current_app = LocalProxy(_find_app)

REST、DRF(View源码解读、APIView源码解读)

一.REST 1.什么是编程? 数据结构和算法的结合 2.什么是REST? 首先我们回顾下我们之前的图书管理系统,我们设计了这样的URL,如下: 127.0.0.1:9001/books/ 127.0.0.1:9001/get_all_books/ 访问所有的数据 127.0.0.1:9001/books/{id}/ 127.0.0.1:9001/books/{id}?method=get 访问单条数据 127.0.0.1:9001/books/add/ 127.0.0.1:9001/books

QCustomplot使用分享(二) 源码解读

一.头文件概述 从这篇文章开始,我们将正式的进入到QCustomPlot的实践学习中来,首先我们先来学习下QCustomPlot的类图,如果下载了QCustomPlot源码的同学可以自己去QCustomPlot的目录下documentation/qcustomplot下寻找一个名字叫做index.html的文件,将其在浏览器中打开,也是可以找到这个库的类图.如图1所示,是组成一个QCustomPlot类图的可能组成形式. 一个图表(QCustomPlot):包含一个或者多个图层.一个或多个ite

vue源码解读预热-0

vueJS的源码解读 vue源码总共包含约一万行代码量(包括注释)特别感谢作者Evan You开放的源代码,访问地址为Github 代码整体介绍与函数介绍预览 代码模块分析 代码整体思路 总体的分析 从图片中可以看出的为采用IIFE(Immediately-Invoked Function Expression)立即执行的函数表达式的形式进行的代码的编写 常见的几种插件方式: (function(,){}(,))或(function(,){})(,)或!function(){}()等等,其中必有