Flask源碼分析二:路由內部實現原理

前言

Flask是目前爲止我最喜歡的一個Python Web框架了,爲了更好的掌握其內部實現機制,這兩天準備學習下Flask的源碼,將由淺入深跟你們分享下,其中Flask版本爲1.1.1。html

上次瞭解了Flask服務的啓動流程,今天咱們來看下路由的內部實現機理。python

Flask系列文章:app

  1. Flask開發初探
  2. Flask源碼分析一:服務啓動

關於路由

所謂路由,就是處理請求URL和函數之間關係的程序。框架

Flask中也是對URL規則進行統一管理的,建立URL規則有兩種方式:ide

  1. 使用@app.route修飾器,並傳入URL規則做爲參數,將函數綁定到URL,這個過程便將一個函數註冊爲路由,這個函數則被稱爲視圖函數。
  2. 使用app.add_url_rule()。

在開始閱讀源碼以前,我是有這幾點疑問的?函數

  1. 註冊路由的過程是什麼?
  2. Flask內部是如何進行URL規則管理的?
  3. 一個視圖函數綁定多個URL內部是如何實現的?
  4. 動態URL是如何進行視圖函數匹配的呢?
  5. 匹配路由的過程是怎樣的呢?

那就讓咱們帶着這幾點疑問一塊兒去學習源碼吧!工具

正文

註冊路由

首先,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

route()有兩個參數,rule表示url規則。該函數對參數進行處理以後,調用方法add_url_role(),這裏也就驗證了兩種註冊路由的方法等價。咱們來看下代碼:學習

def add_url_rule(
        self,
        rule,
        endpoint=None,
        view_func=None,
        provide_automatic_options=None,
        **options
    ):
        
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options["endpoint"] = endpoint
        methods = options.pop("methods", None)

        # if the methods are not given and the view_func object knows its
        # methods we can use that instead.  If neither exists, we go with
        # a tuple of only ``GET`` as default.
        if methods is None:
            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)

        # Methods that should always be added
        required_methods = set(getattr(view_func, "required_methods", ()))

        # starting with Flask 0.8 the view_func object can disable and
        # force-enable the automatic options handling.
        if provide_automatic_options is None:
            provide_automatic_options = getattr(
                view_func, "provide_automatic_options", None
            )

        if provide_automatic_options is None:
            if "OPTIONS" not in methods:
                provide_automatic_options = True
                required_methods.add("OPTIONS")
            else:
                provide_automatic_options = False

        # Add the required methods now.
        methods |= required_methods

        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options

        self.url_map.add(rule)
        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

入參包括:ui

  1. rule: url規則
  2. endpoint : 要註冊規則的endpoint,默認是視圖函數的名兒
  3. view_func: 視圖函數
  4. provide_automatic_options: 請求方法是否添加OPTIONS方法的一個標誌
  5. options: 關於請求處理的一些方法等

能夠看到,add_url_rule()首先進行參數處理,包括:

  1. endpoint默認爲視圖函數的name
  2. url請求的方法默認爲GET
  3. 若請求方法中沒有設置OPTIONS,添加該方法。

在處理完全部的參數後,將該URL規則寫入url_map(建立好Rule對象,並添加到Map對象中),將視圖函數寫入view_function字典中。

其中,url_map 是werkzeug.routing:Map 類的對象,rule是 werkzeug.routing:Rule 類的對象,也就是Flask的核心路由邏輯是在werkzeug中實現的

werkzeug

werkzeug是使用Python編寫的一個WSGI工具集,werkzeug.routing模塊主要用於url解析。

Rule類

Rule類繼承自RuleFactory類,一個Rule實例表明一個URL模式,一個WSGI應用會處理不少個不一樣的URL模式,與此同時產生不少個Rule實例,這些實例將做爲參數傳給Map類。

Map類

Map類構造的實例存儲全部的url規則,解析並匹配請求對應的視圖函數。

路由匹配

在應用初始化的過程當中,會註冊全部的路由規則,能夠調用(app.url_map)查看,當服務收到URL請求時,就須要進行路由匹配,以找到對應的視圖函數,對應的流程和原理是什麼呢?

當用戶請求進入Flask應用時,調用Flask類的wsgi_app方法:

def wsgi_app(self, environ, start_response):
    
    ctx = self.request_context(environ)
    error = None
    try:
        try:
            ctx.push()
            response = self.full_dispatch_request()
        except Exception as e:
            error = e
            response = self.handle_exception(e)
        except:  # noqa: B001
            error = sys.exc_info()[1]
            raise
        return response(environ, start_response)
    finally:
        if self.should_ignore_error(error):
            error = None
        ctx.auto_pop(error)

該函數的處理過程包括:

  1. 建立RequestContext對象,在對象初始化的過程當中調用app.create_url_adapter()方法,將請求參數environ傳給Map對象建立MapAdapter對象,保存在url_adapter字段中
  2. 將RequestContext對象推入_request_ctx_stack棧中
  3. 經過RequestContext的match_request方法,調用MapAdapter對象的match方法找到匹配的Rule並解析出參數,保存在request的url_rule和view_args字段中
  4. 調用full_dispatch_request()

接下來咱們看下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()
    except Exception as e:
        rv = self.handle_user_exception(e)
    return self.finalize_request(rv)

能夠看到,重點執行dispatch_request():

def dispatch_request(self):
    req = _request_ctx_stack.top.request
    if req.routing_exception is not None:
        self.raise_routing_exception(req)
    rule = req.url_rule
    # if we provide automatic options for this URL and the
    # request came with the OPTIONS method, reply automatically
    if (
        getattr(rule, "provide_automatic_options", False)
        and req.method == "OPTIONS"
    ):
        return self.make_default_options_response()
    # otherwise dispatch to the handler for that endpoint
    return self.view_functions[rule.endpoint](**req.view_args)

處理的過程是:獲取請求對象的request,找到對應的endpoint,繼而從view_functions中找到對應的視圖函數,傳遞請求參數,視圖函數處理內部邏輯並返回,完成一次請求分發。

以上,就是Flask路由的內部實現原理。

相關文章
相關標籤/搜索