flask框架----day02( flask的cbv分析,模板 ,请求与响应,session及源码分析,闪现(跨请求保存数据),请求扩展类似于中间件 )
面试
1 数据库三大范式是什么
# 第一范式(1NF)是指数据库表的每一列都是不可分割
# 第二范式:如果表是单主键,那么主键以外的列必须完全依赖于主键;如果表是复合主键,那么主键以外的列必须完全依赖于主键,不能仅依赖主键的一部分。
# 第三范式(3NF)要求:表中的非主键列必须和主键直接相关而不能间接相关;
2 mysql有哪些索引类型,分别有什么作用
# 聚簇索引,聚集索引,主键索引,主键,如果不存在主键,隐藏一个主键,构建聚簇索引
# 辅助索引,普通索引 index
# 唯一索引 unique
# 联合索引,组合索引,多列索引:unique_to
3 事务的特性
# 原子性(Atomicity):数据库把“要么全做,要么全部做”的这种规则称为原子性
# 隔离性(Isolation):事务之间相互隔离,不受影响,这与事务的隔离级别密切相关
# 一致性(Consistency):事务执行前后的状态要一致,可理解为数据一致性
# 持久性(Durable):事务完成之后,她对数据的修改是永恒的,即时出现故障也能够正常保持
4 隔离级别
# READ UNCOMMITTED:未提交读(读未提交)
# READ COMMITTED:已提交读(读已提交)
# REPEATABLE READ:可重复读
# SERIALIZABLE:可串行化
.
.
.
.
.
.
上节课回顾
# 1 web 框架
-django 大而全
-flask 小而精
-sanic
-fastapi
-同步框架和异步框架
# 2 flask框架
-wsgiref
-werkzeug
# 3 第一个flask
# 4 登录小案例
1 注册路由 app.route(路径,methods=[请求方式get,post])
2 新手四件套:
-render_template 渲染模板 跟django有区别
-redirect 重定向
-return 字符串 返回字符串
-jsonify 返回json格式
3 请求的request对象,是全局的,直接导入使用即可,在不同视图函数中不会混乱
request.method 请求方式
request.form post请求的body体的内容转成了字典
4 session 全局的,直接导入使用即可,一定要指定秘钥app.secret_key = 'asdfasdfa33aef3aefads'
放值:session['name']='lqz'
取值:session.get('name')
5 模板的渲染
-兼容django的dtl
-更强大,可以加括号,字典可以.get .values() .items()
-{% for %}
6 转换器@app.route('/detail/<int:pk>')
# 5 配置文件
-app.config 对象
-配置中心
# 6 路由的本质
app.add_url_rule 方法
参数:
rule, URL规则
view_func, 视图函数名称
defaults = None, 默认值, 当URL中无参数,函数需要参数时,使用defaults = {'k': 'v'}
为函数提供参数
endpoint = None, 名称,用于反向生成URL,即: url_for('名称')
methods = None, 允许的请求方式,如:["GET", "POST"]
.
.
.
.
.
.
今日内容
1 flask的cbv分析
1.1 基于视图类的写法
from flask import Flask,request
from flask.views import View, MethodView
app = Flask(__name__)
app.debug = True
# 视图类, 继承MethodView,类中写跟请求方式同名的方法即可, 和django的用法一致
class IndexView(MethodView):
def get(self):
print(request.method)
return 'get 请求'
def post(self):
print(request.method)
return 'post 请求'
app.add_url_rule('/index', endpoint='index', view_func=IndexView.as_view('index'))
if __name__ == '__main__':
app.run()
.
.
.
.
.
小补充:
def add():
pass
print(add.__name__) # add
add.__name__ = 'hahaha'
print(add.__name__) # hahaha
# 不做设置的话,函数点双下name的结果就是函数名!!!
---------------------------------------------------------
.
.
.
.
1.2 cbv源码分析
# 1 IndexView.as_view('index') 执行完的结果,是个函数(view的)内存地址, 和django也比较像
# as_view方法是View类里面的方法83行
# 如果路由匹配成功后,IndexView.as_view('index')() 也就是view()
def as_view(cls, name, *class_args, **class_kwargs):
if cls.init_every_request:
def view(**kwargs: t.Any):
# 本质在执行self.dispatch_request,只是此处用了异步
return current_app.ensure_sync(self.dispatch_request)(**kwargs)
view.__name__ = name # 将view函数的__name__设置为传进来的字符串!!!
return view
# 所以路由匹配成功后,本质是在执行self.dispatch_request函数,只是用了异步
# dispatch_request方法找的是MethodView类里面的dispatch_request方法 点MethodView类的179行
--------------------------------------------------
--------------------------------------------------
# 2 请求来了,执行view()--->本质在执行self.dispatch_request---》MethodView中的
def dispatch_request(self, **kwargs):
# self是视图类的对象 去视图类对象的request对象里面反射出请求方法的字符串对应的函数名
# 也就是反射出请求方法对应的视图函数名,用变量名meth接收
meth = getattr(self, request.method.lower(), None)
# 用异步执行meth() 就是异步执行请求方法对应的视图函数!!!
return current_app.ensure_sync(meth)(**kwargs)
---------------------------------------------------
---------------------------------------------------
# 3 总结:执行原理跟django一样
.
.
.
.
route函数里面如果不传endpoint='xxx',别名就是函数名,为什么?
# 4 route里面如果不传endpoint='xxx',别名就是函数名,为什么?
# 分析一下源码
# route函数点进去发现Scaffold类 447行
@setupmethod
def route(self, rule: str, **options: t.Any):
def decorator(f: T_route) -> T_route:
endpoint = options.pop("endpoint", None) # 如果没传endpoint参数,返回None
self.add_url_rule(rule, endpoint, f, **options) # 添加路由的函数!!!
return f
return decorator
# 这个是Flask类里面1306行左右,将endpoint=None 传到add_url_rule函数里
def add_url_rule(self,rule,endpoint=None,view_func= None,):
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func) # 所以此时endpoint = 函数名了
def _endpoint_from_view_func(view_func):
assert view_func is not None, "expected view func if endpoint is not provided."
return view_func.__name__ # 返回函数名了
# 所以 route函数里面如果不传endpoint='xxx',别名就是函数名了!!!
# @app.route('/index')-----》没有传endpoint='xxx'
# endpoint 就是None-----》调用了app.add_url_rule,传入了None,
# 又会触发_endpoint_from_view_func函数 返回的就是视图函数双下name名字!!!
# 最终endpoint就等于视图函数的名字了!!!
.
.
.
.
.
.
.
as_view('index') 必须传参数,而且传进来的参数,必须是路由的【别名】
# 5 as_view('index') 必须传参数,而且传进来的参数,必须是路由的【别名】
# 因为as_view函数 127行有如下代码:
# 小写view函数是as_view内的内层函数,闭包函数
def as_view(cls, name, *class_args, **class_kwargs):
view.__name__ = name # 修改了view函数的双下name名字变成了你传入的
app.add_url_rule('/index',view_func=IndexView.as_view('index'))
# 简写成:app.add_url_rule('/index',view_func=view)
# 如果不传endpoint='xxx',
# 默认以小写的view 函数的双下name名字 作为该路由的别名
-----------------------------------------
# 假设写了一个视图函数
@app.route('/index')
def index():
return 'index'
# 记住: 如果不传endpoint参数,这个路由也会有一个别名,该别名就是下面的函数名!!!
-------------------------------------
class IndexView(MethodView):
def get(self):
return 'get 请求'
def post(self):
return 'post 请求'
# app.add_url_rule('/index', endpoint='index', view_func=IndexView.as_view('index'))
app.add_url_rule('/index',view_func=view)
# 不传endpoint参数,路由别名就是函数名,就是view函数的函数名了
# 但是我们写cbv视图类的时候,无论视图类的类名叫什么,注册路由的代码最后都会变成如下
# app.add_url_rule('/login', view_func=LoginView.as_view('xxx'))
# 简写成app.add_url_rule('/login',view_func=view)
这样就出了一个问题了,好多个路由的别名都是view函数的函数名了,
那到时候,用别名去反向解析的时候就乱套了,不知道对应的是拿一个路由了
# 所以flask框架不允许,别名有重名的情况出现,会主动报错的
@app.route('/index')
def index():
return 'index'
@app.route('/')
def index():
return 'hahaha'
# 像这种情况,不传endpoint参数,这两个路由对应的别名就都是函数名index了
# 这种情况flask框架不允许,会主动报错!!!
# 所以只要给一个视图函数,传endpoint参数,保证别名不重复,就行了
# 比如 @app.route('/',endpoint='index1')
----------------------------------------------------
----------------------------------------------------
.
.
.
.
视图类必须继承MethodView,如果继承View,
# 6 视图类必须继承MethodView,如果继承View,
# 它的dispatch_request没有具体实现,你的视图类必须重写dispatch_request
# 我们不想重写dispatch_request,就继承MethodView
def dispatch_request(self) -> ft.ResponseReturnValue:
raise NotImplementedError()
-----------------------------------------------------
.
.
.
.
视图类加装饰器,直接配置在类属性上【decorators】即可
# 7 视图类加装饰器,直接配置在类属性上【decorators】即可
就会对视图类里面的每一个视图函数起作用!!!
decorators = [auth,]
# View类的源码115行,cls是视图类,中有decorators
if cls.decorators:
for decorator in cls.decorators:
view = decorator(view) # 相当于手动执行装饰器 view=auth(view)
-----------------------------------------------------
.
.
.
.
.
.
绕了一大圈,最后就是说,只要写视图类,并注册路由,必须要给as_view()括号里面传一个路由的别名
多个视图类的路由别名不能一样!!!
cbv源码 总结
# 捋一下:add_url_rule函数里面只要不主动传endpoint='xxx',
# 就会拿后面view_func参数对应的,函数的双下name 来作为endpoint的值
# 那么现在如果as_view()括号里面不传值,就会报错!!!因为name参数没有设定默认值
# 如果传了值后,那么as_view函数执行完了,view函数的双下name的名字就是 传到as_view函数里面的值了
# view_func=IndexView.as_view('index')运行的结果就是view函数名,view_func=view
# 所以最后只要不主动传endpoint='xxx',那么别名就是view_func函数的双下name名字,也就是view函数名字
# view函数的双下name就等于as_view()括号里面传的值了
# 所以最后endpoint没传值,endpoint别名就等于as_view()括号里面传的值了!!!
# 绕了一大圈,最后就是说,只要写视图类,并注册路由,必须要给as_view()括号里面传一个路由的别名
-----------------------------------------------------
.
.
.
.
.
.
.
分析cbv源码学到的
# 源码学到的
-1 as_view 执行流程跟djagno一样
-2 路径如果不传别名,别名就是函数名(endpoint)
-3 视图函数加多个装饰器(上下顺序和必须传endpoint)
-4 视图类必须继承MethodView,否则需要重写dispatch_request,
View类里面的dispatch_request方法没写,直接抛的异常
-5 视图类加装饰器:类属性decorators = [auth,]
-----------------------------------------------------
补充:flask的路由注册使用装饰器,如果写了一个登录认证装饰器
那么应该放在路由装饰器上还是下?
放在路由装饰器下面
而且 路由必须传endpoint,如果不传,会报错
多层装饰器的情况下,被装饰的函数加括号运行时,
会从最上面的装饰器函数开始,依次运行每一个装饰器函数里面的内层函数!!!
从上往下,依次执行!!!
.
.
.
.
.
.
.
.
.
.
.
2 模板 Markup方法包对应的标签字符串 就可以在html上渲染出对应的标签
2.1 py
from flask import Flask, render_template, Markup
# static_folder参数表示静态文件的路径 template_folder参数表示模板的存放路径
app = Flask(__name__, template_folder='templates', static_folder='static')
# 模板的路径必须是templates,因为实例化app对象时,双下init传入的
app.debug = True
def add(a, b):
return a + b
@app.route('/')
def index():
a = '<a href="http://www.baidu.com">点我看美女啊啊啊</a>' # 不存在xss攻击,处理了xss
a2 = Markup(a)
return render_template('index.html', name='lqz', a=a, a2=a2, add=add)
# a 对应的字符串直接渲染到html里面,还是字符串
# {{a|safe}} 这样在html里面就能将字符串渲染成a标签了
# 用Markup把字符串包一下,也能将字符串渲染成标签
# 传个函数到html页面上,{{add(4,5)}} 在页面上可以直接给函数传参并运行
if __name__ == '__main__':
app.run()
---------------------------------------------
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>哈哈哈</h1>
<img src="/static/123.png" alt="">
<h2>{{name}}</h2>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
<h2>标签渲染</h2>
<a href="http://www.baidu.com">点我看美女</a>
<p> {{a|safe}} </p>
<p> {{a2}} </p>
<h3>执行函数</h3>
<p> {{add(4,5)}} </p>
</body>
</html>
---------------------------------------------
# 补充
Flask类的双下init方法里面:
template_folder = "templates",
static_folder = "static",
---------------------------------------------
.
.
.
.
.
.
.
.
.
3 请求与响应
# 请求:全局的request对象
# 响应:四件套
from flask import Flask, request, make_response,render_template
app = Flask(__name__)
app.debug = True
@app.route('/', methods=['GET', 'POST'])
def index():
#### 请求
# request.method 提交的方法
# request.args get请求提及的数据
# request.form post请求提交的数据
# request.values post和get提交的数据总和
# request.cookies 客户端所带的cookie
# request.headers 请求头
# request.path 不带域名,请求路径
# request.full_path 不带域名,带参数的请求路径
# request.url 带域名带参数的请求路径
# request.base_url 带域名请求路径
# request.url_root 域名
# request.host_url 域名
# request.host 只拿ip地址 127.0.0.1:500
print(request.method)
print(request.args)
print(request.form)
print(request.values)
print(request.cookies)
print(request.headers)
print(request.path)
print(request.full_path)
print(request.url)
print(request.base_url)
print(request.host_url)
print(request.host)
obj = request.files['file'] # 根据键取对应的文件对象
obj.save(obj.filename) # 将文件以文件名保存在当前路径下!!!
------------------------------------------------------------
### 响应 四件套
# 1 响应头中写入cookie
str1 = 'hello'
res = make_response(str1) # 往响应对象里面放东西
print(type(res))
res.set_cookie('xx','yy')
# 往响应对象的头里面放cookie,如果是浏览器,就自动保存到浏览器的cookie里面去了
# 不设置过期时间 ,页面关掉cookie就过期了
return res # 直接返回字符串,框架内部会自动将字符串用make_response方法包一下
------------------------------------------------------------
# 2 响应头中写数据 ( 新手四件套,都用make_response包一下 )
response = render_template('index.html')
res = make_response(response) # flask.wrappers.Response
print(type(res))
res.headers['qq']='ww' # 解决跨域就可以通过该方法,往响应头里面加数据
return res
if __name__ == '__main__':
app.run()
# 总结就是想往响应头里面放参数,就用make_response将4件套包一下,
# 并return make_response函数的返回值
------------------------------------------------------------
.
.
.
.
.
.
.
.
.
.
.
4 session及源码分析
4.1 session的使用
from flask import Flask, request, session, render_template, redirect
app = Flask(__name__)
app.debug = True
app.secret_key = 'asdfas33asdfasf'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
name = request.form.get('name')
password = request.form.get('password')
print(password)
session['name'] = name
return redirect('/index')
@app.route('/index', methods=['GET', 'POST'])
def index():
return 'hello %s' % session.get('name', '匿名用户')
if __name__ == '__main__':
app.run()
.
.
.
.
.
.
.
4.2 session的源码分析
cookie : 存在于客户端浏览器的键值对
session: 存在于服务端的键值对 # django 放在了django_session表中
flask中,session根本就没有在服务端存!!!
---------------------------------------------
# flask中,叫session,问题来了,存哪里了?
加密后,放到了cookie中
如果session发生了变化,我们的浏览器里的cookie也会跟着变
对于开发者来说,可以把 session 简单地看成字典,所有的操作都是和字典一致的。
# flask默认通过SecureCookieSessionInterface对象管理session,
# 其重写了SessionInterface对象的open_session方法和save_session方法,
# 将用户的数据加密后存储在cookie中。
---------------------------------------------
---------------------------------------------
---------------------------------------------
# 源码部分:
# 1 Flask类里面有一个属性 session_interface 接收的是一个对象
# 在 Flask 中,所有和 session 有关的调用,都是转发到 self.session_interface 的方法调用上
session_interface = SecureCookieSessionInterface() # 源码551行
这个就是session的执行流程
# 2 SecureCookieSessionInterface类中有两个非常重要的方法:
请求来了,会执行 open_session # 源码360行
请求走了会执行 save_session # 源码376行
Flask类里面,default_config属性对应的配置字典 源码488行里,
配置了 "SESSION_COOKIE_NAME": "session",
----------------------------------------------------------
# 请求来了,会执行该代码,拿到请求头里cookies对应的字典里,session键对应的串!!!
# session.py文件 的源码360行
def open_session(self, app, request) :
# 检测是否设置了secret_key参数,返回一个签名对象
# 这个签名对象作用就是请求走的时候对session字典加密成字符串,
# 最后以 response.set_cookie('session','字符串') 的形式放到响应头里面去,
# 这样浏览器接收到响应后就会,自动保存到浏览器的cookie里面去了
# 请求来的时候,通过该签名对象,去将字符串解码,解出来的数据再放到空的session对象里面去
# 如果在解码的过程中,发现字符串有被动过,它会直接放弃使用。
# get_signing_serializer 保证了 cookie 和 session 的转换过程中的安全问题。
s = self.get_signing_serializer(app)
if s is None:
return None
# 1 取出前端传入的cookie的session信息 就是加密的字符串
# get_cookie_name(app) 方法就是用 app.config["SESSION_COOKIE_NAME"]
# 得到的就是 "session" 这个字符串
# 换句话说,如果你把flask的配置文件里面的session换成其他名字
# 你在浏览器里,cookie下面看到的就不是session这个名字了,就变成其他名字了!!!
val = request.cookies.get(self.get_cookie_name(app))
# 2 如果是第一次请求,没有val,构造了一个空session对象
if not val:
return self.session_class()
# 获取session的失效时间
max_age = int(app.permanent_session_lifetime.total_seconds())
try:
# 对session信息进行解码得到用户信息
# 如果时间已经过期了,loads方法运行的过程中就会报错!!!
# 如果没有过期,解密,放到session对象里面去,
# 这样空的session对象里面就有原来登录成功后,放在session里面的一些用户信息了,
# 比如用户名,用户id这些,一般不会往session里面放密码这些,敏感信息的
# 后续直接用session对象去取即可
# 如果在解码的过程中,发现字符串有被动过,它会直接放弃使用。这样data就是个空了!!!
data = s.loads(val, max_age=max_age)
# 返回有用户信息的session对象,如果data是个空,那么session对象也是个空了
return self.session_class(data)
except BadSignature:
# 如果过期了,也是构造了一个空session对象
return self.session_class()
# 请求走了,会执行该代码
# session.py文件 的源码376行
def save_session(self, app, session, response) :
name = self.get_cookie_name(app) # 从config对象里面取配置信息,session串的名字
# 如果session是空的,直接把cookie删掉
if not session:
if session.modified: # 判断session是否被更新过,如果更新过就要重新计算,并设置 cookie
response.delete_cookie(
name,
domain=domain,
path=path,
secure=secure,
samesite=samesite,
httponly=httponly,
)
return
expires = self.get_expiration_time(app, session) # 获取失效的时间点
# 把session加密转成字符串
val = self.get_signing_serializer(app).dumps(dict(session))
# 再写到cookie中
response.set_cookie(
name,
val,
expires=expires,
。。。
)
----------------------------------------------
# 扩展,想把session放到redis或mysql中,
思路是创建一个类继承SessionInterface,重写open_session,save_session 方法
自己写逻辑,比如在登录成功后,该生成的随机字符串,往redis中添加,
主要是怎么自动让app对象用你自己写的session类????
----------------------------------------------
# 已经有人帮咱们写了,第三方的模块 Flask-Session
参考博客 https://blog.csdn.net/weixin_30919235/article/details/101275512
----------------------------------------------
参考博客 https://blog.csdn.net/weixin_30919235/article/details/101275512
# 自己写大致代码思路:
可以模仿着SecureCookieSessionInterface这个类来写,该类继承了SessionInterface类,
并且重写了open_session,save_session 方法,
而且Flask类里面的属性 session_interface = SecureCookieSessionInterface()
所以我们重写类继承SessionInterface类,那么最后必须把app对象的session_interface属性
等于我们自己写的类的对象,不用的就是Flask类里面默认的SecureCookieSessionInterface类的对象了
from flask.sessions import *
session_json_serializer = TaggedJSONSerializer()
# 自己写的类
class JsonFileSessionInterface(SessionInterface):
serializer = session_json_serializer
session_class = SecureCookieSession
# 可能自带的SecureCookieSession类不适用
# 可能还需要自己写一个session_class属性对应的类
def __init__(self, app=None):
self.app = app
if app is not None:
# 替换app的session_interface属性
app.session_interface = self._get_interface(app)
def _get_interface(self, app):
# 加载配置参数返回本身,必须配置'SESSION_TYPE'和'MY_SESSION_PATH'参数,否则使用默认的session
config = app.config.copy()
if config['SESSION_TYPE'] == 'file':
if not config['MY_SESSION_PATH']:
return SecureCookieSessionInterface()
self.path = app.static_folder + config['MY_SESSION_PATH'] # session文件路径
self.permanent = app.permanent_session_lifetime.total_seconds() # 失效时间
return self
# 如果条件满足就用我们自己的写类的对象作为app.session_interface对应的值
return SecureCookieSessionInterface()
def open_session(self, app, request):
# 获取session数据,自己写逻辑
pass
def save_session(self, app, session, response):
# 保存session信息,自己写逻辑
pass
# 还要配置文件配置一些参数
SESSION_TYPE = ‘redis’ # 保存在redis中
# 设置连接哪个redis,及哪个库
SESSION_REDIS = redis.StrictRedis(host="127.0.0.1", port=6390, db=4)
SESSION_TYPE = ‘null’ # 采用flask默认的保存在cookie中
# 最后在app.run 前 先替换Flask类里面自带的session_interface对应的对象
JsonFileSessionInterface(app=app)
----------------------------------------------
浏览器里面的cookie里面的 session 就是键 里面的串就是对应的值
app.config["SESSION_COOKIE_NAME"] 得到的就是'session'这个字符串名字
val = request.cookies.get('session') 就从浏览器请求头里面,拿到了session键对应的串了!!!
.
.
.
.
.
.
.
.
.
.
为什么session.modified能判断出session是否被修改了 补充
# open_session:前端写到cookie到后端,后端取出cookie对应的value值,解密,
转到session对象中,后续再视图函数中,使用session即可
# save_session:请求走的时候,校验session有没有被改过,如果被改了,删除cookie,重新设置cookie
# session用起来像字典---》如何做,一个对象可以像字典一样使用, __getitem__ __setitem__,
只要触发了__setitem__ 就说明动了,对象属性 modified,一开始是false,
只要触发了__setitem__,将对象属性 modified设置为true,
后期只要判断modified,就可以判断session有没有被改过!!!
.
.
.
.
.
.
session的签名算法 补充
# 检测是否设置了secret_key参数,返回一个签名对象
s = self.get_signing_serializer(app)
# 取出前端传入的cookie的session信息 就是value值
val = request.cookies.get(app.session_cookie_name)
# 对session信息进行解码得到用户信息
data = s.loads(val, max_age=max_age)
# 返回有用户信息的session对象
return self.session_class(data)
--------------------------------------
# get_signing_serializer 保证了 cookie 和 session 的转换过程中的安全问题。
# 如果 flask 发现请求的 cookie 被篡改了,它会直接放弃使用。
# 这样本次请求的session对象就是个空对象了,后端只需要校验,session对象是不是空,
# 就能确定当次请求是否合法了!!!
def get_signing_serializer(self, app):
if not app.secret_key:
return None
signer_kwargs = dict(
key_derivation=self.key_derivation,
digest_method=self.digest_method )
return URLSafeTimedSerializer(app.secret_key,
salt=self.salt,
serializer=self.serializer,
signer_kwargs=signer_kwargs )
# 我们看到这里需要用到很多参数:
secret_key:密钥。这个是必须的,如果没有配置 secret_key 就直接使用 session 会报错
salt:盐 为了增强安全性而设置一个 salt 字符串
serializer:序列算法
signer_kwargs:其他参数,包括摘要/hash 算法(默认是 sha1)和 签名算法(默认是 hmac)
URLSafeTimedSerializer 是 itsdangerous 库的类,主要用来进行数据验证,增加网络中数据的安全性。
.
.
.
.
.
.
.
.
.
4.3 session执行原理
登录成功先把数据写到session对象中 比如session['name'] = username
把session对象中的key-value取出来后,加密生成一个字符串,
写入响应头的cookie里 res = make_response(response)
res.set_cookie('session', 'yy')
这样在登录成功后,客户端在发送请求的时候,会在请求头里面带着cookie到后端
后端拿到cookie里面的字符串后,解密再转到flask的session对象中
这样我们在视图函数中使用session对象就可以取出,上次放进session对象中的数据了
登录成功后,会将生成的session对象中的键值对取出来,组装成字符串,写入到cookie中
再向浏览器发请求,浏览器会自动携带cookie,不是写到cookie,字打错了
服务器拿到请求头中的cookie,解密后,转到session对象中,这样在视图函数中使用session,
就能取出登录成功放入的session对象中的键值对来!!!
.
.
.
.
flask类的源码里面 wsgi_app 函数的代码,是请求来与请求出出入口!!!
ctx = self.request_context(environ)
这句话干3件事,先生成请求上下文对象,生成Request对象,生成空的session对象
.
.
.
RequestContext类里面
如果request是None,就把environ字典传到Request类的对象里
request = app.request_class(environ)
self.session = None 一开始让session等于空
.
.
.
ctx.push()方法的作用,
self.session = session_interface.open_session(self.app, self.request)
主要就是执行SecureCookieSessionInterface类里面的open_session方法,
从请求头里面拿cookie,然后封装到空的session对象里面去!!!
.
.
.
.
.
.
.
.
.
flask类里面 session_interface = SecureCookieSessionInterface() 就是一个对象
.
.
.
.
full_dispatch_request函数里面的
response = self.process_response(response) 该方法里面执行了save_session方法
.
.
.
.
.
.
.
请求到来的时候,将包含了request与session对象的ctx对象放到flask的用线程/协程号区分的大字典中!!!
我们的导入语句 from flask import request,session
内部就是根据当前线程或协程号,到大字典里取到ctx请求上下文对象,再去ctx对象里面取request对象!!!
.
请求结束后
根据当前线程/协程的唯一标志,将大字典里面的该线程/协程对应的数据移除!!!
.
.
.
.
5 闪现
# djagno中有这个东西吗?
message消息框架
---------------------------------------------------
# flash 翻译过来的
当次请求先把一些数据,放在某个位置
下一次请求,把这些数据取出来,取完,就没了
# 作用:
1 可以跨请求,来保存数据
2 当次请求,访问出错,被重定向到其他地址,重定向到这个地址后,拿到当时的错误
---------------------------------------------------
用法:
# 设置 闪现
flash('%s,我错了'%name) # 可以设置多次,放到列表中
flash('超时错误',category="debug") # 分类存
# 获取 闪现
get_flashed_messages() # 取完就没了
get_flashed_messages(category_filter=['debug']) # 只会取出该'debug'类型的flash数据
# 本质,放到session对象中,最后放到浏览器的cookie中
------------------------------------------------
------------------------------------------------
from flask import Flask,request, flash, get_flashed_messages
app = Flask(__name__)
app.secret_key = 'sadfa33aesfasdf'
@app.route('/set_flash')
def set_flash():
name=request.args.get('name')
flash('%s,我错了'%name) # 本质是写入到session中,再取出来加密生成字符串,再写到cookie里面去
return '写入了'
@app.route('/get_flash')
def get_flash():
res = get_flashed_messages() # 从cookie中取出字符串,解密,放到flask的session对象中
# 然后就可以取出上次放到session对象中的字符串了
print(res)
return '取出来'
if __name__ == '__main__':
app.run()
# 注意闪现是针对的同一个用户,前一个请求往session中放数据,后面的请求可以从session中取数据
# 另一个用户是不能取到,该用户放到session里的数据的!!!
# 只针对于同一个用户,可以跨请求,前一个请求放数据,后面的请求拿数据!!!
------------------------------------------------
.
.
.
.
.
.
.
.
.
.
6 请求扩展( 类似于django的中间件 )
# 请求扩展中: 请求来或请求走,可以绑定一些函数,这样请求到这,就会执行绑定的函数
# 在flask中就用请求扩展,来代替djagno的中间件
-----------------------------------------------------
# 好几个请求扩展
before_request # 请求来了会走,如果他返回了四件套,就结束了
after_request # 请求走了会走,一定要返回response对象
teardown_request # 无论视图函数是否出异常,执行完视图函数后,都会走
# 可以用该请求扩展装饰,错误日志函数,来记录错误日志
errorhandler # 可以监听到对应的响应状态码,触发装饰的函数
template_global # 标签 可以忽略
template_filter # 过滤器 可以忽略
before_first_request # 项目启动,第一个请求来了会走 新版本已经被弃用了
.
.
.
请求扩展示例
from flask import Flask, request,render_template
app = Flask(__name__)
# 请求来了,执行的函数,从上往下执行
@app.before_request
def hahaha1():
print('我来了111')
return '停止了' # 如果在该请求扩展里面返回了四件套,说明请求被被拦截了,就走不到视图函数了
# 会直接开始走返回的中间件了
@app.before_request
def hahaha2():
print('我来了222')
# 请求走了,执行的函数,从下往上执行!!!
@app.after_request
def heihei1(response):
print('我走了111')
return response # 必须要返回响应对象
# 请求走了,执行的函数
@app.after_request
def heihei2(response):
print('我走了222')
return response # 必须要返回响应对象
@app.route('/index')
def index():
return 'index'
if __name__ == '__main__':
app.run()
# 所以页面显示的是停止了,
# 后端打印的是:
我来了111 # 不会走下一个app.before_request修饰的函数了,也不会走视图函数了
我走了222
我走了111
------------------------------------------------
------------------------------------------------
# 2 项目启动后的第一个请求,flask的最新版本,该中间件已经被弃用了
@app.before_first_request
def first():
print('我的第一次')
------------------------------------------------
------------------------------------------------
# 3 teardown_request,无论视图函数是否出错,都会执行它
# 一般用了做记录错误日志
# 执行完视图函数后,就会执行被app.teardown_request装饰的函数
# 无论该视图函数是否出错,都会执行它,没出错,e就是none
@app.teardown_request
def teardown(e):
print(e)
print('执行我了')
@app.route('/index')
def index():
print('index')
return 'index'
if __name__ == '__main__':
app.run()
# 执行结果:
index
None
执行我了
-------------------------------------------------
-------------------------------------------------
# 4 errorhandler 监听响应状态码,如果符合监听的状态码,就会走它装饰的函数
@app.errorhandler(404)
def error_404(arg):
return "404错误了"
@app.errorhandler(500)
def error_500(arg):
return "500错误了"
@app.route('/index')
def index():
a = [1,2,3]
print(a[9])
return 'index'
if __name__ == '__main__':
app.run()
# 访问不存在的路径就会,走到404对应的函数里面去
# 访问index,由于服务器会报错,所以走到500对应的函数里面去了
-------------------------------------------------
-------------------------------------------------
# 5 template_global 在模板中直接使用该标签
@app.template_global()
def add(a1, a2):
return a1 + a2
# 在模板里面就可以直接这样用 {{add(7,8)}} 就会运行add函数
--------------------------------------------------
# 6 template_filter 在模板中直接使用该过滤器
@app.template_filter()
def db(a1, a2, a3):
return a1 + a2 + a3
# 在模板里面就可以直接这样用 {{6|db(7,8)}} 就会执行6+7+8
@app.route('/')
def index():
# a = [1, 2, 3]
# print(a[9])
return render_template('index1.html')
if __name__ == '__main__':
app.run()
.
.
.
.
.
源码里什么地方执行请求扩展的
# 1818行左右 rv = self.preprocess_request() 点进去
# before_request请求扩展的执行
def preprocess_request(self) :
for name in names:
# before_request_funcs 是个字典,当你用了多个before_request请求扩展后
# 会把每一个请求扩展下面的函数名,放到该字典里面去了
if name in self.before_request_funcs:
for before_func in self.before_request_funcs[name]:
# 把字典里面的函数名一个个的拿出来,并加括号运行
rv = self.ensure_sync(before_func)()
# 如果该函数有返回值,这里for循环就直接结束了
# 所以这也是为什么在before_request请求扩展的下面的函数
# 不能有返回值的原因,一有返回值,其他的请求扩展都不走了,视图函数也不走了
# 直接就返回了!!!
if rv is not None:
return rv
# 同理其他的请求扩展里面也是由对应的函数触发的
.
.
.
.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY