1 学习目标
-
能够写出带有参数的路由及视图函数
-
能够说出 url_for 函数的作用
-
能够说出自定义转换器的步骤
2 路由基本定义
- 明确路由定义的参数,请求方式指定
- PostMan 的使用
2.1 指定路由地址
# 指定访问路径为 demo1
@app.route('/demo1')
def demo1():
return 'demo1'
2.2 给路由传参示例
有时我们需要将同一类 URL 映射到同一个视图函数处理,比如:使用同一个视图函数来显示不同用户的个人信息。
# 路由传递参数
@app.route('/user/<user_id>')
def user_info(user_id):
return 'hello %s' % user_id
- 路由传递的参数默认当做 string 处理,也可以指定参数的类型 <转换器名字:参数名>
# 路由传递参数
@app.route('/user/<int:user_id>')
def user_info(user_id):
return 'hello %d' % user_id
这里指定int,尖括号中的内容是动态的,在此暂时可以理解为接受 int 类型的值,实际上 int 代表使用 IntegerConverter 去处理 url 传入的参数
无论 user_id 是什么类型,都可以用 %s 来输出
2.3 指定请求方式
在 Flask 中,定义一个路由,默认的请求方式为:
- GET
- OPTIONS(自带)
- HEAD(自带)
如果想添加请求方试,那么可以如下指定:
@app.route('/demo2', methods=['GET', 'POST'])
def demo2():
# 直接从请求中取到请求方式并返回
return request.method
demo2 请求方式为:
3 使用 PostMan 对请求进行测试
3.1 安装 PostMan 插件
PostMan 是一款功能强大的网页调试与发送网页 HTTP 请求的 Chrome 插件,可以直接去对我们写出来的路由和视图函数进行调试,作为后端程序员是必须要知道的一个工具。
安装方式1:去 Chrome 商店直接搜索 PostMan 扩展程序进行安装
安装方式2:https://www.getpostman.com/ 官网下载桌面版
安装方式3:将已下载好的 PostMan 插件文件夹拖入到浏览器
打开 Chrome 的扩展程序页面,打开 开发者模式 选项
将插件文件夹拖入到浏览器(或者点击加载已解压的扩展程序选择文件夹)
使用 PostMan,打开之后,会弹出注册页面,选择下方的 Skip this,go straight to the app
进行程序
3.2 postman使用
我们使用postman来测试一下这个函数:
使用如下:
4 视图常用逻辑
- 返回 JSON
- 重定向
- url_for
- 自定义状态码
4.1 JSON数据格式&返回JSON
4.1.1 JSON 介绍
新建一个json文件,我们来看一下json数据的个数。
json其实说白了就是一个字符串,格式类似于python中的字典。
作用:用于浏览器与服务器进行数据传输。
json:浏览器与服务器直接进行数据传输的一种数据格式。
4.1.2 字典转换为json
我们在代码中定义一个字典,然后将字典转换为json格式的字符串返回给浏览器
访问结果:
验证一下这个格式是否正确如下:(将上图的json字符串复制到下图的大框中,然后点击校验)
最终看到正确的json及说明是正确的json。
4.1.3 json转换为字典
4.1.4 返回JSON
在使用 Flask 写一个接口时候需要给客户端返回 JSON 数据,在 Flask 中可以直接使用 jsonify 生成一个 JSON 的响应,并且以application/json的内容格式返回到浏览器.
# 返回JSON
@app.route('/demo4')
def demo4():
json_dict = {
"user_id": 10,
"user_name": "laowang"
}
# jsonify 会指定响应内容的数据格式(告诉客户端,返回的数据格式是什么)
return jsonify(json_dict)
直接返回json字符串和通过jsonify返回json字符串的区别:
jsonify:返回的时候content-type为application/json
直接返回:content-type为默认text/html
为什么要用jsonify:这个跟前端逻辑有关系
不推荐使用 json.dumps 转成 JSON 字符串直接返回,因为返回的数据要符合 HTTP 协议规范,如果是 JSON 需要指定 content-type:application/json
这里大家先记住,如果要返回json格式的字符串,并且让前端知道是一个json格式的字符串,那就必须设置content-type:application/json。
5 重定向
重定向到 京东官网
# 重定向
@app.route('/demo5')
def demo5():
return redirect('http://www.jd.com')
重定向到自己写的视图函数
可以直接填写自己 url 路径(这种写法有缺陷,如果user的路径修改了,那么我们的重定向的url也需要修改)
也可以使用 url_for 生成指定视图函数所对应的 url(推荐使用,需要导包)
语法:url_for(“函数名”)
@app.route('/demo1')
def demo1():
return 'demo1'
# 重定向
@app.route('/demo5')
def demo5():
return redirect(url_for('demo1'))
重定向到带有参数的视图函数
在 url_for 函数中传入参数
# 路由传递参数
@app.route('/user/<int:user_id>')
def user_info(user_id):
return 'hello %d' % user_id
# 重定向
@app.route('/demo5')
def demo5():
# 使用 url_for 生成指定视图函数所对应的 url
return redirect(url_for('user_info', user_id=100))
6 自定义状态码
在 Flask 中,可以很方便的返回自定义状态码,以实现不符合 http 协议的状态码,例如:status code: 666
@app.route('/demo6')
def demo6():
return '状态码为 666', 666
还可以为自定义的状态码进行说明
语法: “状态码 状态码名字” (双引号引起来 空格隔开)
7 正则匹配路由
7.1 默认转换器的缺点:
如果我们现在想限制user/后边只能有6个数字,int转换器做不到
7.2 自定义转换器
定义一个自定义的转换器:RegexConverter
继承BaseConverter基类,重写regex属性
访问:
如果是4位数字,就找不到:
我们来看下原理:
看下int转换器:IntegerConverter
注:基类中 regex 为类属性,而重写 regex 属性实质上是添加了实例属性 regex。
7.3 自定义转换器升级
发现上面的正则转换器,有点笨,只能处理一种正则表达式,所以需要进行升级。
7.3.1 实现步骤
在 web 开发中,可能会出现限制用户访问规则的场景,那么这个时候就需要用到正则匹配,根据自己的规则去限定请求参数再进行访问
具体实现步骤为:
- 导入转换器基类:在 Flask 中,所有的路由的匹配规则都是使用转换器对象进行记录
- 自定义转换器:自定义类继承转换器基类
- 添加转换器到默认的转换器字典中
- 使用自定义转换器实现自定义匹配规则
7.3.2 代码实现
导入转换器基类
from werkzeug.routing import BaseConverter
自定义转换器:继承转换器基类
# 自定义正则转换器
class RegexConverter(BaseConverter):
def __init__(self, url_map, *args):
super(RegexConverter, self).__init__(url_map)
# 将接受的第1个参数当作匹配规则进行保存
self.regex = args[0]
添加转换器到默认的转换器字典中,并指定转换器使用时名字为:re
app = Flask(__name__)
# 将自定义转换器添加到转换器字典中,并指定转换器使用时名字为: re
app.url_map.converters['re'] = RegexConverter
使用转换器去实现自定义匹配规则
当前此处定义的规则是:6位数字
# <re("\\d{6}"):user_id> 或者 <re(r"\d{6}"):user_id>
@app.route('/user/<re("[0-9]{6}"):user_id>')
def user_info(user_id):
return "user_id 为 %s" % user_id
运行测试:http://127.0.0.1:5000/user/123 ,如果访问的url不符合规则,会提示找不到页面
8 补充-startflask
配置:live Templates 活动模板, 配置完之后,就可以快速编码-代码块。
注意第七步:
原本不是change:而是define.
点击Define,弹出如下框. 选择Python( 这一步的意思就是,你这个代码块要在哪里使用.我们在python代码中使用)
输入startflask敲回车:
就会生成代码。
9 自定义转换器其他两个函数实现
9.1 转换器 to_python
断点调试访问:(如上图,在36行打上断点,然后访问下边的url)
观察下图可以发现,user_ids的值是一个字符串:
但是我们想接收到的是一个列表怎么办?换句话说,如何才能在视图函数中接收到的 user_ids 就是一个列表?
接下来就来看一下我们要分析的 to_python
先定义一个转换器,并且重写 to_python 如下
注:regex 还可以写为:regex = r"(\d+,?)+\d$"
使用转换器.
再次访问之后,发现跟之前没有区别。这里的user_ids也没有变成我们想要的列表.
怎么办呢? 做如下修改:
再次访问:
断点如下:
最终结果:
思考:这个int转换器是如何将匹配到的字符串转换为int类型之后给user_id的?(提示:to_python)
to_python 分析:
9.2 转换器to_url
9.2.1 问题
我们先来看一个例子:
新建demo3函数。重定向到demo2
上图红框代码说明:
其实demo2,在访问的时候url应该是:127.0.0.1:5000/users/1,2,3,4 然后list转换器将1,2,3,4转换为列表。
那么上图中通过url_for传递数据user_ids数据的时候,应该传递字符串:1,2,3,4
但是我们程序中可能就是一个user id的一个列表。
所以我们这里就传递列表过去。
来访问:
发现报错。肯定报错了。这是因为url中怎么可能传递一个列表的数据呢?
上述url是经过浏览器编码了。
其实应该是如下:
其实修改一下我们传递的数据即可,如下:
但是我们的逻辑中可能user的id本身是一个列表。传的时候,我们确实也可以将列表转换为一个字符串,传递到下边。但是这件事情,能不能别人帮咱们处理呢?
9.2.2 to_url介绍
来看一下to_url:
注意,demo3的代码,还应该是列表:
我们先来研究下to_url
访问/demo3,断点调试如下:
发现to_url中的value是我们url_for传递的参数。user_ids。
得出如下结论:
那就简单了。我们利用to_url,对value做一个转换即可:
再次访问:
断点调试如下:
断点走完,最终结果:
9.3 总结
继承于自定义转换器之后,还可以实现 to_python 和 to_url 这两个函数去对匹配参数做进一步处理:
to_python:
- 该函数参数中的 value 值代表匹配到的值,可输出进行查看
- 匹配完成之后,对匹配到的参数作最后一步处理再返回,比如:转成 int 类型的值再返回:
class RegexConverter(BaseConverter):
def __init__(self, url_map, *args):
super(RegexConverter, self).__init__(url_map)
# 将接受的第1个参数当作匹配规则进行保存
self.regex = args[0]
def to_python(self, value):
return int(value)
运行测试,在视图函数中可以查看参数的类型,由之前默认的 str 已变成 int 类型的值
to_url:
- 在使用 url_for 去获取视图函数所对应的 url 的时候,会调用此方法对 url_for 后面传入的视图函数参数做进一步处理
- 具体可参见 Flask 的 app.py 中写的示例代码:ListConverter
9.4 系统自带转换器
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
系统自带的转换器具体使用方式在每种转换器的注释代码中有写,请留意每种转换器初始化的参数。
10 异常捕获
10.1 HTTP 异常主动抛出
abort 方法
- 抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用 abort(404)。
- 参数:code – HTTP的错误状态码
# abort(404)
abort(500)
抛出状态码的话,只能抛出 HTTP 协议的错误状态码
常见的状态码:HTTP Status Code
10.2 捕获错误
errorhandler 装饰器
- 注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法
参数:
- code_or_exception – HTTP的错误状态码或指定异常
例如统一处理状态码为500的错误给用户友好的提示:
@app.errorhandler(500)
def internal_server_error(error):
return '服务器搬家了'
捕获指定异常
@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
return '除数不能为0'
11 请求钩子
在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:
- 在请求开始时,建立数据库连接;
- 在请求开始时,根据需求进行权限校验;
- 在请求结束时,指定数据的交互格式;
为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设施的功能,即请求钩子。
请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:
- before_first_request
- 在处理第一个请求前执行
- before_request
- 在每次请求前执行
- 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
- after_request
- 如果没有抛出错误,在每次请求后执行
- 接受一个参数:视图函数作出的响应
- 在此函数中可以对响应值在返回之前做最后一步修改处理
- 需要将参数中的响应在此参数中进行返回
- teardown_request:
- 在每次请求后执行
- 接受一个参数:错误信息,如果有相关错误抛出
总结:请求钩子类似于装饰器,可以在不修改函数内部的逻辑前提下增加逻辑.
代码测试
from flask import Flask
from flask import abort
app = Flask(__name__)
# 在第一次请求之前调用,可以在此方法内部做一些初始化操作
@app.before_first_request
def before_first_request():
print("before_first_request")
# 在每一次请求之前调用,这时候已经有请求了,可能在这个方法里面做请求的校验
# 如果请求的校验不成功,可以直接在此方法中进行响应,直接return之后那么就不会执行视图函数
@app.before_request
def before_request():
print("before_request")
# if 请求不符合条件:
# return "请求失败!"
# 在执行完视图函数之后会调用,并且会把视图函数所生成的响应传入,可以在此方法中对响应做最后一步统一的处理
@app.after_request
def after_request(response):
print("after_request")
response.headers["Content-Type"] = "application/json"
return response
# 请每一次请求之后都会调用,会接受一个参数,参数是服务器出现的错误信息
# 没有报异常也会在index之后执行此函数,只不过没有将异常信息传递给error而已.
@app.teardown_request
def teardown_request(error):
print("teardown_request")
@app.route('/')
def index():
return 'index'
if __name__ == '__main__':
app.run(debug=True)
在第1次请求时的打印:
before_first_request
before_request
after_request
teardown_request
在第2次请求时的打印:
before_request
after_request
teardown_request