關於我
一個有思想的程序猿,終身學習實踐者,目前在一個創業團隊任team lead,技術棧涉及Android、Python、Java和Go,這個也是咱們團隊的主要技術棧。
Github:https://github.com/hylinux1024
微信公衆號:終身開發者(angrycode)html
接上一篇的話題,繼續閱讀Flask
的源碼,來看一下這個框架路由原理。python
首先看下Flask
的簡易用法linux
from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return f'Hello, World!' if __name__ == '__main__': app.run()
在Flask
中是使用@app.route
這個裝飾器來實現url
和方法之間的映射的。nginx
打開route
方法git
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
路徑規則,和方法名稱對應關係保存起來,而後將函數方法名與函數對象也對應的保存到一個字典中。github
def add_url_rule(self, rule, endpoint, **options): options['endpoint'] = endpoint options.setdefault('methods', ('GET',)) self.url_map.add(Rule(rule, **options))
這個方法的註釋也是很詳細的,大概的意思若是定義了一個方法express
@app.route('/') def index(): pass
等價於flask
def index(): pass app.add_url_rule('index', '/') app.view_functions['index'] = index
最後調用url_map.add
方法將rule
和option
構形成Rule
添加到一個Map
對象中。服務器
Rule
表示url
規則,它是在werkzeug
函數庫中定義的類。微信
url_map
是一個自定義的Map
對象。它的目的就是實現url
與方法之間映射關係。
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
方法,這裏纔是真正實現綁定的邏輯。
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
當服務啓動以後,Flask
會默認開啓一個Web
服務器,便於開發調試,而實際環境中可能會使用nginx+gunicorn
等工具進行部署。因爲部署不是本節主題,咱們仍是專一於客戶端請求是如何響應的。
在上一篇咱們知道Flask
經過Werkzeug
函數庫中的run_simple
方法將服務啓動了。
當客戶端發送請求時這個方法會被執行
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
。
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
規則找到對應函數方法。
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
路由規則匹配過程就完成了。
在Flask
啓動後會把route
裝飾器解析後,把url
規則與函數方法進行對應保存。
在客戶端請求時,Flask.wsgi_app
方法會被執行,並開始匹配url
找到對應的方法,執行後將結果返回。