flask-cbv源码分析、模板、请求与响应、session、闪现、请求扩展

1.flask写cbv

1.1 cbv模板

之前我们都是写fbv,现在我们写cbv,这样可以把get请求写在一个视图类中:

from flask import Flask, request
from flask.views import View, MethodView

app = Flask(__name__)

'''每次修改之后项目会自己启动'''
app.debug = True


class IndexView(MethodView):
    '''在这里不用再把request加入形参,直接使用全局的request就可以'''

    def get(self):
        print(request.method)
        return 'get请求'

    def post(self):
        print(request.method)
        return 'post 请求'

'''endpoint是别名,as_view()括号内也要写别名,后面详解'''
app.add_url_rule('/index',endpoint='index',view_func=IndexView.as_view('index'))
if __name__ == '__main__':
    app.run()

image
image

1.2 cbv源码分析

1.首先我们需要从as_view()源码中分析,点进原码:

    def as_view(
        cls, name: str, *class_args: t.Any, **class_kwargs: t.Any
    ) -> ft.RouteCallable:
            def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
                self = view.view_class(  # type: ignore[attr-defined]
                    *class_args, **class_kwargs
                )
# 不管走if分支还是else分支,都会返回一个current_app.ensure_sync(self.dispatch_request)(**kwargs)。这句代码其实是在执行self.dispatch_request,只不过是异步执行
                return current_app.ensure_sync(self.dispatch_request)(**kwargs)
        return view

2.当请求来了之后,执行view(),本质还是在执行self.dispatch_request,所以我们现在需要找到dispatch_request源码(我们直接点进去发现什么都没有,这是因为我们直接点进去的是View下的dispatch_request源码,而我们的self是视图类的对象,查找顺序是:对象名称空间(没有)>>>视图类名称空间(没有)>>>MethodView名称空间(找到了)):

   def dispatch_request(self, **kwargs: t.Any) -> ft.ResponseReturnValue:
        meth = getattr(self, request.method.lower(), None)
		'''meth通过反射拿到了视图类中的post或者get,此时的meth就是get或者post'''
        if meth is None and request.method == "HEAD":
            meth = getattr(self, "get", None)
		'''异步执行了get()或者post()'''
        return current_app.ensure_sync(meth)(**kwargs)

1.3 as_view()参数分析

当我们在as_view()中不传参数时,项目无法启动。所以as_view()中必须要传参数。这是因为as_view()的返回值是view,所以如果我们不传参数,所有的返回值都是view,结果一样那么必然会报错。
view源代码:

		'''view中的形参**kwargs使用了其外层函数as_view中的参数,所以这是一个闭包函数'''
            def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
                self = view.view_class(  # type: ignore[attr-defined]
                    *class_args, **class_kwargs
                )
                return current_app.ensure_sync(self.dispatch_request)(**kwargs)
			...
		view.view_class = cls  # type: ignore
		view.__name__ = name

view中的形参使用了as_view()中的参数,所以这是一个闭包函数。view.name = name,view就是闭包函数内的view,这句代码是修改了函数的名字。

拓展:当我们print(函数名.name)拿到的是一个函数名,当然我们也可以自己修改其名称,再次打印其名字就是修改之后的:

def index():
    pass

print(index.__name__)  # index
index.__name__ = 'add'
print(index.__name__)  # add

紧接上面,其实我们可以简写生成路由代码:

app.add_url_rule('/index',view_func=IndexView.as_view('index'))
# 等同于:
app.add_url_rule('/index',view_func=view)

所以如果我们不传endpoint参数,并且不在as_view()中传参数的话,那么所有的view都是一个,就会报错。
image
下面我们来看endpoint和as_view()中参数的关系,首先查看add_url_rule源码:

    @setupmethod
    def add_url_rule(
        self,
        rule: str,
        endpoint: t.Optional[str] = None,
        view_func: t.Optional[ft.RouteCallable] = None,
        provide_automatic_options: t.Optional[bool] = None,
        **options: t.Any,
    ) -> None:
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)  # type: ignore

