一、路由系统
1.浅析@app.route的源码
我们使用@app.route("/index")可以给视图函数加上路由映射。我们分析一下@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()函数。这里的self就是Flask的实例app。因为是app调用的route。
也就是说,我们不使用装饰器,也可以直接调用该函数实现路由映射:
def test(): return 'test' # 使用app.add_url_rule代替@app.route # 第一个参数就是url,第二个参数是endpoint(即路由name),第三个参数为视图函数引用 app.add_url_rule('/test', None, test)
执行结果:
可以看到,这种方式实现的路由,也可以正常访问。
2.分析add_url_rule函数
@setupmethod def add_url_rule( self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options ): if endpoint is None: # 如果传入的endpoint为None,则使用视图函数的__name__作为endpoint endpoint = _endpoint_from_view_func(view_func) options["endpoint"] = endpoint # 将endpoint设置到options中 methods = options.pop("methods", None) # 从参数中获取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为None,就去view_func中找methods,如果找不到,则默认为GET methods = getattr(view_func, "methods", None) or ("GET",) if isinstance(methods, string_types): # 如果methods是str,则报错,必须是列表 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中元素全部转换为大写 # Methods that should always be added required_methods = set(getattr(view_func, "required_methods", ())) # 获取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 provide_automatic_options = getattr( view_func, "provide_automatic_options", None ) if provide_automatic_options is None: # 如果还为None if "OPTIONS" not in methods: # 如果methods中没有OPTIONS provide_automatic_options = True # provide_automatic_options置为True required_methods.add("OPTIONS") else: provide_automatic_options = False # 如果methods中有,则provide_automatic_options置为False # Add the required methods now. methods |= required_methods # 并集 # 将我们传入的url,endpoint,func等封装起来,成为一个Rule对象 rule = self.url_rule_class(rule, methods=methods, **options) rule.provide_automatic_options = provide_automatic_options # 将封装好的Rule对象添加到Map类的对象中 self.url_map.add(rule) if view_func is not None: # 传入的视图函数是否为空,这里不为空 old_func = self.view_functions.get(endpoint) # 去view_functions字典中看看有没有同名的视图函数 if old_func is not None and old_func != view_func: # 如果有同名视图函数,且函数不是我们当前传入的函数,则报错 raise AssertionError( "View function mapping is overwriting an " "existing endpoint function: %s" % endpoint # 报错:存在同名的视图函数 ) # 如果endpoint没有冲突,则将视图函数加入view_functions字典中 self.view_functions[endpoint] = view_fun
这段代码主要的功能就是,将我们传入的url、endpoint、methods等一系列路由参数,封装成一个Rule对象,然后添加到Map对象中。然后判断是否存在endpoint冲突的视图函数,如果没有,则将 endpoint:视图函数引用 键值对存放在app.view_functions字典中,该字典主要就是用来检查endpoint的冲突问题。
所以从这里可以看出,我们在写路由的时候,尽量不要让endpoint重名,如果一定要重名,则函数必须是相同的(例如两个url对应一个视图函数的场景)。例如:
@app.route('/test2', endpoint='t1') @app.route('/test', endpoint='t1') def test(): return 'test'
[email protected]装饰器的参数
@app.route和app.add_url_rule参数: rule, # URL规则 view_func, # 视图函数名称 defaults = None, # 默认值, 当URL中无参数,函数需要参数时,使用defaults = {'k': 'v'}为函数提供参数 endpoint = None, # 名称,用于反向生成URL,即: url_for('名称') methods = None, # 允许的请求方式,如:["GET", "POST"] strict_slashes = None, # 对URL最后的 / 符号是否严格要求,如: 例如: @app.route('/index', strict_slashes=False) #访问 http:// www.xx.com/index/ 或http://www.xx.com/index 均可 @app.route('/index', strict_slashes=True) #仅访问 http://www.xx.com/index indexredirect_to = None, # 重定向到指定地址 如: 例如: @app.route('/index/<int:nid>', redirect_to='/home/<nid>') 或 def func(adapter, nid): return "/home/888" @app.route('/index/<int:nid>', redirect_to=func) subdomain = None, # 子域名访问,什么是子域名:主干域名是www.leeoo.com admin.leeoo.com就是admin子域名 例如: from flask import Flask, views, url_for app = Flask(import_name=__name__) # 配置服务器地址和端口 app.config['SERVER_NAME'] = 'leeoo.com:5000' # 当访问admin.leeoo.com/时才会走这个路由 @app.route("/", subdomain="admin") def static_index(): """Flask supports static subdomains This is available at static.your-domain.tld""" return "static.your-domain.tld" # 动态子域名,访问user1.leeoo.com/dynamic,则相当于将'user1'作为参数传入username_index()视图函数 @app.route("/dynamic", subdomain="<username>") def username_index(username): """Dynamic subdomains are also supported Try going to user1.your-domain.tld/dynamic""" return username + ".your-domain.tld" if __name__ == '__main__': app.run()
4.