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