查看_endpoint_from_view_func源码:如果endpoint没有传入,执行以下代码,直接返回view_func对应函数的名字。所以如果路径不传endpoint,那么别名就是函数名:

def _endpoint_from_view_func(view_func: t.Callable) -> str:

    assert view_func is not None, "expected view func if endpoint is not provided."
    return view_func.__name__

以后我们只要在as_view()中填写别名,就不需要再传入endpoint参数了

1.4 视图类加装饰器

如果我们要给某个视图类加装饰器,以登录认证为例,我们需要将装饰的函数写在类中decorators的列表中:

class IndexView(MethodView):
    decorators = [loginauth]
    def get(self):
        print(request.method)
        return 'get请求'

    def post(self):
        print(request.method)
        return 'post 请求'

阅读decorators源码得知:

        if cls.decorators:
            for decorator in cls.decorators:
                view = decorator(view)

decorator就是一个个认证的函数,在这里就是loginauth,view=loginauth(view),这就是装饰器的原理,这句代码相当于:

@loginauth
def view():
pass

1.5 补充

如果我们给flask的视图函数加一个登录认证装饰器,那么应该放在路由装饰器的下面,并且route中需要传入endpoint参数。

总结:
1.as_view执行流程和django一样
2.如果写视图类,那么必须继承MethodView(view中的dispatch_request直接抛异常,所以继承的父类必须要重写dispatch_request),否则需要重写dispatch_request
3.视图函数加多个装饰器,必须要传入endpoint
4.路径如果不传endpoint,那么别名就是函数名
5.视图类加装饰器,按照:decorators = [...]传入

2.模板语法

首先我们生成app对象时,需要上传一些参数,点击Flask查看参数:

def __init__(
        self,
        import_name: str,
        static_url_path: t.Optional[str] = None,
        static_folder: t.Optional[t.Union[str, os.PathLike]] = "static",
		'''静态文件,所以我们创建静态文件时路径和文件名需要从这里传入,这里只是一个默认值,如果不传参数就按照默认值'''
        static_host: t.Optional[str] = None,
        host_matching: bool = False,
        subdomain_matching: bool = False,
        template_folder: t.Optional[t.Union[str, os.PathLike]] = "templates",
		'''之所以我们要在根目录创建templates目录,是因为在这里有规定,如果我们的路径和文件名有变化,则需要从这里传值修改'''
        instance_path: t.Optional[str] = None,
        instance_relative_config: bool = False,
        root_path: t.Optional[str] = None,
    ):

模板语法可以在前端显示图片,实现if判断,for循环,放置链接 ,执行函数:
flask.py

from flask import Flask, render_template, Markup

app = Flask(__name__, template_folder='templates', static_folder='static')

def add(a, b):
    return a + b

@app.route('/')
def index():
    # 方式一:后端直接把标签引起来,前端使用{{a|safe}}
    a = '<a href="https://www.cnblogs.com/ERROR404Notfound/p/16983752.html">点我看美女</a>'
    # 方式二:后端使用Markup包起来,前端使用{{a}}
    a = Markup(a)
    return render_template('index.html', name='max', a=a,add=add)

if __name__ == '__main__':
    app.run()

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>模板语法,照片渲染</h1>
    <img src="/static/1.png" alt="">
<h1>模板语法,if</h1>
{% if name %}
<p>{{name}}</p>
{% else %}
<p>anonymous</p>
{% endif %}
<h1>链接</h1>
{{a}}
<h1>执行函数</h1>
{{add(2,3)}}
</body>
</html>

执行结果:
image

3.请求与响应

3.1 request的方法

首先发送get请求,请求路由:http://127.0.0.1:5000/?name=max

from flask import Flask, request

app = Flask(__name__)
# app.debug = True


