1 学习目标
-
能够说出装饰器路由实现的几个关键的类
-
能够说出实现 HTTP 状态保持的原理
-
能够说出 Flask 各个上下文对象
-
能够说出 Flask-Script 扩展的作用
2 装饰器路由具体实现梳理
Flask有两大核心:Werkzeug和Jinja2
- Werkzeug 实现路由、调试和Web服务器网关接口
- Jinja2 实现了模板。
Werkzeug是一个遵循WSGI协议的python函数库
- 其内部实现了很多Web框架底层的东西,比如request和response对象;
- 与WSGI规范的兼容;支持Unicode;
- 支持基本的会话管理和签名Cookie;
- 集成URL请求路由等。
Werkzeug库的 routing 模块负责实现 URL 解析。不同的 URL 对应不同的视图函数,routing模块会对请求信息的URL进行解析,匹配到URL对应的视图函数,执行该函数以此生成一个响应信息。
routing模块内部有:
- Rule类:用来构造不同的URL模式的对象,路由URL规则
- Map类:存储所有的URL规则和一些配置参数
- BaseConverter的子类:负责定义匹配规则
- MapAdapter类:负责协调Rule做具体的匹配的工作
3 获取请求的参数
3.1 request
request 就是flask中代表当前请求的 request 对象,其中一个请求上下文变量(理解成全局变量,在视图函数中直接使用可以取到当前本次请求)
常用的属性如下:
属性 | 说明 | 类型 |
---|---|---|
data | 记录请求的数据,并转换为字符串 | * |
form | 记录请求中的表单数据 | MultiDict |
args | 记录请求中的查询参数 | MultiDict |
cookies | 记录请求中的cookie信息 | Dict |
headers | 记录请求中的报文头 | EnvironHeaders |
method | 记录请求使用的HTTP方法 | GET/POST |
url | 记录请求的URL地址 | string |
files | 记录请求上传的文件 | * |
查询字典args,就是url?问号后边跟的数据
3.2 示例
获取上传的图片并保存到本地
@app.route('/', methods=['POST'])
def index():
pic = request.files.get('pic') # pic 为上传时的 key 值
pic.save('aaa.png')
return 'success'
注意:这里必须限制请求方式是post,因为上传文件必须是表单post提交
接下来上传图片:我们通过postman来上传图片:
点击send之后,就可以看到视图函数返回的success:
查询上传结果:工程目录下多了一个图片
3.3 端口暂用问题
3.4 request.data例子
新增加data函数:
@app.route("/data", methods=["POST"])
def data():
data = request.data
print(data)
return "success"
发起请求:选择raw数据类型(原始数据类型)
控制台打印:
上图中的b,代表的是数据类型 byte
4 状态保持
4.1 cookie
因为 http 是一种无状态协议,浏览器请求服务器是无状态的。
- 无状态:指一次用户请求时,浏览器、服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求。
- 无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象。
有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等
实现状态保持主要有两种方式:
- 在客户端存储信息使用
Cookie
- 在服务器端存储信息使用
Session
无状态协议:
- 协议对于事务处理没有记忆能力
- 对同一个 url 请求没有上下文关系
- 每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况
- 服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器
- 人生若只如初见
状态举例:
有状态:
- A:你今天中午吃的啥?
- B:吃的大盘鸡。
- A:味道怎么样呀?
- B:还不错,挺好吃的。
无状态:
- A:你今天中午吃的啥?
- B:吃的大盘鸡。
- A:味道怎么样呀?
- B:???啊?啥?啥味道怎么样?
所以需要cookie这种东西:
- A:你今天中午吃的啥?
- B:吃的大盘鸡。
- A:你今天中午吃的大盘鸡味道怎么样呀?
- B:还不错,挺好吃的。
4.1.1 Cookie 介绍
Cookie:指某些网站为了辨别用户身份、进行会话跟踪而储存在用户本地的数据(通常经过加密)。
- 复数形式Cookies。
- Cookie最早是网景公司的前雇员Lou Montulli在1993年3月的发明。
- Cookie是由服务器端生成,发送给客户端浏览器,浏览器会将Cookie的key/value保存,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。
- Cookie的key/value可以由服务器端自己定义。
应用:
- 最典型的应用是判定注册用户是否已经登录网站,用户可能会得到提示,是否在下一次进入此网站时保留用户信息以便简化登录手续,这些都是Cookie的功用。
- 网站的广告推送,经常遇到访问某个网站时,会弹出小窗口,展示我们曾经在购物网站上看过的商品信息。
- 购物车,用户可能会在一段时间内在同一家网站的不同页面中选择不同的商品,这些信息都会写入Cookie,以便在最后付款时提取信息。
提示:
- Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用
- Cookie基于域名安全,不同域名的Cookie是不能互相访问的
- 如访问itcast.cn时向浏览器中写了Cookie信息,使用同一浏览器访问baidu.com时,无法访问到itcast.cn写的Cookie信息
- 浏览器的同源策略
-
当浏览器请求某网站时,会将本网站下所有Cookie信息提交给服务器,所以在request中可以读取Cookie信息
4.1.2 cookie流程分析图
cookie:一般用户保存用户的登录状态(user_id , user_name)
4.1.3 设置cookie
@app.route("/login")
def login():
# 相当于 return "success"
response = make_response("success")
return response
上图代码说明:
通过make_response(“响应体”)创建response响应对象,然后返回
与直接return “响应体” 是一样的
但是这里我们需要用到response响应对象去设置cookie,所以需要这样写
访问:
用 response 响应对象设置 cookie 代码:
@app.route("/login")
def login():
response = make_response("success")
response.set_cookie("user_id", "1")
response.set_cookie("user_name", "laowang")
return response
再次访问,在响应头中有set-cookie:
4.1.4 获取cookie
如果此时我们再访问当前网站的任何url,浏览器都会通过 request 携带 cookie,传递给服务器。如下:
获取cookie代码:
@app.route("/")
def index():
user_id = request.cookies.get("user_id")
user_name = request.cookies.get("user_name")
return "index: %s ---> %s" %(user_id, user_name)
访问:
查看cookie:
点击上图中的正在使用2个,进入下图:
上图中的到期时间是关闭浏览器就到期
那我们如何设置过期时间呢?
4.1.5 设置 cookie 过期时间
@app.route("/login")
def login():
response = make_response("success")
response.set_cookie("user_id", "1", max_age=3600) # max_age的单位为秒
response.set_cookie("user_name", "langwang", max_age=3600) # 3600 即1小时
return response
4.1.6 删除 cookie
代码如下:
@app.route("/logout")
def logout():
response = make_response("success")
response.delete_cookie("user_id")
response.delete_cookie("user_name")
return response
访问:
注:删除cookie 的实质其实就是给max_age设置为0
创建时间与过期时间一致即为删除:如下图
4.2 session
session:请求上下文对象,用于处理 http 请求中的一些数据内容
对于敏感、重要的信息,建议要存储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息
在服务器端进行状态保持的方案就是
Session
Session 依赖于 Cookie
4.2.1 session流程分析图
4.2.2 session数据的设置
代码:
@app.route("/login")
def login():
# 假装校验成功
session["user_id"] = "1"
session["user_name"] = "zhangsan"
return "success"
需要导入: from flask import session
访问:
发现报错,设置session的时候需要设置SECRET_KEY,因为要进行加密(内容可以是任意字符串)
记得设置secret_key:
app.secret_key = 'asdfghjkl'
secret_key的作用:https://segmentfault.com/q/1010000007295395
再次访问:
这里就多了一个设置cookie的操作,我们设置的session为啥这里设置有cookie呢?这是因为服务器自动设置的,并且这个cookie叫做sessionid,值还是加密后的值
4.2.3 session数据的获取
代码:
@app.route("/")
def index():
user_id = session["user_id"]
user_name = session["user_name"]
return "%s ---> %s" %(user_id, user_name)
访问:
访问的时候,浏览器通过请求携带着cookie到服务端,服务端就可以根据这个sessionid找到对应的session数据
4.2.4 删除 session
代码:
@app.route("/logout")
def logout():
session.pop("user_id")
session.pop("user_name")
return "success"
pop()函数的作用:
- 删除key对应的session
- 将key对应的session值返回
访问:
4.2.5 代码优化
再次访问首页(或者访问"/logout"),获取session(或者删除session),发现报错:没有这个key
其实这里可以优化一下,即使没有这个key,也不至于报错,代码如下:
from flask import Flask, session
app = Flask(__name__)
app.config["SECRET_KEY"] = "asdfghjkl"
@app.route("/")
def index():
# 如果为空会报错:builtins.KeyError
# user_id = session["user_id"]
# user_name = session["user_name"]
# 如果为空,就用“”代替
user_id = session.get("user_id", "")
user_name = session.get("user_name", "")
return "%s ---> %s" %(user_id, user_name)
@app.route("/login")
def login():
# 假装校验成功
session["user_id"] = "1"
session["user_name"] = "zhangsan"
return "success"
@app.route("/logout")
def logout():
# 如果为空会报错:builtins.KeyError
# session.pop("user_id")
# session.pop("user_name")
# 如果为空,就返回 None
session.pop("user_id", None)
session.pop("user_name", None)
return "success"
if __name__ == '__main__':
app.run(debug=True)
5 上下文
上下文:相当于一个容器,保存了 Flask 程序运行过程中的一些信息。
Flask中有两种上下文:请求上下文和应用上下文
5.1 请求上下文(request context)
思考:在视图函数中,如何取到当前请求的相关数据?比如:请求地址,请求方式,cookie等等
在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session
request
- 封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get('user'),获取的是get请求的参数。
session
- 用来记录请求会话中的信息,针对的是用户信息。举例:session['name'] = user.id,可以记录用户信息。还可以通过session.get('name')获取用户信息。
5.2 请求上下文演示
5.2.1 请求上下文 request
报错如下:
5.2.2 请求上下文session
报错如下:
注意:
这里报错还是说是请求上下文,因为session也是请求上下文的一种(只有请求发起才会产生session)
5.3 应用上下文(application context)
它的字面意思是应用上下文,但它不是一直存在的,它只是request context 中的一个对 app 的代理(人),所谓local proxy(本地代理)。它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。
应用上下文对象有:current_app,g
5.3.1 current_app
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:
- 应用的启动脚本是哪个文件,启动时指定了哪些参数
- 加载了哪些配置文件,导入了哪些配置
- 连了哪个数据库
- 有哪些public的工具类、常量
- 应用跑再哪个机器上,IP多少,内存多大
current_app.name
current_app.test_value='value'
5.3.2 g变量
g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别
g.name='abc'
注意:不同的请求,会有不同的全局变量
5.4 应用上下文current_app
报错信息如下:
5.5 两者区别:
- 请求上下文:保存了客户端和服务器交互的数据
- 应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如程序名、数据库连接、应用信息等
上下文中的对象只能在指定上下文中使用,超出范围不能使用 请求上下文和应用上下文原理实现:https://segmentfault.com/a/1190000004223296
6 Flask-Script 扩展
通过使用Flask-Script扩展,我们可以在Flask服务器启动的时候,通过命令行的方式传入参数。而不仅仅通过app.run()方法中传参,比如我们可以通过:
python xxxxx.py runserver -host ip地址
以上代码告诉服务器在哪个网络接口监听来自客户端的连接。默认情况下,服务器只监听来自服务器所在的计算机发起的连接,即localhost连接。
6.1 安装插件
安装 Flask-Script 扩展
pip install flask-script
6.2 Flask-Script
集成 Flask-Script,代码:
from flask import Flask
from flask_script import Manager
app = Flask(__name__)
# 需求:可以通过命令行在运行的时候指定运行的端口
# 把 Manager 类和应用程序实例进行关联
# 将app交给manager进行管理:
manager = Manager(app)
@app.route("/")
def index():
return "index"
if __name__ == '__main__':
# app.run()
manager.run()
Flask-Script 还可以为当前应用程序添加脚本命令,后续项目中会使用到
运行:
添加shell相当于启动一个python shell
runserver就是启动flask项目
如下:
我们可以通过python hello.py runserver --help来查看参数。
添加端口和调试:
再次运行:
6.3 pycharm 运行
刚刚点右键使用pycharm运行不了,那怎么也能使用pycharm运行呢?
如下:
在script parameters出添加参数即可:
注:还可以添加 -d (相当于 debug = True)
此时点击右键运行:
就可以了: