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,如果不传,会报错
多层装饰器的情况下,被装饰的函数加括号运行时,
会从最上面的装饰器函数开始,依次运行每一个装饰器函数里面的内层函数!!!
从上往下,依次执行!!!
image
.
.
.
.
.
.
.
.
.
.
.

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键对应的串了!!!
image
.
.
.
.
.
.
.
.
.
.

为什么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对象中的键值对来!!!
image
.
.
.
.
flask类的源码里面 wsgi_app 函数的代码,是请求来与请求出出入口!!!
image
ctx = self.request_context(environ)
这句话干3件事,先生成请求上下文对象,生成Request对象,生成空的session对象
image
.
.
.
RequestContext类里面

如果request是None,就把environ字典传到Request类的对象里
request = app.request_class(environ)
self.session = None 一开始让session等于空
image
.
.
.
ctx.push()方法的作用,
self.session = session_interface.open_session(self.app, self.request)
主要就是执行SecureCookieSessionInterface类里面的open_session方法,
从请求头里面拿cookie,然后封装到空的session对象里面去!!!
image
.
.
.
.
.
.
.
.
.
flask类里面 session_interface = SecureCookieSessionInterface() 就是一个对象
image
image
.
.
.
.
full_dispatch_request函数里面的
response = self.process_response(response) 该方法里面执行了save_session方法
image
image
image
.
.
.
.
image
image
.
.
.
请求到来的时候,将包含了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



# 同理其他的请求扩展里面也是由对应的函数触发的

.
.
.
.

posted @   tengyifan  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示