@app.route('/', methods=['GET', 'POST'])
def index():
    print('request.method',request.method)  # GET
    print('request.args',request.args)  # ImmutableMultiDict([('name', 'max')])
    print('request.form',request.form)  # ImmutableMultiDict([])
    print('request.values',request.values)   # CombinedMultiDict([ImmutableMultiDict([('name', 'max')])])
    print('request.cookies',request.cookies)  # ImmutableMultiDict([])
    print('request.headers',request.headers) 
    '''
    Host: 127.0.0.1:5000
    Connection: keep-alive
    Sec-Ch-Ua: "Microsoft Edge";v="111", "Not(A:Brand";v="8", "Chromium";v="111"
    Sec-Ch-Ua-Mobile: ?0
    Sec-Ch-Ua-Platform: "Windows"
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.62
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
    Sec-Fetch-Site: none
    Sec-Fetch-Mode: navigate
    Sec-Fetch-User: ?1
    Sec-Fetch-Dest: document
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
    '''   print('request.path',request.path)  # /
    print('request.full_path',request.full_path)  # /?name=max
    print('request.url',request.url)  # http://127.0.0.1:5000/?name=max
    print('request.base_url',request.base_url)  # http://127.0.0.1:5000/
    print('request.host_url',request.host_url)  #  http://127.0.0.1:5000/
    print('request.host',request.host)  # 127.0.0.1:5000
    return '111'

if __name__ == '__main__':
    app.run()

接下来用postman发送post请求,请求地址为:
http://127.0.0.1:5000?name=max&age=22, 在body体中携带了键值对{"hobby":"soccer","file":文件对象}

from flask import Flask, request

app = Flask(__name__)
# app.debug = True


@app.route('/', methods=['GET', 'POST'])
def index():
    print('request.method',request.method)  # POST
    print('request.args',request.args)  # ImmutableMultiDict([('name', 'max'), ('age', '22')])
    print('request.form',request.form)  # ImmutableMultiDict([('hobby', 'soccer')])
    print('request.values',request.values)   # CombinedMultiDict([ImmutableMultiDict([('name', 'max'), ('age', '22')]), ImmutableMultiDict([('hobby', 'soccer')])])
    print('request.cookies',request.cookies)  # ImmutableMultiDict([])
    print('request.headers',request.headers)
    '''
   User-Agent: PostmanRuntime/7.31.3
    Accept: */*
    Postman-Token: 7e19ce43-ead9-47fa-b364-6e49032b735c
    Host: 127.0.0.1:5000
    Accept-Encoding: gzip, deflate, br
    Connection: keep-alive
    Content-Type: multipart/form-data; boundary=--------------------------721949126982998088027215
    Content-Length: 42306
    '''
    print('request.path',request.path)  # /
    print('request.full_path',request.full_path)  # /?name=max&age=22
    print('request.url',request.url)  # http://127.0.0.1:5000/?name=max&age=22
    print('request.base_url',request.base_url)  # http://127.0.0.1:5000/
    print('request.host_url',request.host_url)  #  http://127.0.0.1:5000/
    print('request.host',request.host)  # 127.0.0.1:5000
'''文件操作'''
	obj = request.files['file']
    print(obj)  # <FileStorage: '01cea05acc4cc0a801204029f30fa6.jpg@2o.png' ('image/png')>
    obj.save(obj.filename)
	
    return '111'

if __name__ == '__main__':
    app.run()

image
image

3.2 响应写入cookies

from flask import Flask, request,make_response

app = Flask(__name__)
# app.debug = True

@app.route('/', methods=['GET', 'POST'])
def index():
    response = 'hello world'
    res = make_response(response)
 	'''这时的res是一个对象,但是依然可以在前端正常显示'''
 	print(type(res))  # <class 'flask.wrappers.Response'>
    res.set_cookie('aa','bb')
    return res

if __name__ == '__main__':
    app.run()

image
image

3.3 响应头中写数据

from flask import Flask, request,make_response

app = Flask(__name__)
# app.debug = True

@app.route('/', methods=['GET', 'POST'])
def index():
    response = 'hello world'
    res = make_response(response)
    print(type(res))
    res.headers['cc'] = 'dd'
    return res

