[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>")

###

posted @ 2020-02-26 21:29  风间悠香  阅读(812)  评论(0编辑  收藏  举报