I、请求钩子
什么是请求钩子?在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要统一处理,为了让每个视图函数避免编写重复功能的代码,flask提供了统一的接口可以添加这些处理函数,即请求钩子。
比如:请求开始时的db_connect、auth认证,或者结束时的置顶数据的交互格式。
先回顾一下flask对请求的处理流程:
接收请求--》创建请求上下文--》请求上下文入栈--》创建该请求的应用上下文--》应用上下文入栈--》处理逻辑--》请求上下文出栈--》应用上下文出栈
看了这个过程,flask放置请求钩子的位置有:处理逻辑之前,处理逻辑之后,应用上下文出栈之前。
那么为什么要设计请求钩子呢?
为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设置等功能,即请求钩子。请求钩子是通过装饰器的形式实现,Flask支持五种请求钩子:
1.before_first_request
在第一个请求前被调用(在处理第一个请求之前执行),可在此方法内做一些初始化操作
2.before_request
在每次请求前执行,如果在某修饰的函数中返回了一个响应(response),视图函数将不再被调用。
3.after_request
在每次请求后(如果没有抛出错误)执行,并且把视图函数所生成的响应传入。可接收一个参数:视图函数做出的相应,在此函数中可以对响应值在返回之前做最后一步修改处理,需要将参数中的相应在此参数中进行返回。在此方法中可对响应做最后一步统一处理。
4.teardown_request
在每次请求后执行;接收一个参数(错误信息),如果有相关错误抛出,需要设置flask的配置DEBUG=False
,teardown_request
才会接收到异常对象
5.teardown_appcontext
在应用上下文从栈中弹出之前运行
首先创建一个新的app,demo_gouzi.py并填充
from flask import Flask
app = Flask(__name__)
@app.before_first_request
def before_first_request():
print('before_first_request')
@app.before_request
def before_request():
print('before_request')
@app.after_request
def after_request(response):
print('after_request')
response.headers['Content-Type'] = "application/json"
return response
@app.teardown_request
def teardown_request(response):
print('teardown_request')
@app.route('/')
def index():
return 'index'
if __name__ == '__main__':
app.run(debug=False)
我们运行下服务,观察执行结果
II、request对象属性
语法 | 描述 |
---|---|
request.scheme | 代表请求的方案,http或者https |
request.path | 请求的路径 |
request.method | 表示请求使用的http方法,GET或者POST请求 |
request.encoding | 表示提交数据的编码方式 |
request.GET | 获取GET请求 |
request.POST | 获取post的请求,比如前端提交的用户密码,可以通过request.POST.get()来获取 另外:如果使用 POST 上传文件的话,文件信息将包含在 FILES 属性中 |
request.cookies | 包含所有的cookie |
request.session | 一个既可读又可写的类似于字典的对象,表示当前的会话 |
III、状态保持
因为http是一种无状态协议,不保持某一次请求所产生的信息,如想实现状态保持,在开发中解决方式有:
cookie:数据存储在客户端,节省服务器空间,但是不安全
session:会话,数据存储在服务器端
对于无状态协议的理解,看做以下五条
1、协议对于事务处理没有记忆能力
2、对同一个url请求没有上下文关系
3、每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况
4、服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器
5、人生若只如初见:比如商品加入购物车,重登后购物车里的东西无了
在Django中我们接触过cookie和session,下面我们看看在Flask中如何使它们
1、cookie
cookie在flask中的使用,我们以如下示例展示:
①设置cookie
# make_response相当于Django中的http_response
from flask import make_response
@app.route('/cookie')
def set_cookie():
res = make_response('this is to set cookie')
res.set_cookie('username', 'laoma')
return res
启动服务,进入http://127.0.0.1:5000/cookie观察结果
注:cookie是有时间限制的,超过时间会过期,我们可以自己设计cookie的时间(利用max_age
关键字)
# 设计cookie的时间,时间戳单位为:秒
res.set_cookie('username', 'laoma', max_age=3600)
②获取cookie
from flask import request
@app.route('/request')
def get_cookie():
res = request.cookies.get('username')
return res
启动服务观察结果
2、session
session会话主要用于存储敏感涉密信息,例如:个人信息、账户余额、验证码等问题,session依赖于cookie,在TensorFlow中,也会使用session尽管和web中的session有所不同,但也可理解为会话方式。
下面我们将使用session
from flask import Flask, redirect, url_for
from flask import session
# 需要设置secret_key
app.secret_key = '9999999'
@app.route('/index1')
def index1():
session['chase'] = '9999'
return redirect(url_for('index'))
# ‘0’为设置的默认值,默认值必须设置否则会报500错误
@app.route('/')
def index():
return session.get('chase', '0')
Session, Cookies以及一些第三方扩展都会用到SECRET_KEY
值,这是一个比较重要的配置值,应该尽可能设置为一个很难猜到的值,随机值更佳。随机的问题在于很难判断什么是真随机。一个密钥应该足够随机。你的操作系统可以基于一个密码随机生成器来生成漂亮的随机值,这个值可以用来做密钥;
SECRET_KEY
配置变量是通用密钥, 可在 Flask 和多个第三方扩展中使用. 如其名所示, 加密的强度取决于变量值的机密度. 不同的程序要使用不同的密钥, 而且要保证其他人不知道你所用的字符串.其主要作用应该是在各种加密过程中加盐以增加安全性。在实际应用中最好将这个参数存储为系统环境变量。当然我们也可以利用加密哈希,就是让cookie变得“安全”的字段。服务器向我们发送最新的会话数据之前,会结合我们的会话数据、当前时间戳以及服务器的私钥来计算哈希从而加密session,使得会话变得安全。
快速入门中的 “ 会话”部分对应设置哪种服务器端机密提供了很好的建议。加密取决于机密;如果你没有设置要使用的加密服务器端密码,那么每个人都可以破坏你的加密;就像你计算机的密码一样。秘密加上要签名的数据用于创建签名字符串,使用密码哈希算法很难重新创建值;仅当你具有完全相同的机密且原始数据时,你才能重新创建此值,让Flask检测是否未经许可对任何内容进行了更改。由于Flask永远不会将秘密包含在发送给客户端的数据中,因此客户端无法篡改会话数据,并希望产生新的有效签名。
Flask尽量使用该itsdangerous库来完成所有艰苦的工作;会话使用带有自定义JSON序列化程序的itsdangerous.URLSafeTimedSerializer类。
3、上下文
①请求上下文request context
request和session都属于请求上下文对象。request:封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get('user')
,获取的是get请求的参数。
这不再做过多介绍
②应用上下文application context
在发送请求时,我们可以通过上下文发送全局对象,例如app.name
current_app和g都属于应用上下文对象。
1、current_app:表示当前运行程序文件的程序实例。
2、g:(global) 处理请求时,用于临时存储的对象,每次请求都会重设这个变量。比如:我们可以获取一些临时请求的用户信息。当调用
app = Flask(_name_)
的时候,创建了程序应用对象app;
request 在每次http请求发生时,WSGI server调用Flask.call()
;然后在Flask内部创建的request对象;
app的生命周期大于request和g,一个app存活期间,可能发生多次http请求,所以就会有多个request和g。
最终传入视图函数,通过return、redirect或render_template生成response对象,返回给客户端。
区别: 请求上下文:保存了客户端和服务器交互的数据。 应用上下文:在flask程序运行过程中,保存的一些配置信息,比如程序文件名、数据库的连接、用户信息等。
一个操作实例:
from flask import current_app, g
@app.route('/')
def index():
print(current_app.name)
print(g.ip)
return session.get('chase', '0')
IV、上下文隔离原理
Flask主要是借助werkzueg来实现的,其中请求之间隔离的方式借助了库中Local、LocalStack、LocalProxy 三个类,下面将逐一介绍
1、Local:
首先local添加了一个storage字典用来存储添加的属性,当给local对象进行添加属性会自动根据get_ident方法获取当前的线程、进程,键为线程、进程ID号,值为添加的属性,同理当取值时,会自动判断当前的环境来取值, 类似于threading中的local
2、LocalStack:
LocalStack基于local实现了一个‘先进晚出’的栈结构,内部通过为每个线程、进程添加
stack属性来存储数据,存储数据的容器为list,值得注意是每次添加值、取值得时候内部会自动判断当前的线程、进程环境为每个线程、进程床架一个单独的容器,这也是不同请求之间实现隔离的主要原因,同样的current_app 当前的应用上下文也用到了类似的方式来管理,也就是说每个请求都有自己的应用上下文,请求的每次入栈都会判断当前的应用上下文,如果没有创建应用上下文,则创建对应的上下文压入栈也就是_app_ctx_stack,request对应的则是_request_ctx_stack
3、LocalProxy:
作为一种代理模式的实现,我们通过查看源码发现,LocalProxy实现了重写了大量的类的‘魔术’方法,其实localproxy主要就是对对象、方法进行了包装,通过localproxy进行过渡可以直接操作目标对象LocalProxy对_lookup_req_object方法进行了包装,当我们访问request属性的时候,由于LocalProxy重写了setattr getattr等特殊方法,在执行的时候会调用_get_cuurent_object方法且直接执行了self.__local()方法其实也就是包装之前的_lookup_req_object,通过这个方法可以获取全局请求上下文栈顶的请求上下文中的request属性,注意_request_ctx_stack中存储的是请求上下文(Request_Context),我们每次操作的request其实是Request_Context的一个属性
此处编辑匆忙,存有大量借鉴,而在此之后作者还会更新该内容
上述内容分别:
源码解析转载于:https://www.cnblogs.com/alplf123/p/10517057.html
流程图转载于:https://www.cnblogs.com/baijinshuo/p/10264326.html
V、初探模板 Jinja2
我们先建立一个flask_templates.py文件,建立应用来体验一下Flask如何调用模板
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run()
index.html的内容为
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
INDEX
</body>
</html>
我们启动一下服务,看看模板是否被调用
我们来介绍一下Jinja2
Jinja2是Python下一个被广泛应用的模版引擎,他的设计思想来源于Django的模板引擎,并扩展了其语法和一系列强大的功能。其中最显著的一个是增加了沙箱执行功能和可选的自动转义功能,这对大多应用的安全性来说是非常重要的。它基于unicode并能在python2.4之后的版本运行,包括python3(来源于百度百科)
其中Jinja2的模板语法与Django的模板内容基本相似
在Jinja2中,存在三种语法:
1、控制结构{% %}
2、变量取值{{ }}
Jinja2模板中使用 {{ }} 语法表示一个变量,它是一种特殊的占位符。当利用Jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,Jinja2支持python中所有的Python数据类型比如列表、字段、对象等。
3、注释{# #}
@app.route('/')
def index():
res = {'my_str': 'my', 'my_int': 1, 'my_list': [1, 2, 3], "my_dict": {"on": "day"}}
return render_template('index.html', my_str='my', my_int=1, my_list=[1, 2, 3], my_dict={"on": "day"})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Jinja2模板</h1>
{{ my_str }}<br>
{{ my_int }}<br>
{{ my_list }}<br>
{{ my_dict }}<br>
<hr>
my_int + 10 = {{ my_int + 10 }}<br>
my_int _ my_list[0] = {{ my_int + my_list[0] }}<br>
list[2] = {{ my_list.2 }}<br>
my_dict['on'] = {{ my_dict['on'] }}<br>
my_dict['on'] = {{ my_dict.on }}
</body>
</html>
启动服务观察结果,其语法与python内的语法十分相似。