if __name__ == '__main__':
    app.run()

image
image
跨域在没有第三方模块的情况下也可以用这种方式来解决

4.session使用及其源码分析

4.1 session的使用

flask.py

from flask import Flask,request,render_template,session,redirect

app = Flask(__name__)
app.debug = True
app.secret_key = 'fiuhgy487y97tufgiorewy'

@app.route('/login',methods=['POST','GET'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        name = request.form.get('name')
        password = request.form.get('password')
        if name and password:
            '''如果用户名和密码有值,就重定向到/index'''
            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()

login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post">
  <p>用户名:<input type="text" name="name"></p>
  <p>密码:<input type="password" name="password"></p>
  <p><input type="submit" value="提交"></p>
</form>
</body>
</html>

image
image
image
虽然我们使用的都是全局的session,但是在不同浏览器中取到的值却不一样,flask可以很好地管理不同浏览器之间的信息。但是只要用同一个浏览器同时登陆两次,那么session中第一次保存的session就会被取代掉。

4.2 session源码分析

cookies:存在于客户端浏览器中的键值对
session:存在于服务端的键值对(django存在了django_session表中)
当我们在django中设置一个已经有的session键对应的值,django_session表中的键还是不变,变的是该键对应的值。但是在flask中没有session表。而flask将该信息存储到了浏览器中的cookies中,它的名字叫session但是它并不是一个真正的session,也不是一个cookie,因为cookie是一个键值对,而这里只有一个信息:
image
当我们再换一个账号登陆,发现该session会变化。当我们删掉该session,发现再次刷新/index页面,发现名字变成了匿名用户:
image
那么当我们在后端执行了代码:session['name'] = name后后端做了什么事呢?
1.首先是上述代码校验是否有用户名和密码,如果有,执行代码session['name'] = name
2.将session对象中的key-value组成一个字符串
3.将该字符串返回到前端,返回到浏览器中存储
4.浏览器中存储了session后,浏览器发送请求时会携带该cookie,后端取出携带过来的字符串,转到session中
5.咱们在视图函数中使用session(真正的session)中取到上次放进去的值

而在django中,后端随机生成一个字符串,将它保存在session表中,并且返回给前端一份。此时前端在发请求就会携带该随机字符串(cookie)到后端,后端取出cookie,并且区session表中做校验(在中间件中)。

4.2 源码分析

app.session_interface中配置了一个类的对象,这个就是session的执行流程,点进查看:

session_interface: SessionInterface = SecureCookieSessionInterface()

查看类SecureCookieSessionInterface源码:

class SecureCookieSessionInterface(SessionInterface):
...
'''请求来的时候会走open_session'''
    def open_session(
        self, app: "Flask", request: "Request"
    ) -> t.Optional[SecureCookieSession]:
        s = self.get_signing_serializer(app)
        if s is None:
            return None
'''根据名字,取出前端传入的cookie值'''
        val = request.cookies.get(self.get_cookie_name(app))
'''如果没有值,构造了一个空的session对象'''
        if not val:
            return self.session_class()
        max_age = int(app.permanent_session_lifetime.total_seconds())
        try:
'''如果没有过期,把字符串解密,做成session对象,后续直接用session对象'''
            data = s.loads(val, max_age=max_age)
            return self.session_class(data)
        except BadSignature:
'''如果过期提供的cookie也就不能用了,返回一个空session对象'''
            return self.session_class()

在上述代码中有一个get_cookie_name方法,这行代码就是在配置文件中拿到浏览器中cookie的键名,如果这个配置修改,那么前端cookie中的键名也会修改:

    def get_cookie_name(self, app: "Flask") -> str:
        return app.config["SESSION_COOKIE_NAME"]

image
image
image
image
当请求走的时候会执行

    def save_session(
        self, app: "Flask", session: SessionMixin, response: "Response"
    ) -> None:
  
        expires = self.get_expiration_time(app, session)
		'''把session加密转成字符串,存到cookie中'''
        val = self.get_signing_serializer(app).dumps(dict(session))  # type: ignore
        response.set_cookie(
            name,
            val,  # type: ignore
            expires=expires,
            httponly=httponly,
            domain=domain,
            path=path,
            secure=secure,
            samesite=samesite,
        )

5.闪现

当次请求先把一些数据,放在某个位置,下一次请求把这些数据取出来,一次取出就没了。
作用:
1.可以跨请求保存数据
2.当次请求,访问出错,被重定向到其他地址
django中也有该内容,就是message框架,django中有级别,flask中没有级别

from flask import Flask, request, render_template, session, redirect, flash, get_flashed_messages

app = Flask(__name__)
app.secret_key='fghyeu56t378r92wyfgh'

@app.route('/set')
def set_flash():
    name = request.args.get('name')
    flash('我是%s' % name)
    return 'flash写入'

@app.route('/get')
def get_flash():
    res = get_flashed_messages()
    print(res)
    return 'flash取出'

if __name__ == '__main__':
    app.run()

我们首先执行取出方法:
image
没有存入所以取出来的为空:
image
然后再执行写入方法,再取出,发现已经有值:
image
image
再取出一次取出的值为空,所以只能取一次
image
并且在另一个浏览器中,存入的信息不一样,取出的信息也不同

6.请求扩展

请求扩展中,请求来了,或者请求走了,可以绑定一些函数,到这里就会执行这个函数,类似于django的中间件
请求扩展有以下几种:
before_request:请求来了会走,如果他返回了四件套,就结束了
after_request :请求走了会走,一定要返回response对象
before_first_request:第一次来了会走
teardown_request:无论是否出异常,会走
errorhandler:监听状态码,404 500
template_global:标签
template_filter:过滤器
1.before_request和after_request:

from flask import Flask

app = Flask(__name__)

@app.before_request
def before():
    print('请求来了')

@app.after_request
def after(res):
    print('请求走了')
    return res

@app.route('/index')
def index():
    return 'index'

if __name__ == '__main__':
    app.run()

image
如果有多个,before_request会从上往下依次执行,after_request会从下往上依次执行:

from flask import Flask

app = Flask(__name__)

@app.before_request
def before():
    print('请求来了1')

@app.before_request
def before1():
    print('请求来了2')

@app.after_request
def after(res):
    print('请求走了1')
    return res

@app.after_request
def after(res):
    print('请求走了2')
    return res

@app.route('/index')
def index():
    return 'index'

if __name__ == '__main__':
    app.run()

image
请求走的时候我们设置cookie:

@app.after_request
def after(res):
    print('请求走了1')
    res.set_cookie('name','max')
    return res

image
在before_request中返回新手四件套之一,就会拦截请求:

@app.before_request
def before():
    print('请求来了1')
    return '不让走了'

image
2.before_first_request:第一次请求会执行,此后不会再执行

from flask import Flask

app = Flask(__name__)

@app.before_first_request
def first():
    print('第一次执行才会显示')

@app.route('/index')
def index():
    return 'index'

if __name__ == '__main__':
    app.run()

3.teardown_request:每一次请求来时候都会执行,有一个形参,是报错时才会触发,该形参就是错误,没有错误时依旧会执行该函数体代码,只不过形参是None,用来做错误日志:

from flask import Flask

app = Flask(__name__)

@app.teardown_request
def tear_down(e):
    print('teardown执行了')
    print(e)

@app.route('/index')
def index():
    l = [1,2]
    print(l[4])
    return 'index'

if __name__ == '__main__':
    app.run()

image
4.errorhandler 监听响应状态码,如果符合监听的状态码,就会走它

from flask import Flask

app = Flask(__name__)
'''也可以有@app.errorhandler(500)'''
@app.errorhandler(404)
def error_404(a):
    return '页面不存在'

@app.route('/index')
def index():
    l = [1,2]
    print(l[4])
    return 'index'

if __name__ == '__main__':
    app.run()

image

posted @   ERROR404Notfound  阅读(38)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
Title
点击右上角即可分享
微信分享提示