美文网首页大数据 爬虫Python AI SqlPython小哥哥
Python Web Flask源码解读——路由原理!

Python Web Flask源码解读——路由原理!

作者: 14e61d025165 | 来源:发表于2019-07-24 14:17 被阅读0次

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

    0x00 路由原理

    首先看下 Flask 的简易用法

    Python资源共享群:484031800

    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 方法中有两个参数 rule 和 options 。 rule 是 url 规则, 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 方法将 rule 和 option 构造成 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)
    复制代码
    environ 是 Web 服务器传递过来的参数, request_context(environ) 会创建一个请求上下文实例,通过预处理 preprocess_request 之后就会进入分发请求 dispatch_request ,然后是执行响应 make_response 和 process_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_functionsendpoint
    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_functionsendpoint 来执行对应函数方法,并返回函数的返回值。

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

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

    0x02 总结一下

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

    相关文章

      网友评论

        本文标题:Python Web Flask源码解读——路由原理!

        本文链接:https://www.haomeiwen.com/subject/odsxrctx.html