前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python Web Flask源码解读(二)——路由原理

Python Web Flask源码解读(二)——路由原理

作者头像
阳仔
发布2019-07-30 15:25:26
7510
发布2019-07-30 15:25:26
举报
文章被收录于专栏:终身开发者

关于我 编程界的一名小小程序猿,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。 联系:hylinux1024@gmail.com 微信公众号:angrycode

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

0x00 路由原理

首先看下 Flask的简易用法

代码语言:javascript
复制
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方法

代码语言:javascript
复制
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.addurlrule
代码语言:javascript
复制
def add_url_rule(self, rule, endpoint, **options):
    options['endpoint'] = endpoint
    options.setdefault('methods', ('GET',))
    self.url_map.add(Rule(rule, **options))

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

代码语言:javascript
复制
@app.route('/')
def index():
    pass

等价于

代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
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和响应的函数方法对应起来。

调用逻辑为

代码语言:javascript
复制
Flask.route -> Flask.add_url_rule -> Map.add -> Rule.bind
0x01 响应请求

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

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

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

Flask.wsgi_app
代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
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 学习资料
  • https://werkzeug.palletsprojects.com/en/0.15.x/
  • https://palletsprojects.com/p/flask/
  • https://docs.python.org/3/library/http.server.html#module-http.server
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 终身开发者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x00 路由原理
    • Flask.route
      • Flask.addurlrule
        • Rule
          • Map.add
            • Rule.bind
            • 0x01 响应请求
              • Flask.wsgi_app
                • Flask.dispatch_request
                  • Flask.match_request
                  • 0x02 总结一下
                  • 0x03 学习资料
                  相关产品与服务
                  云开发 CloudBase
                  云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档