Flaskday08
Flask 中的钩子函数 & g 对象
目录
介绍
在实际开发中,我们往往会在客户端和服务器交互的过程中,需要进行一些预处理与扫尾工作,比如:
- 在请求开始的时候,根据需求进行权限的校验;在业务逻辑开始时候创建数据库连接等;
- 在请求结束的时候,,指定数据的交互格式;释放数据库连接等。
你当然可以在指定视图函数中加入这些预处理与扫尾工作,但往往要在多个视图函数中编写这些重复功能的代码,而 Flask 提供了钩子函数就是为了解决这一问题。下面介绍比较常用的四个钩子函数,其实也叫做请求扩展。
brefore_first_request()
before_request()
after_request()
teardown_request
而当你的项目使用了蓝图之后,那么你所创建的蓝图也有自己的请求钩子,且只在该蓝图下才会起作用,并且钩子函数相比上面所列举的也会有稍许不同,你可以理解为钩子函数可以这么分为两层来使用,一层是你没有创建蓝图之前所谓的 app
层,而创建蓝图之后就是 blueprint
层,然而其实你没有创建蓝图之前,即只有一个主 app
下定义的路由规则下,其默认是在以 None 为名字的蓝图下面的。简而言之,在实际开发中,我们通常使用下面的四个钩子函数(这里假定你开发肯定使用了蓝图~)
before_app_first_request()
before_app_request()
after_app_request()
teardown_app_request()
而像我所说,钩子函数需要完成的事情是预处理与扫尾工作并且避免在每个视图函数中编写重复的代码,那么实际上上面所说的四个钩子函数的运行顺序也和我所列举的顺序一样,具体到每一个函数的功能我们下面慢慢展开。
before_app_first_request()
如上图所描述的,执行顺序先于 before_app_request
实际上,这个钩子函数仅在第一次请求的时候去调用,比如初始化加载一次性的数据,执行完这个钩子函数才会去执行 before_app_request
,你可以理解为项目启动后第一次请求的时候执行。
before_app_request()
这才是学习的重点,实际上这才是预处理与扫尾工作我们真正使用到的钩子函数。before_app_request
:请求已经到达了服务器,但是还没有进入到具体的视图函数之前调用,你可以通俗地把这个钩子函数看做为 “一层滤网”,在满足这层钩子所修饰的函数之后,才能进入视图函数,返回视图函数的返回值,否则根本进入不了视图函数,这也是经常用来作权限验证的钩子函数,并且这个函数并不需要参数。
after_app_request()
after_app_request()
在每次请求结束后如果处理逻辑没有异常抛出则运行,而这个函数带有参数,用来接收响应对象,从而修改响应的内容,比如说修改响应头等等。
teardown_app_request()
teardown_app_request()
也是请求结束后运行,但是唯一与 after_app_request()
不同的是,不管是否出现异常都会运行。此外这个函数也需要一个参数来接收异常(没有异常的情况下这个参数的值为 None
),一般用来释放程序占用的资源,比如释放数据库链接等。
请求过程总结
当一个请求首次进入的时候,先通过 before_app_first_request()
(未断开连接之后的每次请求不再调用),然后会通过 before_app_request()
,而后执行满足条件下的视图函数,如果程序没有异常,则会调用 after_app_request()
,这也是我们上面将这个钩子函数带了括号的原因,程序出现异常则会调用 teardown_app_request()
(程序是否异常都会调用 teardown_app_request()
,只是是这么一个处理逻辑 )。
具体实例
假设已经有一个 User
模型,下面则创建一个 user
蓝图(记得去注册蓝图),并使用 before_app_request()
进行简单的权限验证。
from flask import Blueprint, g
from apps.user.models import User
# 创建登录后才能访问的路径列表
required_login_list = ['/user/center', '/user/change', '/user/publish']
@user_bp.before_app_request
def before_request1():
if request.path in required_login_list:
id = session.get('uid')
if not id:
return render_template('user/login.html')
else:
user = User.query.get(id)
g.user = user
@user_bp.route('/center')
def user_center():
return render_template('user/center.html', user=g.user)
g 对象
上面代码中 highlight 的代码用到了 g
对象,g
对象则是在应用上下文中存储数据的命名空间对象,是由 Flask 底层去创建的 在一个请求之间用它来存储数据是再好不过了。那么一个应用上下文的生命周期是当一个请求结束了,当前的 g
也就失效了。
看上面的代码,我们显示创建了一个登录后才能访问的路径列表,接着使用钩子函数处理这个逻辑:
- 如果所访问的路由(这里假设我们访问
/user/center
)是我们所要求内的需要登录之后才能访问的路由,那么我们先获取他是否登录了(获取session
中的uid
)- 如果没有
uid
说明,用户没有登录,我们则跳转到登录页面进行登录; - 如果
uid
存在,说明用户已经登录, 我们获取uid
并到数据库查询,并将查询的user
赋值给g.user
,此时按照上面所说的顺序我们正常进入视图函数也就是user_center()
这个函数,从而进入到用户中心的页面,从而传递 user 对象给到模板那边,让模板那边也知晓我这个用户是已经登录了并且会有此用户显示(模板页面设置了相应的逻辑)。
- 如果没有
问题又来了,g.user
?是 g 对象本来有一个 user
属性吗,其实并非如此,看下面这个简单的例子:
class G:
pass
g = G()
g.username = 'zhangsan'
上面简单的例子,我们定义了类 G
,接着实例化类,创建对象 g
,接着给 g
动态地去创建 username
属性,那么 G 中也就有了 username
这个属性了,这样是允许的,那么上面也就说的通了。