赵走x博客
网站访问量:151489
首页
书籍
软件
工具
古诗词
搜索
登录
4、全局变量
3、模板渲染过程
2、路由原理
1、整体运行梳理
2、路由原理
资源编号:76589
Flask源码解读
Python Web
热度:97
来源:https://juejin.im/post/5d3713cc51882548de1e6be7
接上一篇的话题,继续阅读Flask的源码,来看一下这个框架路由原理。 # 0x00 路由原理 首先看下Flask的简易用法 ``` 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 \ '(?/?)' 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_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规则找到对应函数方法。 ### 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_functions[endpoint](**values)来执行对应函数方法,并返回函数的返回值。 如果上述dispatch_request没有匹配到url规则,则会执行error_handlers字典中找到对应的错误码执行handler方法。 至此url路由规则匹配过程就完成了。 # 0x02 总结一下 在Flask启动后会把route装饰器解析后,把url规则与函数方法进行对应保存。 在客户端请求时,Flask.wsgi_app方法会被执行,并开始匹配url找到对应的方法,执行后将结果返回。 # 0x03 学习资料 [werkzeug.palletsprojects.com/en/0.15.x/](https://werkzeug.palletsprojects.com/en/0.15.x/) [palletsprojects.com/p/flask/](https://palletsprojects.com/p/flask/) [docs.python.org/3/library/h…](https://docs.python.org/3/library/http.server.html#module-http.server)