zoukankan      html  css  js  c++  java
  • Python Web Flask源码解读(二)——路由原理

    关于我
    一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。
    Github:https://github.com/hylinux1024
    微信公众号:终身开发者(angrycode)

    上一篇的话题,继续阅读Flask的源码,来看一下这个框架路由原理

    0x00 路由原理

    首先看下Flask的简易用法

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/')
    def hello():
        return f'Hello, World!'
    
    if __name__ == '__main__':
        app.run()
    
    

    Flask中是使用@app.route这个装饰器来实现url和方法之间的映射的。

    Flask.route

    打开route方法

    def route(self, rule, **options):
        """这个方法的注释非常详细,为了避免代码篇幅过长,这里省略注释"""
        def decorator(f):
            self.add_url_rule(rule, f.__name__, **options)
            self.view_functions[f.__name__] = f
            return f
    
        return decorator
    

    route方法中有两个参数ruleoptionsruleurl规则,options参数主要是werkzeug.routing.Rule类使用。 方法内部还定义decorator方法,将url路径规则,和方法名称对应关系保存起来,然后将函数方法名与函数对象也对应的保存到一个字典中。

    Flask.add_url_rule
    def add_url_rule(self, rule, endpoint, **options):
        options['endpoint'] = endpoint
        options.setdefault('methods', ('GET',))
        self.url_map.add(Rule(rule, **options))
    

    这个方法的注释也是很详细的,大概的意思如果定义了一个方法

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

    等价于

    def index():
        pass
    app.add_url_rule('index', '/')
    app.view_functions['index'] = index
    

    最后调用url_map.add方法将ruleoption构造成Rule添加到一个Map对象中。

    Rule

    Rule表示url规则,它是在werkzeug函数库中定义的类。

    url_map是一个自定义的Map对象。它的目的就是实现url与方法之间映射关系。

    Map.add
    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.bind(self)
            self._rules.append(rule)
            self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
        self._remap = True
    

    add方法中就调用了rule中的bind方法,这里才是真正实现绑定的逻辑。

    Rule.bind
    def bind(self, map, rebind=False):
        """Bind the url to a map and create a regular expression based on
        the information from the rule itself and the defaults from the map.
    
        :internal:
        """
        if self.map is not None and not rebind:
            raise RuntimeError('url rule %r already bound to map %r' %
                               (self, self.map))
        # 将url与map对应起来,即将map保存在rule对象自身的map属性上
        self.map = map
        if self.strict_slashes is None:
            self.strict_slashes = map.strict_slashes
        if self.subdomain is None:
            self.subdomain = map.default_subdomain
    
        rule = self.subdomain + '|' + (self.is_leaf and self.rule or self.rule.rstrip('/'))
    
        self._trace = []
        self._converters = {}
        self._weights = []
    
        regex_parts = []
        for converter, arguments, variable in parse_rule(rule):
            if converter is None:
                regex_parts.append(re.escape(variable))
                self._trace.append((False, variable))
                self._weights.append(len(variable))
            else:
                convobj = get_converter(map, converter, arguments)
                regex_parts.append('(?P<%s>%s)' % (variable, convobj.regex))
                self._converters[variable] = convobj
                self._trace.append((True, variable))
                self._weights.append(convobj.weight)
                self.arguments.add(str(variable))
                if convobj.is_greedy:
                    self.greediness += 1
        if not self.is_leaf:
            self._trace.append((False, '/'))
    
        if not self.build_only:
            regex = r'^%s%s$' % (
                u''.join(regex_parts),
                (not self.is_leaf or not self.strict_slashes) and 
                    '(?<!/)(?P<__suffix__>/?)' or ''
            )
            self._regex = re.compile(regex, re.UNICODE)
    

    bind方法中的for循环中调用了parse_url方法,这是一个生成器函数,它使用正则进行并yield回一个元组。这个方法的细节还是挺多的,但这里我们抓住主脉络,先把整体流程搞清楚。

    Flask启动时从装饰器route开始就把会把url和响应的函数方法对应起来。

    调用逻辑为

    Flask.route -> Flask.add_url_rule -> Map.add -> Rule.bind
    

    0x01 响应请求

    当服务启动之后,Flask会默认开启一个Web服务器,便于开发调试,而实际环境中可能会使用nginx+gunicorn等工具进行部署。由于部署不是本节主题,我们还是专注于客户端请求是如何响应的。

    上一篇我们知道Flask通过Werkzeug函数库中的run_simple方法将服务启动了。

    当客户端发送请求时这个方法会被执行

    Flask.wsgi_app
    def wsgi_app(self, environ, start_response):
        """The actual WSGI application.  This is not implemented in
        `__call__` so that middlewares can be applied:
    
            app.wsgi_app = MyMiddleware(app.wsgi_app)
    
        :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
        """
        with self.request_context(environ):
            rv = self.preprocess_request()
            if rv is None:
                rv = self.dispatch_request()
            response = self.make_response(rv)
            response = self.process_response(response)
            return response(environ, start_response)
    

    environWeb服务器传递过来的参数,request_context(environ)会创建一个请求上下文实例,通过预处理preprocess_request之后就会进入分发请求dispatch_request,然后是执行响应make_responseprocess_response,最后返回response

    这里我们重点关注dispatch_request

    Flask.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`.
        """
        try:
            endpoint, values = self.match_request()
            return self.view_functions[endpoint](**values)
        except HTTPException as e:
            handler = self.error_handlers.get(e.code)
            if handler is None:
                return e
            return handler(e)
        except Exception as e:
            handler = self.error_handlers.get(500)
            if self.debug or handler is None:
                raise
            return handler(e)
    

    这个方法的核心就是match_request,通过匹配客户端请求的url规则找到对应函数方法。

    Flask.match_request
    def match_request(self):
        """Matches the current request against the URL map and also
        stores the endpoint and view arguments on the request object
        is successful, otherwise the exception is stored.
        """
        rv = _request_ctx_stack.top.url_adapter.match()
        request.endpoint, request.view_args = rv
        return rv
    

    匹配完成后就会调用self.view_functions[endpoint](**values)来执行对应函数方法,并返回函数的返回值。

    如果上述dispatch_request没有匹配到url规则,则会执行error_handlers字典中找到对应的错误码执行handler方法。

    至此url路由规则匹配过程就完成了。

    0x02 总结一下

    Flask启动后会把route装饰器解析后,把url规则与函数方法进行对应保存。
    在客户端请求时,Flask.wsgi_app方法会被执行,并开始匹配url找到对应的方法,执行后将结果返回。

    0x03 学习资料

  • 相关阅读:
    [转]Entity Framework 和NHibernate的区别
    NHibernate One Session Per Request简单实现
    memcache和memcached之间的区别
    Memcached 内存分配机制介绍
    linux,apache,php,mysql常用的查看版本信息的方法
    Linux查看文件夹大小
    Linux查看系统配置常用命令
    php 文件缓存(转)
    memcache在xampp下的安装
    通过改进代码将for循环遍历数组提高效率
  • 原文地址:https://www.cnblogs.com/angrycode/p/11445163.html
Copyright © 2011-2022 走看看