Flask路由系統(tǒng)

通常有3種定義路由函數(shù)的方法:

  1. 使用flask.Flask.route() 修飾器。
  2. 使用flask.Flask.add_url_rule()函數(shù)。
  3. 直接訪問基于werkzeug路由系統(tǒng)的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函數(shù)的包裝,當(dāng)我們寫如下代碼時:

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

實際上上面的代碼轉(zhuǎn)換成:

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

也就是,rule = '/index.html', options = { }, 執(zhí)行decorator(index) 時會執(zhí)行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 (視圖函數(shù)選項)
            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 (視圖函數(shù)選項)
        required_methods = set(getattr(view_func, 'required_methods', ()))

        # View Function Options (視圖函數(shù)選項)
        provide_automatic_options = getattr(view_func,
            'provide_automatic_options', None)
        # View Function Options (視圖函數(shù)選項)
        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 (視圖函數(shù)選項)
        methods |= required_methods
        
        # url_rule_class默認(rèn)為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 不能重復(fù)定義 
        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參數(shù)為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就設(shè)置為view_func.name視圖函數(shù)的名字。然后將endpoint添加到options字典中,對于methods = options.pop('methods', None),當(dāng)我們指定時,@app.route('/login', methods=['GET', 'POST']),methods = ['GET', 'POST'] 否則methods = None. 如果methods == None, 同時,view_func 沒有methods屬性,則methods默認(rèn)設(shè)置為('GET', ). 當(dāng)然,methods不能設(shè)置為字符串類型,‘POST’可以不區(qū)分大小寫。

關(guān)于View Function Options的代碼暫時忽略。
add_url_rule執(zhí)行完畢后,我們獲得了Flask.url_map, 以及填充了Flask.view_functions.
我們可以做實驗看看url_map里面都有啥,詳見示例代碼

Part 2


下面回過頭,來看看當(dāng)Flask運行時,一個Request來了,會發(fā)生什么,仍然從Flask.wsgi_app開始閱讀。
已經(jīng)知道,當(dāng)一個Request到來時,會首先push RequestContext和AppContext,在RequestContext中的init函數(shù)中有:

...
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與當(dāng)前到來的Request中environ進(jìn)行綁定,獲得一個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之后,調(diào)用match_request,url_adapter.match()會返回一個元組view_args就是url_rule中的參數(shù),比如Rule(/<int:year>/, 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中后,我們繼續(xù)對wsgi_app中的response = self.full_dispatch_request()這個過程與路由相關(guān)的內(nèi)容進(jìn)行分析。

    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()中處理與藍(lán)本和@before_request有關(guān)的東西.暫時忽略。

    def dispatch_request(self):
        # 從_request_ctx_stack獲得當(dāng)前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
        # 視圖函數(shù)選項(暫時忽略)
        if getattr(rule, 'provide_automatic_options', False) \
           and req.method == 'OPTIONS':
            return self.make_default_options_response()
        # 如果沒有設(shè)定視圖函數(shù)選項,直接調(diào)用視圖函數(shù),在view_functions中查找
        # 鍵值為rule.endpoint的函數(shù),并傳入req.view_args(字典)作為
        # key-word參數(shù)。
        return self.view_functions[rule.endpoint](**req.view_args)

dispatch_request()處理完畢,將返回值儲存在rv變量中。通常,視圖函數(shù)會return render_template(...). 返回值接下來經(jīng)過一系列處理,發(fā)送到客戶端。

Part 3


視圖函數(shù)選項,藍(lán)本?(to be contiued...)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 22年12月更新:個人網(wǎng)站關(guān)停,如果仍舊對舊教程有興趣參考 Github 的markdown內(nèi)容[https://...
    tangyefei閱讀 35,215評論 22 257
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,868評論 18 139
  • [TOC]一直想做源碼閱讀這件事,總感覺難度太高時間太少,可望不可見。最近正好時間充裕,決定試試做一下,并記錄一下...
    何柯君閱讀 7,219評論 3 98
  • 本文大致梳理一下Flask框架在處理url路由時的主要過程。 類圖 route裝飾器 在Flask應(yīng)用中,我們一般...
    Jakiro閱讀 3,648評論 0 6
  • 在flask框架中,我們經(jīng)常會遇到endpoint這個東西,最開始也沒法理解這個到底是做什么的。最近正好在研究Fl...
    卡薩諾瓦_(dá)閱讀 982評論 0 0