Flask 框架学习目录
1. 概述
基本上,Web应用开发的主要逻辑就是:从HTTP请求中提取数据, 根据这些请求数据,进行相应的处理生成响应数据再发送回客户端。
Flask框架对这一来一去的两个关键数据进行了封装。HTTP请求 被封装为Request对象,而HTTP应答被封装为Response对象。因此, Flask应用开发的逻辑处理,基本就基于这两个对象:输入的Request 和输出的Response。
本节课程主要从以下几个方面讲解Request对象和Response对象:
-
如何访问Request对象?
-
如何读取请求中的表单数据?
-
如何读取请求中的查询参数?
-
如何读取请求中的JSON数据?
-
如何生成Response对象?
-
如何设置响应cookie?
-
如何构造JSON格式的响应?
-
如何重定向响应?
-
如何异常终止响应?
此外,由于大多数Web应用避免不了使用会话来维持访问者的上下文 信息,因此本节课的最后,将讲解Flask框架中的会话对象Session。 这部分的内容主要侧重于以下问题:
-
会话的来由与原理
-
如何访问Session对象?
-
如何使用Session构造一个简单的有状态应用?
2. 请求 :Request & request
WSGI服务器转发的http请求数据,Flask框架会将其封装为一个Request类的实例。Request实例对象中包含 了关于一次HTTP请求的一切信息,常用的属性包括:
-
form - 记录请求中的表单数据。类型:MultiDict
-
args - 记录请求中的查询参数。类型:MultiDict
-
cookies - 记录请求中的cookie。类型:Dict
-
headers - 记录请求中的报文头。类型:EnvironHeaders
-
method - 记录请求使用的HTTP方法:GET/POST/PUT....。类型:string
-
environ - 记录WSGI服务器转发的环境变量。类型:Dict
-
url - 记录请求的URL地址。类型:string
关于Request类的详细信息,可以查阅 Flask官网
2.1 request对象
在视图函数中,可以直接使用全局对象request访问当次请求对应的Response对象。 下面的示例打印当前请求的全部报文头信息:
@app.route('/')
def index():
print request.headers
return 'see console output'
3. 读取表单数据
Flask框架将用户使用POST方法提交的表单数据,存储在所创建Request对象的 form属性中。
![10012455_FssT.png](https://static.oschina.net/uploads/img/201709/10012455_FssT.png)
form 是一个 MultiDict 类型的对象,和Dict类似,我们可以使用 [] 操作符读取 指定的键值:
@app.route('/')
def v_index():
uid = request.form['uid']
pwd = request.form['pwd']
return 'uid : %s pwd : %s' % (uid,pwd)
实验代码如下:
# -*- coding:utf-8 -*-
from flask import Flask,request,url_for
app = Flask(__name__)
@app.route('/')
def v_index():
return '''
<form action="/login" method="POST">
<input type="text" name="system" placeholder="input your system id"> <br />
<input type="text" name="uid" placeholder="input your user id"> <br />
<input type="password" name="pwd" placeholder="input your password"> <br />
<input type="submit" value="Login">
</form>
'''
@app.route('/login',methods=['POST'])
def v_login():
uid = request.form['uid']
pwd = request.form['pwd']
system = request.form['system']
if uid=='admin' and pwd=='admin' and system=='CRM':
return 'Authorized successfully!'
else:
return 'Un-Autorized!'
app.run(host='0.0.0.0',port=8080)
实验页面如下:
![10012456_y8iA.png](https://static.oschina.net/uploads/img/201709/10012456_y8iA.png)
![10012459_0BWe.png](https://static.oschina.net/uploads/img/201709/10012459_0BWe.png)
![10012500_EgcT.png](https://static.oschina.net/uploads/img/201709/10012500_EgcT.png)
4. 读取查询参数
对于浏览器以GET方法提交的表单数据,Flask框架将其存储在Request实例对象的args 属性中。
![10012500_PiNq.png](https://static.oschina.net/uploads/img/201709/10012500_PiNq.png)
和form属性一样,args属性也是一个MultiDict类型的对象,因此我们可以是用[]操作 符读取指定键值:
@app.route('/search')
def v_search():
q = request.args['q']
return 'you are searching %s' % q
除了args属性,也可以使用Request对象的values属性来读取查询参数。
实验代码如下:
# -*- coding:utf-8 -*-
from flask import Flask,request,url_for
app = Flask(__name__)
@app.route('/')
def v_index():
return '''
<form method="GET" action="/search">
<input type="text" placeholder="input keywords" value="Python Flask" name="q"> <br />
<input type="text" name="page"> <br />
<input type="submit" value="search">
</form>
'''
@app.route('/search')
def v_search():
if 'q' in request.args:
ret = '<p>searching %s...</p><p> %s </p>' % (request.args['q'], request.args['page'])
else:
ret = 'what do you want to search?'
return ret
app.run(host='0.0.0.0',port=8080)
实验页面如下:
![10012501_To2r.png](https://static.oschina.net/uploads/img/201709/10012501_To2r.png)
![10012503_BEHD.png](https://static.oschina.net/uploads/img/201709/10012503_BEHD.png)
5. 响应 :Response
与Request类相对应,Flask框架使用Response类表征对HTTP请求的响应。
根据视图函数的返回结果,Flask确保向后续处理环节传递一个正确的Response实例对象。 这有几种可能性:
5.1 视图函数返回字符串
当视图函数返回的是一个字符串时,Flask自动使用 这个字符串作为正文内容,以200作为状态码,以text/html作为mimetype,构造 一个Response对象传递给后续处理环节。
下面的示例中,视图函数v_ping()返回一个字符串:
@app.route('/ping')
def ping():
return 'pong'
Flask框架将基于这个返回结果构造如下的Response对象:
response : ['pong']
status_code : 200
mimetype : 'text/html'
5.2 视图函数返回元组
当视图函数返回的是一个形式如(response,status,headers)的元组时, Flask自动根据这几个值构造一个Response对象。
下面的示例中,视图函数v_ping()返回一个包含响应正文、状态码和包头的元组:
@app.route('/ping')
def v_ping():
return 'pong',200,{'x-tag':'sth. magic'}
Flask框架将基于这个返回结构构造如下的Response对象:
response: ['pong']
status_code : 200
mimetype : 'text/html'
headers : [('x-tag','sth. magic')]
5.3 视图函数返回Response对象
当视图函数返回的是一个Response对象时,Flask 框架直接将这个对象向后续处理环节传递:
from flask import Flask,make_response
@app.route('/ping')
def v_ping():
rsp = make_response('pong')
rsp.mimetype = 'text/plain'
rsp.headers['x-tag'] = 'sth. magic'
return rsp
make_response() 函数用来构造一个Response对象,第一个参数为响应的正文。
实验代码如下:
# -*- coding:utf-8 -*-
from flask import Flask,make_response, url_for
app = Flask(__name__)
@app.route('/')
def v_index():
return '<a href="%s">ping</a>' % url_for('v_ping')
@app.route('/ping')
def v_ping():
rsp = make_response('pong')
rsp.mimetype = 'text/plain'
rsp.headers['x-tag'] = 'sth.magic'
return rsp
app.run(host='0.0.0.0',port=8080)
实验页面如下:
![10012504_nn3M.png](https://static.oschina.net/uploads/img/201709/10012504_nn3M.png)
![10012505_BePR.png](https://static.oschina.net/uploads/img/201709/10012505_BePR.png)
6. 设置 cookie
使用Response类的set_cookie()方法可以设置客户端cookie:
Response.set_cookie(
key, //键
value='', //值
max_age=None, //秒为单位的cookie寿命,None表示http-only
expires=None, //失效时间,datetime对象或unix时间戳
path='/', //cookie的有效路径
domain=None, //cookie的有效域
secure=None,
httponly=False)
显然,如果要设置cookie,我们必须自行构造Response对象,而不是交给Flask框架去 完成这件事情。
下面的示例在访问首页 / 时设置cookie,并在访问 /page2 时读取cookie:
@app.route('/')
def index():
rsp = make_response('go <a href="%s">page2</a>' % '/page2')
rsp.set_cookie('user','JJJJJohnny')
return rsp
@app.route('/page2')
def page2():
user = request.cookies['user']
return 'you are %s' % user
实验代码如下:
# -*- coding:utf-8 -*-
from flask import Flask,request,make_response,url_for
app = Flask(__name__)
@app.route('/')
def v_index():
rsp = make_response('go <a href="%s">page2</a>' % url_for('v_page2'))
rsp.set_cookie('user','JJJJJohnny')
return rsp
@app.route('/page2')
def v_page2():
user = request.cookies['user']
return 'you are %s' % user
app.run(host='0.0.0.0',port=8080)
实验页面如下:
![10012508_MWqS.png](https://static.oschina.net/uploads/img/201709/10012508_MWqS.png)
![10012511_1W80.png](https://static.oschina.net/uploads/img/201709/10012511_1W80.png)
7. 构造JSON响应
在Flask中,可以使用json模块的dumps()方法将数组或字典对象转换为JSON字符串:
from flask import json
a = [1,2,3]
print json.dumps(a) # '[1,2,3]'
b = {'x':1,'y':2}
print json.dumps(b) # '{"x":1,"y":2}'
这在开发REST API时相当有用。比如我们定义获得用户列表的API为GET /user:
@app.route('/user')
def v_users():
users = ['Linda','Marion5','Race8']
return json.dumps(users),200,[('Content-Type','application/json;charset=utf-8')]
为了向客户端正确标示响应的类型,我们在视图函数返回时使用了元组,在响应报文头/headers 中添加了Content-Type字段,并设置响应正文类型为application/json。
实验代码如下:
# -*- coding:utf-8 -*-
from flask import Flask,json
app = Flask(__name__)
users = ['Linda','Marion5','Race8']
@app.route('/')
def v_index():
return json.dumps(users),200,[('Content-Type','application/json;charset=utf-8')]
app.run(host='0.0.0.0',port=8080)
实验页面如下:
![10012511_EXpz.png](https://static.oschina.net/uploads/img/201709/10012511_EXpz.png)
8. 重定向响应
使用flask框架的redirect()方法,可以要求客户端进行重定向:
flask.redirect(location, code=302, Response=None)
redirect()方法其实是构造了一个具有重定向状态码的Response对象。重定向状态码 默认为302,这表示一个临时性重定向。redirect()方法还支持以下重定向状态码:
-
301 - 请求的网页已被永久移动到新位置
-
302 - 服务器目前正从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
-
303 - 对于POST请求,它表示请求已经被处理,客户端可以接着使用GET方法去请求Location里的URI
-
305 - 请求者只能使用代理访问请求的网页。
-
307 - 对于POST请求,表示请求还没有被处理,客户端应该向Location里的URI重新发起POST请求
下面的示例中,当用户访问首页时,将自动重定向到新手页/newbies:
@app.route('/')
def v_index():
return redirect('/newbies')
@app.route('/newbies')
def v_newbies():
return 'this page is for newbies only!'
实验代码如下:
# -*- coding:utf-8 -*-
from flask import Flask,redirect
app = Flask(__name__)
@app.route('/')
def v_index():
return redirect('/newbies')
@app.route('/newbies')
def v_newbies():
return 'this page is for newbies only!'
app.run(host='0.0.0.0',port=8080)
实验页面如下:
![10012513_ltPr.png](https://static.oschina.net/uploads/img/201709/10012513_ltPr.png)
9. 终止响应
可以使用flask框架的 abort() 方法通知框架终止处理当前响应:
flask.abort(code)
abort()方法的code参数用来指定返回给客户端的HTTP状态码。由于abort()方法 将抛出HttpException异常,因此它之后的代码不会被执行。
下面的示例中,要求访问/admin时必须附加查询参数token,否则返回HTTP 状态码401,提醒用户没有权限:
@app.route('/admin')
def v_admin():
if 'token' in request.args:
return 'you are a good boy.'
else:
abort(401)
实验代码如下:
# -*- coding:utf-8 -*-
from flask import Flask,abort,request
app = Flask(__name__)
@app.route('/')
def v_index():
return '''
<ul>
<li><a href="/admin?token=111">allowed</a></li>
<li><a href="/admin">forbidden</a></li>
</ul>
'''
@app.route('/admin')
def v_admin():
if 'token' in request.args:
return 'you are a good boy.'
else:
abort(401)
app.run(host='0.0.0.0',port=8080)
实验页面如下:
![10012515_pfm8.png](https://static.oschina.net/uploads/img/201709/10012515_pfm8.png)
![10012515_Yt8s.png](https://static.oschina.net/uploads/img/201709/10012515_Yt8s.png)
![10012518_sOVB.png](https://static.oschina.net/uploads/img/201709/10012518_sOVB.png)
10. 会话 : 访问者上下文
会话/Session是为Web服务器建立状态的一个成熟模式。会话主要解决两个问题:
10.1 访问者的标识问题
服务器需要识别来自同一访问者的请求。这主要是通过浏览器的cookie实现的。 访问者在第一次访问服务器时,服务器在其cookie中设置一个唯一的ID号——会话ID。 这样,访问者后续对服务器的访问头中将自动包含该信息,服务器通过这个ID号,即可区 隔不同的访问者。
Flask框架中,每当一个请求进来时会自动根据请求中cookie的会话ID创建 一个Session类的实例对象。你可以查看当前请求的cookie验证这一点(会话ID的键 默认为session):
@app.route('/')
def v_index():
return request.cookies['session']
10.2 访问者信息的记录问题
服务器可以记录、提取指定访问者的历史信息。对每一个会话ID,服务端维护一个 数据上下文,这个数据运行在内存中,通常在变化时持久化到文件系统中或数据库中。
在视图函数内,Flask提供了一个全局对象session,它始终等效于当前请求所对应的 Session类实例对象。Session类定义了get_item()方法和set_item()方法, 因此我们可以像使用Dict对象一样,通过[]操作符读取或设置会话变量:
@app.route('/')
def index():
if !session['user']:
return redirect('/login')
return 'some restricted for authorized users only'
由于默认情况下,Flask将会话对象加密后存储在客户端的cookie里,因此必须要 为应用实例的secret_key属性配置一个加密种子才能使用session:
app.secret_key = 'sth. random as a encrypt key.'
实验代码如下:
# -*- coding:utf-8 -*-
from flask import Flask,session,request
app = Flask(__name__)
app.secret_key = 'XOIU01293jkjsjfdg127'
@app.route('/')
def v_index():
if 'counter' not in session:
session['counter'] = 0
session['counter'] = session['counter'] + 1
return 'this is your %d times visit' % session['counter']
app.run(host='0.0.0.0',port=8080)
实验页面如下:
![10012519_qRpN.png](https://static.oschina.net/uploads/img/201709/10012519_qRpN.png)
11. 会话应用示例
在一个邮件应用中,用户必须登录才能够访问自己的收件箱 —— 也就是说,当 用户访问/login登录后,服务端必须在会话对象中记录点用户信息;而当用户 访问/inbox读取邮件箱时,服务端必须根据会话对象中的相关信息给出合理的 数据。
这是一个适合使用会话的应用场景:
![10012520_Bk13.png](https://static.oschina.net/uploads/img/201709/10012520_Bk13.png)
第1~3步:
当访问者第一次访问/login时,它的请求头中cookie为空,服务器将生成一个会话ID: EQxE8eeq73QK7K_8,在服务端数据上下文中保存这个ID对应的上下文信息:{user:'Mary'}, 然后向访问者下发cookie,内容为:{session:'EQxE8eeq73QK7K_8'}。
第4~6步:
当访问者接着访问/inbox时,它的请求包头中将带有已经设置的cookie。服务端根据 cookie中的会话ID,从数据上下文中提取对应的信息:{user:'Mary'},并根据这个 信息从数据库中提取对应的邮件信息,返回访问者。
实验代码:
# -*- coding:utf-8 -*-
from flask import Flask,request,session,redirect,url_for
app = Flask(__name__)
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
@app.route('/')
def v_inbox():
if 'username' in session:
return '<h1>%s\' mailbox</h1>' % session['username']
else:
return 'not authorized.go <a href="%s">here</a> to authorize yourself' % url_for('v_auth')
@app.route('/login',methods=["POST","GET"])
def v_auth():
if request.method == 'GET':
return '''
<form action="%s" method="POST">
<input type="text" name="username" placeholder="input your username">
<input type="password" name="password" placeholder="input your password">
<input type="submit" value="submit">
</form>
''' % url_for('v_auth')
if request.method == 'POST':
session['username'] = request.form['username']
return 'authorized! go <a href="%s">inbox</a>' % url_for('v_inbox')
app.run(host='0.0.0.0',port=8080)
Reference: