Flask路由系统

通常有3种定义路由函数的方法:

  1. 使用flask.Flask.route() 修饰器。
  2. 使用flask.Flask.add_url_rule()函数。
  3. 直接访问基于werkzeug路由系统的flask.Flask.url_map.

Part 1


让我们从最常用的@app.route()修饰器开始。

    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

可以看到修饰器是对add_url_rule函数的包装,当我们写如下代码时:

    @app.route('/index.html')
    def index():
        return "Hello World!"

实际上上面的代码转换成:

def index():
    return "Hello World!"
index = app.route('/index.html')(index)

也就是,rule = '/index.html', options = { }, 执行decorator(index) 时会执行self.add_url_rule(rule, endpoint, f, **options):

    @setupmethod
    def add_url_rule(self, rule, endpoint=None, view_func=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:
            # View Function Options (视图函数选项)
            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)

        # View Function Options (视图函数选项)
        required_methods = set(getattr(view_func, 'required_methods', ()))

        # View Function Options (视图函数选项)
        provide_automatic_options = getattr(view_func,
            'provide_automatic_options', None)
        # View Function Options (视图函数选项)
        if provide_automatic_options is None:
            if 'OPTIONS' not in methods:
                provide_automatic_options = True
                required_methods.add('OPTIONS')
            else:
                provide_automatic_options = False

        # View Function Options (视图函数选项)
        methods |= required_methods
        
        # url_rule_class默认为Werkzeug的Rule类,
        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options

        self.url_map.add(rule)
        # view_func 不能重复定义 
        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参数为None,那么:

def _endpoint_from_view_func(view_func):
    """Internal helper that returns the default endpoint for a given
    function.  This always is the function name.
    """
    assert view_func is not None, 'expected view func if endpoint ' \
                                  'is not provided.'
    return view_func.__name__

endpoint就设置为view_func.name视图函数的名字。然后将endpoint添加到options字典中,对于methods = options.pop('methods', None),当我们指定时,@app.route('/login', methods=['GET', 'POST']),methods = ['GET', 'POST'] 否则methods = None. 如果methods == None, 同时,view_func 没有methods属性,则methods默认设置为('GET', ). 当然,methods不能设置为字符串类型,‘POST’可以不区分大小写。

关于View Function Options的代码暂时忽略。
add_url_rule执行完毕后,我们获得了Flask.url_map, 以及填充了Flask.view_functions.
我们可以做实验看看url_map里面都有啥,详见示例代码。

Part 2


下面回过头,来看看当Flask运行时,一个Request来了,会发生什么,仍然从Flask.wsgi_app开始阅读。
已经知道,当一个Request到来时,会首先push RequestContext和AppContext,在RequestContext中的init函数中有:

...
self.url_adapter = app.create_url_adapter(self.request)
...
self.match_request()
    def create_url_adapter(self, request):
        if request is not None:
            return self.url_map.bind_to_environ(request.environ,
                server_name=self.config['SERVER_NAME'])
        ...

首先将Flask.url_map与当前到来的Request中environ进行绑定,获得一个url_adapter。

    def match_request(self):
        try:
            url_rule, self.request.view_args = \
                self.url_adapter.match(return_rule=True)
            self.request.url_rule = url_rule
        except HTTPException as e:
            self.request.routing_exception = e

获得url_adaptor之后,调用match_request,url_adapter.match()会返回一个元组view_args就是url_rule中的参数,比如Rule(//, endpoint='blog/archive')这个Rule,而请求是/2016/,那么view_args={year: 2016}. url_rule和view_args被储存在Request中。在Request类中,我们可以直接Request.endpoint将返回url_rule.endpoint.

在url_rule和view_args被装载到Request中后,我们继续对wsgi_app中的response = self.full_dispatch_request()这个过程与路由相关的内容进行分析。

    def full_dispatch_request(self):
        self.try_trigger_before_first_request_functions()
        try:
            request_started.send(self)
            rv = self.preprocess_request()
            if rv is None:
                rv = self.dispatch_request()
         ...

在preprocess_request()中处理与蓝本和@before_request有关的东西.暂时忽略。

    def dispatch_request(self):
        # 从_request_ctx_stack获得当前request.
        req = _request_ctx_stack.top.request
        # 如果有异常,处理异常
        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        # 获得储存在request中的url_rule.
        rule = req.url_rule
        # 视图函数选项(暂时忽略)
        if getattr(rule, 'provide_automatic_options', False) \
           and req.method == 'OPTIONS':
            return self.make_default_options_response()
        # 如果没有设定视图函数选项,直接调用视图函数,在view_functions中查找
        # 键值为rule.endpoint的函数,并传入req.view_args(字典)作为
        # key-word参数。
        return self.view_functions[rule.endpoint](**req.view_args)

dispatch_request()处理完毕,将返回值储存在rv变量中。通常,视图函数会return render_template(...). 返回值接下来经过一系列处理,发送到客户端。

Part 3


视图函数选项,蓝本?(to be contiued...)

你可能感兴趣的:(Flask路由系统)