Flask获取请求相关信息
from flask import Flask, request
app = Flask(__name__)
@app.route('/', methods=["GET", "POST"])
def hello_world():
# - 1. request.data: 获取的是以post提交的, 非表单数据
print(request.data)
# - 2. request.form: 获取的是以post提交的, 表单数据
print(request.form)
# - 3. request.args: 获取的是查询参数, 一般是GET请求, 获取的是问号后面拼接的参数
print(request.args)
# - 4. request.method: 获取的是请求方式
print(request.method)
# - 5. request.url: 获取的是浏览器的请求地址
print(request.url)
return "helloworld"
if __name__ == '__main__':
app.run(debug=True)
其中,request.args
获取到的是链接问号后的参数,此参数一般都是键值对形式存在的。要想获取键值对的值,一般有两种方式:
- request.args[“key”]
- request.args.get(“key”)
一般,我们采用第二种方式,因为第一种方式若key不存在,则会报错,第二种方式若key不存在,则返回None
请求钩子(request-hook)
在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:
- 在请求开始时,建立数据库连接;
- 在请求开始时,根据需求进行权限校验;
- 在请求结束时,指定数据的交互格式;
为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设施的功能,即请求钩子。
请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:
- before_first_request:在处理第一个请求前执行
- before_request:在每次请求前执行,在该装饰函数中,一旦return,视图函数不再执行
- after_request:如果没有抛出错误,在每次请求后执行
- 接受一个参数:视图函数作出的响应
- 在此函数中可以对响应值,在返回之前做最后一步处理,再返回
- teardown_request:在每次请求后执行
- 接受一个参数:用来接收错误信息
from flask import Flask
app = Flask(__name__)
# 只有第一次请求会调用此方法,因此可以在此方法内部做一些初始化操作
@app.before_first_request
def bfr():
print('This is the first request')
# 每次请求都会调用此方法,可以在此方法里面做类似验证的语句
# 若请求不成功,可直接再次方法中进行响应,可以直接return
@app.before_request
def br():
print('This is the normal request')
# 在每次执行完视图函数后会调用,并会把视图函数所生成的响应传入-->[<Response 6 bytes [200 OK]>]
# 可在此方法中对响应做最后一步统一的处理
@app.after_request
def ar(a):
print(a,'****************')
print('This is the end of request')
return a
# 每次请求之后都会调用,会接收到一个参数,参数是服务器可能出现的错误信息,若无错误信息,返回None
@app.teardown_request
def tr(e):
print(e,'****************')
print('This is why I cry')
@app.route('/')
def func():
return '123456'
if __name__ == '__main__':
app.run()
保持状态
状态相关概念
- http是一种无状态协议,浏览器请求服务器是无状态的
- 无状态:指一次用户请求时,浏览器、服务器不知道之前这个用户做过什么,每次请求都是一次新的请求
- 无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象
- 有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等
- 实现状态保持主要有两种方式:
- 在客户端存储信息使用Cookie
- 在服务器端存储信息使用Session
cookie
什么是cookie
- 指某些网站为了辨别用户身份、进行会话跟踪而储存在用户本地的数据(通常经过加密),复数形式Cookies
- Cookie是由服务器端生成,发送给客户端浏览器,浏览器会将Cookie的key/value保存,下次请求同一网站时就发送该Cookie给服务器
- Cookie中的key/value可以由服务器端自己定义
设置,获取cookie
from flask import Flask, make_response, request
app = Flask(__name__)
# 设置cookie值
@app.route('/set_cookie')
def set_cookie():
response = make_response("wahaha")
response.set_cookie("name", "zhangsan")
response.set_cookie("age", "13", 60) # 60秒有效期
return response
# 获取cookie
@app.route('/get_cookie')
def get_cookie():
# 获取cookie,可以根据cookie的内容来推荐商品信息
name1 = request.cookies
print(name1)
name = request.cookies.get('name')
age = request.cookies.get('age')
return "获取cookie,name is %s, age is %s" % (name, age)
if __name__ == '__main__':
app.run(debug=True)
session
什么是session
cookie是保存在客户端浏览器中的信息,因此会存在一定安全隐患。为此对于敏感、重要的信息,建议要存储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息,所以可以使用session进行保存。
设置,获取session
from flask import Flask,session
app = Flask(__name__)
#设置任意一个字符串当作secret_key
app.config["SECRET_KEY"] = "fhdk^fk#djefkj&*&*&"
#设置session
@app.route('/set_session/<path:name>')
def set_session(name):
session["name"] = name
session["age"] = "13"
return "set session"
#获取session内容
@app.route('/get_session')
def get_session():
name = session.get('name')
age = session.get('age')
return "name is %s, age is %s"%(name,age)
if __name__ == '__main__':
app.run(debug=True)
上下文
上下文:相当于一个容器,保存了 Flask 程序运行过程中的一些信息。
Flask中有两种上下文,请求上下文和应用上下文
请求上下文
在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session
request
封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get(‘user’),获取的是get请求的参数。
session
用来记录请求会话中的信息,针对的是用户信息。举例:session[‘name’] = user.id,可以记录用户信息。还可以通过session.get(‘name’)获取用户信息。
应用上下文
它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中的一个对 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。
应用上下文对象有:current_app,g
current_app
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:
- 应用的启动脚本是哪个文件,启动时指定了哪些参数
- 加载了哪些配置文件,导入了哪些配置
- 连了哪个数据库
- 有哪些public的工具类、常量
- 应用跑再哪个机器上,IP多少,内存多大
g
g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别
二者区别:
请求上下文:保存了客户端和服务器交互的数据
应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如程序名、数据库连接、应用信息等
Flask-Script扩展
Flask-Script属于flask的扩展包,通过使用Flask-Script扩展,我们可以在Flask服务器启动的时候,通过命令行的方式传入参数,而不仅仅通过app.run()方法中传参。
需要先安装flask-script扩展
pip install flask-script
python hello.py runserver -host ip地址
用代码实现代理
from flask import Flask
#1.从flask_script中导入Manager类
from flask_script import Manager
app = Flask(__name__)
# 2.使用Manager管理app对象
manager = Manager(app)
@app.route('/')
def hello_world():
return "helloworld"
if __name__ == '__main__':
manager.run()
该方法只能在终端启动。若想要在代码页面直接右键运行,需在Edit Configuration处添加参数runserver。
Jinja2
Jinja2模板概述
Jinja2是用来展示数据的html页面,这个过程也通常称为渲染,属于Jinja2的功能使用模板的好处:
- 视图函数只负责业务逻辑和数据处理(业务逻辑方面)
- 而模板则取到视图函数的数据结果进行展示(视图展示方面)
- 代码结构清晰,耦合度低
Jinja2特点
- Jinja2:是 Python 下一个被广泛应用的模板引擎,是由Python实现的模板语言,他的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能,其是Flask内置的模板语言。
- 模板语言:是一种被设计来自动生成文档的简单文本格式,在模板语言中,一般都会把一些变量传给模板,替换模板的特定位置上预先定义好的占位变量名。
- 使用render_template函数封装模板引擎
render_template函数模板语法
Jinja2模板语法
获取变量
<h1>整数:{ {number} }</h1>
<h1>元祖:{ {tuple[0]} }</h1>
<h1>列表:{ { list[0] } }</h1>
<h1>字典:{ { dict['key'] } }</h1>
分支语句if
{ % if 条件 % }
语句1
{ % else % }
语句2
{ % endif % }
for循环
{% for 变量 in 容器 %}
语句
{% endfor%}
代码展示
-使用函数:render_template(‘模板文件名’,key=value)
-将数据携带到,文件中进行展示
-创建文件demo01.py,代码如下:
from flask import Flask,render_template
app = Flask(__name__) #默认省略了三个参数,static_url_path, static_folder, template_folders
@app.route('/')
def hello_world():
#定义数据,整数,字符串,元祖,列表,字典
num = 10
str = "hello"
tuple = (1,2,3,4)
list = [5,6,7,8]
dict = {
"name":"张三",
"age":13
}
return render_template('file01.html',my_num=num,my_str=str,my_tuple=tuple,my_list=list,my_dict=dict)
if __name__ == '__main__':
app.run(debug=True)
在templates文件夹下,创建文件file01.html文件,代码如下:
<h2>1.获取各种变量的值</h2>
<h3>整数: {{ my_num + 20}}</h3>
<h3>字符串: {{ my_str + " python" }}</h3>
<h3>元组: {{ my_tuple }}, 分开获取:{{ my_tuple[0] }}, {{ my_tuple[1] }}</h3>
<h3>列表: {{ my_list }}, 分开获取:{{ my_list[0] }}, {{ my_list[1] }}</h3>
<h3>字典: {{ my_dict }},分开获取:{{ my_dict.name }}, {{ my_dict[age] }}</h3>
<h2>2.遍历元祖中所有的元素</h2>
{% for item in my_tuple %}
<li>{{ item }}</li>
{% endfor %}
<h2>3.取出列表中所有偶数</h2>
{% for item in my_list %}
{% if item %2 == 0 %}
{{ item }}
{% endif %}
{% endfor %}
<h2>4.遍历字典内容</h2>
{% for key in my_dict %}
{# 如果直接是mydict.key ,那么这个key是一个字符串, 如果是 mydict[key], 那么key当成变量 #}
<li>{{ key }} = {{ my_dict[key] }}</li>
{% endfor %}
扩展
变量 | 描述 |
---|---|
loop.index | 当前循环迭代的次数(从 1 开始) |
loop.index0 | 当前循环迭代的次数(从 0 开始) |
loop.revindex | 到循环结束需要迭代的次数(从 1 开始) |
loop.revindex0 | 到循环结束需要迭代的次数(从 0 开始) |
loop.first | 如果是第一次迭代,为 True 。 |
loop.last | 如果是最后一次迭代,为 True 。 |
loop.length | 序列中的项目数。 |
loop.cycle | 在一串序列间期取值的辅助函数。见下面示例程序。 |
Jinja2过滤器
过滤器概述
过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。
Jinja2自带过滤器
字符串
使用格式:{{字符串 | 字符串过滤器 }}
- safe:禁用转义
<p>{{ '<em>hello</em>' | safe }}</p>
- capitalize:把变量值的首字母转成大写,其余字母转小写
<p>{{ 'hello' | capitalize }}</p>
- lower:把值转成小写
<p>{{ 'HELLO' | lower }}</p>
- upper:把值转成大写
<p>{{ 'hello' | upper }}</p>
- title:把值中的每个单词的首字母都转成大写
<p>{{ 'hello' | title }}</p>
- reverse:字符串反转
<p>{{ 'olleh' | reverse }}</p>
- format:格式化输出
<p>{{ '%s is %d' | format('name',17) }}</p>
- striptags:渲染之前把值中所有的HTML标签都删掉
<p>{{ '<em>hello</em>' | striptags }}</p>
列表
使用格式:{{ 列表 | 列表过滤器 }}
- first:取第一个元素
<p>{{ [1,2,3,4,5,6] | first }}</p>
- last:取最后一个元素
<p>{{ [1,2,3,4,5,6] | last }}</p>
- length:获取列表长度
<p>{{ [1,2,3,4,5,6] | length }}</p>
- sum:列表求和
<p>{{ [1,2,3,4,5,6] | sum }}</p>
- sort:列表排序
<p>{{ [6,2,3,1,5,4] | sort }}</p>
语句块操作
{% filter upper %}
#假装这里有一大堆文字#
{% endfilter %}
链式调用
{{ "hello world" | reverse | upper }}
自定义过滤器
方法一
先定义函数,后添加到过滤器列表
app.add_template_filter(‘函数名’,‘过滤器名称’)
def do_listreverse(li):
# 通过原列表创建一个新列表
temp_li = list(li)
# 将新列表进行返转
temp_li.reverse()
return temp_li
app.add_template_filter(do_listreverse,'lireverse')
方法二
定义函数,直接使用@app.template_filter(‘过滤器名称’)装饰
@app.template_filter('lireverse')
def do_listreverse(li):
# 通过原列表创建一个新列表
temp_li = list(li)
# 将新列表进行返转
temp_li.reverse()
return temp_li
在html中使用过滤器
在 html 中使用该自定义过滤器
<h2>my_array 原内容:{{ my_array }}</h2>
<h2> my_array 反转:{{ my_array | lireverse }}</h2>
模板复用
当有部分代码会重复利用的时候,我们就可以利用代码复用的方式。代码复用一般有三种方式:宏、继承和包含。
宏
解释
宏和python中的函数类似,在需要的时候调用即可
定义格式
{%macro 宏名(参数)%}内容{%endmacro%}
{% macro 宏名(参数) %}
{# 内容 #}
{% endmacro %}
使用
在当前文件中
直接{{ 宏名(参数) }}
在其他文件中
先导入
{{ import ‘拥有宏代码的文件名’ as ‘别名’}}
再引用
{{ 宏名(参数) }}
继承
目的
共性抽取,代码复用
格式
{% extends"父模板文件名" %}
父模板
- 多个子类完全相同的部分可以直接写死
- 各个子类直接不同的地方使用block模块定义出来
{%block 块名%}可修改内容{%endblock%}
{% block 块名 %} {# 可修改内容 #} {% endblock %}
子模版
- 子类继承父类
子类继承父类时,继承的代码最好写在所有代码的最上边 - 子类根据自己的需求,重写父类block模块中的代码
- 完全重写
{%block 块名%}新内容{%endblock%}
{% block 块名%} {# 新内容 #} {% endblock%}
- 继承并增加
{%block 块名%}{{super()}}新内容{%endblock%}
{% block 块名%} {{super()}} {# 新内容 #} {% endblock%}
- 完全重写
包含
包含指的是在一个文件中完全拷贝另一个文件的所有代码.这种方式无法对代码进行扩展,因此不够灵活.
格式
{%include "文件名" ignore missing%}
其中,ignore missing是可选参数,选此参数,当包含的文件找不到时,系统不会报错.因此,推荐选择该参数.
模板中的特有变量
特有变量指的是不需要从python中传递到模板,就可以直接使用的变量
常见特有变量
request
就是python中的请求上下文对象
g
是一个全局的应用上下文对象
url_for(函数名)
是一个反解析方法,根据函数名找到对应的路径
config
config指的就是app.config的配置对象
get_flashed_messages()
是消耗消息,消耗的是在python中使用flash(“消息”)方法储存的消息.
flash 储存消息时需要依赖 session ,所以用 flash 应该先设置SECRET_KEY
CSRF攻击
CSRF全拼为Cross Site Request Forgery,译为跨站请求伪造。指攻击者盗用了你的身份,以你的名义发送恶意请求。
CSRF攻击示意图:
如何防止CSRF攻击
防止CSRF攻击的思想:
- 在客户端向后端请求界面数据的时候,后端会往响应中的 cookie 中设置 csrf_token 的值
- 在 Form 表单中添加一个隐藏的的字段,值也是 csrf_token
- 在用户点击提交的时候,会带上这两个值向后台发起请求
- 后端接受到请求,以会以下几件事件:
- 从 cookie中取出 csrf_token
- 从 表单数据中取出来隐藏的 csrf_token 的值
- 进行对比
- 如果比较之后两值一样,那么代表是正常的请求,如果没取到或者比较不一样,代表不是正常的请求,不执行下一步操作
- 提示:代码展示:见<< webA >>, << webB >>文件
代码构思
- flask_wtf模块提供了csrf攻击的保护
- 使用流程:
- from flask_wtf.csrf import CSRFProtect
- CSRFProtect(app)
- CSRFProtect(app)保护原理:
- 对应用程序app中的post,put,dispatch,delete, 4种类型的请求做保护,因为这些类型的请求是用于更改服务器的资源
- 当以上面4种类型的请求,操作服务器资源的时候,会校验cookie中的csrf_token, 表单中的csrf_token信息
- 只有上面二者的值相等的时候,那么校验则通过,可以操作服务器资源
提示 : csrf_token值的生成需要加密,所以要设置SECRET_KEY
代码如下
- 后端代码
from flask import Flask,render_template
from flask_wtf import CSRFProtect
app = Flask(__name__)
#设置SECRET_KEY
app.config["SECRET_KEY"] = "fjkdjfkdfjdk"
#保护应用程序
CSRFProtect(app)
@app.route('/')
def show_page():
return render_template('file01csrf.html')
@app.route('/add_data',methods=["POST"])
def add_data():
return "登陆成功"
if __name__ == '__main__':
app.run(debug=True)
- 前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/add_data" method="post">
{#设置隐藏的csrf_token,使用了CSRFProtect保护app之后,即可使用csrf_token()方法#}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label>用户名:</label> <input type="text" name="username"><br>
<label>密码:</label> <input type="text" name="username"><br>
<input type="submit" value="登陆">
</form>
</body>
</html>