[Python⾃学]Flask框架(3)(路由、CBV、⾃定义正则动态路由、请
求处理流程、蓝图)
⼀、路由系统
1.浅析@ute的源码
我们使⽤@ute("/index")可以给视图函数加上路由映射。我们分析⼀下@ute装饰器的实现源码:
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代替@ute
# 第⼀个参数就是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: @ute(..., 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.(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对应⼀个视图函数的场景)。例如:
@ute('/test2', endpoint='t1')
@ute('/test', endpoint='t1')
def test():
return'test'
3.@ute装饰器的参数
@ute和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最后的 / 符号是否严格要求,如:
例如:
@ute('/index', strict_slashes=False)  #访问 /index/ 或/index 均可
@ute('/index', strict_slashes=True)  #仅访问 /index
indexredirect_to = None,  # 重定向到指定地址如:
例如:
@ute('/index/<int:nid>', redirect_to='/home/<nid>')
def func(adapter, nid):
return"/home/888"
@ute('/index/<int:nid>', redirect_to=func)
subdomain = None,  # ⼦域名访问,什么是⼦域名:主⼲域名是www.leeoo  admin.leeoo就是admin⼦域名
例如:
from flask import Flask, views, url_for
app = Flask(import_name=__name__)
# 配置服务器地址和端⼝
# 当访问admin.leeoo/时才会⾛这个路由
@ute("/", subdomain="admin")
def static_index():
"""Flask supports static subdomains
This is available ur-domain.tld"""
ur-domain.tld"
# 动态⼦域名,访问user1.leeoo/dynamic,则相当于将'user1'作为参数传⼊username_index()视图函数
@ute("/dynamic", subdomain="<username>")
def username_index(username):
"""Dynamic subdomains are also supported
Try going ur-domain.tld/dynamic"""
return username + ".your-domain.tld"
if__name__ == '__main__':
app.run()
⼆、CBV
1.Flask中的CBV
在Flask中也可以使⽤类似Django的CBV。
from flask import views
class UserView(views.MethodView):
def get(self, *args, **kwargs):
return'GET'
def post(self, *args, **kwargs):
return'POST'
# CBV不能使⽤装饰器添加路由,只能使⽤app.add_url_rule(),注意as_view()的参数会被传递给view_func.__name__,然后会赋值给endpoint
app.add_url_rule('/user', None, UserView.as_view("userview"))
Flask的CBV和django的很类似。当⽤户的请求到达时,通过MethodView类的dispatch_request()⽅法,来反射到对应的get或post等视图函数。如下源码所
def dispatch_request(self, *args, **kwargs):
# ⽤户请求类型hod先转化为⼩写,然后看视图类中是否存在对应的⽅法
meth = getattr(self, hod.lower(), None)
# If the request method is HEAD and we don't have a handler for it
# retry with GET.
if meth is None hod == "HEAD":
meth = getattr(self, "get", None)
# 如果meth为None,则说明⽤户请求类型没有对应的处理函数,报错
assert meth is not None, "Unimplemented method %r" % hod
# 否则调⽤对应视图函数
return meth(*args, **kwargs)
2.视图类的静态属性
from flask import views
# 实现⼀个⾃定义装饰器
def wrapper(func):
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
class UserView(views.MethodView):
methods = ['GET']  # 限制⽀持的请求类型
decorators = [wrapper, ]  # 在这⾥使⽤⾃定义装饰器,会⾃动批量添加到各个视图函数
def get(self, *args, **kwargs):
return'GET'
def post(self, *args, **kwargs):
return'POST'
app.add_url_rule('/user', None, UserView.as_view("userview"))
我们可以定义静态属性methods来限制该视图类接收的请求类型。可以定义decorators来批量的对类中的视图函数(get、函数)添加⾃定义装饰器(当然也可以⾃⼰⼿动给需要的视图函数添加)。
三、⾃定义⽀持正则的动态路由
我们在使⽤Flask的动态路由时,Flask默认为我们提供了⼏种数据类型。参考:
1.Flask默认⽀持的动态参数数据类型
drop table if exists admin我们可以在app.verters中看到Flask默认⽀持的数据类型:
#: the default converter mapping for the map.
DEFAULT_CONVERTERS = {
"default": UnicodeConverter,
"string": UnicodeConverter,
"any": AnyConverter,
"path": PathConverter,
"int": IntegerConverter,
"float": FloatConverter,
"uuid": UUIDConverter,
}
该字典中,key为⽀持的类型名,value即为提供转换功能的转换器。
如果我们想要Flask的动态路由⽀持正则表达式,则需要⾃⼰定义⼀个正则转换器,并添加到app.verters中。
2.⾃定义正则转换器
uting import BaseConverter
class RegexConverter(BaseConverter):
"""
⾃定义URL匹配正则表达式
"""
def__init__(self, map, regex):
super(RegexConverter, self).__init__(map)
< = regex
def to_python(self, value):
"""
路由匹配时,匹配成功后传递给视图函数中参数的值
:param value:
:return:
"""
return value
def to_url(self, value):
使⽤url_for反向⽣成URL时,传递的参数经过该⽅法处理,返回的值⽤于⽣成URL中的参数
:param value:
:
return:
"""
val = super(RegexConverter, self).to_url(value)
return val
# 添加到flask中
app.verters['regex'] = RegexConverter
@ute('/index/<regex("\d+-\d+"):nid>')
def index(nid):
print(url_for('index', nid='888-999'))
return'Index'
这样,我们就可以在动态路由中,使⽤正则表达式了。但是注意,这⾥传递进来的nid是字符串格式(我们也可以在RegexConverter类的to_python中对其进⾏处理)。
四、Flask请求处理流程
1.启动服务器
我们知道,最简单的Flask代码如下:
from flask import Flask
app = Flask(__name__)
if__name__ == '__main__':
app.run()
Flask是建⽴在 werkzeug 这个WSGI服务器上的。
当app.run()运⾏Flask的时候,底层的werkzeug会开始监听指定的端⼝,准备接受⽤户请求。
我们可以在Flask类中的run⽅法到如下代码:
try:
run_simple(host, port, self, **options)
这个run_simple的第三个参数就是满⾜WSGI协议调⽤的⽅法。这⾥传⼊了self,这个self就是代指app对象⾃⼰。所以当服务器接收到请求时,会调⽤
app(),其实就是调⽤app中的__call__()⽅法。
2.接收请求
我们看Flask类中__call__的源代码:
def__call__(self, environ, start_response):
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app` which can be
wrapped to applying middleware."""
return self.wsgi_app(environ, start_response)
这个__call__⽅法是在请求到达的时候才会被调⽤。⽽被调⽤时参数是由werkzeug服务器传⼊的,其中environ是请求相关的信息,⽽start_response是服务器提供给Flask框架⽤来封装响应头的函数引⽤。
可以参考:中WSGI原理。
3.处理请求和Session
所有Flask框架的源码都是从wsgi_app()这个函数开始的:
def wsgi_app(self, environ, start_response):
# 1.ctx = RequestContext(self, environ)
#  quest = Request(environ)
#  ctx.session = None
ctx = quest_context(environ)
error = None
try:
try:
# 2.将ctx对象加⼊上下⽂管理,
# 3.执⾏ SecureCookieSessioninterface.open_session,去cookie中获取session值,并给ctx.session重新赋值
ctx.push()
# 4.这⾥调⽤视图函数
#  app.dispatch_request()调⽤视图函数
# 5.视图函数执⾏完毕后,调⽤app.finalize_request(),进⾏善后⼯作
#  在finalize_request中调⽤process_response,将⽤户新设置的session加密序列化后写⼊response中,这⾥调⽤的是SecureCookieSessioninterface.save_session
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except:  # noqa: B001
error = _info()[1]
raise
return response(environ, start_response)
if self.should_ignore_error(error):
error = None
# 5.视图函数处理完请求,返回了响应之后,清空该次请求在上下⽂中的数据
ctx.auto_pop(error)
ctx是quest_context(environ)中返回的RequestContext实例,并将self和environ传递进去:
def request_context(self, environ):
# 实例化RequestContext,传⼊app和environ
return RequestContext(self, environ)
再看RequestContext类的构造函数:
def__init__(self, app, environ, request=None, session=None):
# Flask实例app
self.app = app
# 这⾥request我们没有传⼊,⼀定为空
if request is None:
# request_class是Request类,所以request是Request类的⼀个实例,并封装了environ(得到我们使⽤的request)
request = quest_class(environ)
self.url_adapter = None
try:
self.url_adapter = ate_url_quest)
except HTTPException as e:
# 闪现初始化为None
self.flashes = None
# session初始化为None
self.session = session
self._implicit_app_ctx_stack = []
self.preserved = False
self._preserved_exc = None
self._after_request_functions = []
4.请求处理流程图
五、蓝图
1.修改Flask项⽬⽬录结构
在划分⽬录之前,我们的static⽬录、templates⽬录以及写视图函数的app.py⽂件都位于项⽬根⽬录下。我们对⽬录进⾏以下修改:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。