zoukankan      html  css  js  c++  java
  • [Python自学] Flask框架 (2) (Session、中间件、特殊装饰器)

    一、使用before_request装饰器

    1.before_request装饰器

    [Python自学] Flask框架 (1)的第八节中,我们使用了自定义装饰器实现登录验证功能。

    但是这种方式还是比较麻烦,但可以适用于部分视图函数需要添加额外功能的场景。

    要统一给视图函数加登录验证功能,还可以使用before_request装饰器(Flask给我们提供):

    @app.before_request
    def auth():
        print("before_request")

    我们只需要在自定义函数上使用@app.before_request装饰器,每个视图函数执行之前auth函数都会被执行。

    @app.before_request
    def auth():
        print("before_request")
    
    @app.route('/users', methods=['GET', 'POST'])
    def user_list():
        print("users Page view function")
        if request.method == 'GET':
            return render_template('users.html', user_list=USER_INFO)

    运行结果:

    before_request  # 先打印
    users Page view function  # 后打印

    说明,auth函数是在user_list视图函数之前被执行的。

    2.在before_request中实现登录验证

    @app.before_request
    def auth():
        if request.path == '/login':
            return None  # return None表示没有返回值,即当访问页面login的时候,放行
        
        # 访问其余页面都要通过验证流程
        if session.get('user'):
            return None  # 如果session存在user,说明已经登录,放行
        
        # 否则,跳转到login页面
        return redirect('/login')

    注意,被before_request装饰器装饰的auth函数。在没有返回值(return None)的情况下,会接着执行视图函数,如果有返回值,则和视图函数的返回值效果是一样的(内容会返回给前端页面)。

    二、模板继承与导入

    Flask中模板继承于导入和django是一模一样的,可以参考:[Python自学] day-21 (1) (请求信息、html模板继承与导入、自定义模板函数、自定义分页) 对应章节。

    三、Session补充

    1.Session原理

    Flash的Session默认是保存在加密Cookie中的,下面简述一下这种形式Session的工作原理:

    1)当用户第一次请求时(此时该用户没有session),Flask发现用户Cookie中没有"session",则会在内存中创建一个session对象(继承于Dict,拥有字段的所有操作)。

    2)在后续的业务逻辑中,视图函数可能对session进行的操作,例如存入值等。

    3)当视图函数处理完毕后,要返回响应时,Flash会将session对象序列化,并且进行加密,然后set到用户Cookie中,对应的key可以在配置文件中配置: 'SESSION_COOKIE_NAME': 'session'。

    4)用户下次请求时,Cookie会被自动携带,Flash检查到存在的session,将对应的value解密,并进行反序列化,放到内存中供再次使用。

    2.闪现(Flash)

    Flash功能是在Session的功能上封装的。

    from flask import flash,get_flashed_messages
    
    # 将数据存放到flash
    flash("临时存放的数据")
    
    # 从flash中取出数据
    print(get_flashed_messages())  # 打印一个列表

    flash原理:
    1)使用flash的时候,先从session中去取key叫做 "_flashes" 的value,如果没有,则取回一个空列表。

    # 源码
    flashes = session.get("_flashes", [])  # 获取_flashes,如果没有则获取到空列表

    2)取flash数据的时候,直接从session中pop出这个_flashes列表。

    # 源码
    session.pop("_flashes") if "_flashes" in session else []  # 如果存在_flashes,则pop。如果不存在,则返回空列表

    所以从上述的原理可以看出,flash只是对session功能的一种特殊封装,实现了数据只能取一次的功能(普通session数据只要不过期就可以一直存在)。

    flash分类:

    我们可以在flash中对存入的数据进行分类:

    flash('信息1','info')  # 第二个参数就是分类的名称
    flash('信息2','info')
    flash('错误信息1','error')
    flash('错误信息2','error')
    print(get_flashed_messages(category_filter='error'))  # 指定取哪个分类的数据,返回的也是列表,相当于按类别进行了过滤

    四、中间件

    1.Flask运行流程

    1)首先定义了一个Flask对象,名叫app

    2)调用app.run()开始运行

    3)在app.run()方法中,可以看到源码:

    run_simple(host, port, self, **options)

    这个run_simple就是我们独立使用werkzurg服务器时调用的方法,参考 [Python自学] Flask框架 (1) 一、Flask简介

    第一个参数host表示服务器监听的主机(IP),第二个参数port表示监听的端口号。第三个参数应该传入一个函数,用于接收到请求时调用。

    我们重点关注第三个参数:

      这里传递的self指的就是app这个对象,应为app调用了run()方法,self就是其自身。要让对象能够直接被调用,则调用的是app中的__call__()方法。

    4)在run_simple函数内部,会利用host、port、app这些参数,在内部函数inner()中去调用make_server(),make_server返回一个发HttpServer实例,然后利用这个实例调用 serve_forever() 来启动服务器。查看一下inner()的源码:

    def inner():
        try:
            fd = int(os.environ["WERKZEUG_SERVER_FD"])
        except (LookupError, ValueError):
            fd = None
        srv = make_server(
            hostname,
            port,
            application,
            threaded,
            processes,
            request_handler,
            passthrough_errors,
            ssl_context,
            fd=fd,
        )
        if fd is None:
            log_startup(srv.socket)
        srv.serve_forever()

    5)我们再看以下make_server()函数的内部:

    def make_server(
        host=None,
        port=None,
        app=None,
        threaded=False,
        processes=1,
        request_handler=None,
        passthrough_errors=False,
        ssl_context=None,
        fd=None,
    ):
        """Create a new server instance that is either threaded, or forks
        or just processes one request after another.
        """
        if threaded and processes > 1:
            raise ValueError("cannot have a multithreaded and multi process server.")
        elif threaded:
            return ThreadedWSGIServer(
                host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
            )
        elif processes > 1:
            return ForkingWSGIServer(
                host,
                port,
                app,
                processes,
                request_handler,
                passthrough_errors,
                ssl_context,
                fd=fd,
            )
        else:
            return BaseWSGIServer(
                host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
            )

    在make_server()我们可以看到,其根据不同的配置条件,创建不同种类的WSGIServer。其中包含多线程、多进程和普通的BaseWSGIServer(即非多线程多进程)。

    6)我们再看一下三种WSGIServer类的源码:

    # 继承于HttpServer
    class BaseWSGIServer(HTTPServer, object):
        pass
    
    # 继承于BaseWSGIServer
    class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):
        pass
    
    # 继承于BaseWSGIServer
    class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer):
        pass

    可以看到,这几种服务器的底层都是HttpServer。而HttpServer继承于 socketserver.TCPServer,关于TCPServer可以参考:[Python自学] day-8 (SocketServer)

    7)再回来看在run_simple被调用后,只有当请求到达时,app.__call__()才会被调用。我们可以查看app.__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)  # 真正处理请求,从这里开始

    2.实现中间件(伪·中间件)

    如果我们想在处理请求之前或之后做一些事情,可以在app.__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."""
        print("处理请求之前的操作")
        ret = self.wsgi_app(environ, start_response)  # 真正处理请求,从这里开始
        print("处理请求之后的操作")
        return ret

    理论上我们可以按以上代码这种方式来实现,但是不建议对源码进行修改。则可以使用以下方式来解决:

    class Middleware(object):
        def __init__(self,old):
            self.old = old
    
        def __call__(self, *args, **kwargs):
            print("处理请求前")
            ret = self.old(*args, **kwargs)
            print("处理请求后")
            return ret
    
    if __name__ == '__main__':
        # 将app的wagi_app利用Middleware类包一下,让app.wsgi_app变成Middleware的一个对象
        # 此时再运行app.wsgi_app()就相当于运行Middleware的__call__()方法,
        app.wsgi_app = Middleware(app.wsgi_app)
        app.run()

    Flask中间件不是很常用,但我们也应该了解这种用法。

    五、特殊装饰器(真·中间件,很重要)

    在之前,我们已经了解了三个特殊的装饰器:

    @app.before_request  # 修饰的方法在视图函数之前执行,用于给视图函数批量添加附加功能,例如用户登录验证
    
    @app.template_global  # 用于定义全局模板函数
    @app.template_filter  # 也是定义全局模板函数,调用方式不同

    before_request装饰器参考:上面的 一、使用before_request装饰器

    template_global和template_filter装饰器用法,参考:[Python自学] Flask框架 (1) 七、jinjia2模板语言

    1.after_request装饰器

    我们知道before_request是在视图函数处理请求之前执行,那么after_request就是在视图函数处理完请求之后执行。

    @app.after_request
    def after(response):
        print("视图函数处理完请求之后")
        return response

    after_request装饰的函数会在视图函数处理完请求,并且返回响应之前被执行。所以相当于视图函数应该返回的响应,通过response参数交给了after()函数,after再做了额外附加操作之后,必须将response再返回,否则客户端无法拿到响应数据。

    2.实现真正的中间件

    对比django的中间件(process_request和process_response),我们可以发现:

      process_request  : @app.before_request装饰的函数

      process_response  : @app.after_request装饰的函数

    这两对的功能非常的相似。所以,我们认为Flask中真正的中间件,实际上可以理解为@app.before_request和@app.after_request装饰的函数。

    画一个图看看:

     从图中可以看到,利用特殊装饰器,Flask实现了和django中间件非常相似的功能。只是实现方式更加灵活(装饰器),而django实现得更加死板(利用继承的方式)。

    3.before和after函数执行顺序

    当我们定义了多个以@app.before_request装饰器装饰的函数时:

    @app.before_request
    def before1():
        print("before1")
    
    @app.before_request
    def before2():
        print("before2")

    遵循准则,谁先被定义,谁先被执行

    当我们定义了多个以@app.after_request装饰器装饰的函数时:

    @app.after_request
    def after1():
        print("after1")
    
    @app.after_request
    def after2():
        print("after2")

    遵循准则,谁后定义,谁先被执行

    所以,总的执行顺序:

    before1
    before2
    
    index
    
    after2
    after1

    特殊场景:

    当在before1中直接返回一个数据,我们知道before2和视图函数index都不会被执行。但是after1和after2是否执行呢?

    正常情况下,我们的请求应该是按绿色箭头的方向走完所有的before和after以及视图函数。

    如果在before1中返回了数据(例如请求位通过验证),那么请求应该按黄色箭头还是蓝色箭头的路线走呢。

    答案是按黄色路线走。也就是说before1返回的数据,会经过after2和after1的处理。这个django不太一样,在django中,处理的方式应该是蓝色路线的方式(django 1.9之前的版本也是按黄色路线执行)。

    4.@app.before_first_request装饰器(用得少)

    这是before_request的一次性版本,即Flask运行起来之后,第一个请求到达会执行该装饰器装饰的函数,之后的请求到达就不执行了。

    实际上就是内部添加了一个标识,执行完第一次就修改为false,以后就不执行了。

    5.@app.error_handler装饰器(有用)

    该装饰器装饰的函数主要用来处理请求的url错误,例如404错误。

    当我们觉得404页面不好看的时候,可以在其中返回好看的页面(类似视图函数一样返回模板渲染等):

    @app.errorhandler(404)
    def err_handler(arg):
        print(arg)  # 返回错误信息 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
        from flask import Markup
        return Markup("<h2>404错误</h2>")

    ###

  • 相关阅读:
    项目开发日志——第三篇
    项目开发日志——第二篇
    项目开发日志——第一篇
    实时控制软件设计第四周作业
    实时控制软件设计第三周作业
    实时控制软件设计第二周作业
    关于开放式CNC系统实时软件控制系统的一些简单分析
    初学者的卡尔曼滤波——扩展卡尔曼滤波(一)
    C++_Eigen函数库用法笔记——The Array class and Coefficient-wise operations
    C++_Eigen函数库用法笔记——Block Operations
  • 原文地址:https://www.cnblogs.com/leokale-zz/p/12369163.html
Copyright © 2011-2022 走看看