Flask之REST&API设计
一、REST(一种软件架构风格)
一)、问题
网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备......)。
因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这致使API构架的流行。
二)、基本概念
REST是"Representational State Transfer"缩写,即是"表现层状态转化"。而"表现层"其实指的是"资源(Resource)"的表现层。
一种软件构架风格,设计风格,而不是标准,只是提供了一组设计原则和约束条件。
它主要用于客户端和服务端交互的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。
REST其实是一种组织Web服务的架构,而并不是我们想象的那样是实现Web服务的一种新的技术,更没有要求一定要使用HTTP。其目标是为了创建具有良好扩展性的分布式系统。
1. 资源(Resource) 就是网络上的一个实体,或是网络上的具体信息,可以是一段文本、一张图片、一首歌曲、一部电影。 每个资源都会对应的URL,且是唯一的标识符,想要获取资源只需要调用对应URL。
2. 表现层(Representation) "资源"是一种信息实体,它可以有多种外在表现形式。 而"资源"具体呈现出来的形式,就叫它的"表现层"。
3. 状态转换(State Transfer) 访问一个网站,就是客户端和服务端的交互过程,这个过程中就会涉及到数据和状态的变化。 互联网通信协议HTTP,是无状态协议。即所有状态都保存在服务端。
客户端要操作服务端必须通过某种方式,让服务端发生"状态转换",而这转换是建立在表现层之上的,所以就是"表现层状态转换"。
客户端用到的手段只能是HTTP协议,在操作方式的动词:
GET/POST/PUT/DELETE。
对应GET获取资源,
POST新建资源(或更新资源),
PUT更新资源,
DELETE删除资源。
三)、构架级约束
1.使用客户/服务器模型。客户和服务器之间通过一个统一的接口来互相通讯
2.层次化的系统。在一个REST系统中,客户端并不会固定地与一个服务器打交道
3.无状态。在一个REST系统中,服务端并不会保存有关客户的任何状态。也就是说,客户端自身负责用户状态的维持,并在每次发送请求时都需要提供足够的信息
4.可缓存。REST系统需要能够恰当地缓存请求,以尽量减少服务端和客户端之间的信息传输,以提高性能
5.统一的接口。一个REST系统需要使用一个统一的接口来完成子系统之间以及服务与用户之间的交互。这使得REST系统中的各个子系统可以独自完成演化
注意:一个系统满足了上面所列出的五条约束,那么该系统就被称为是RESTful
二、RESTful API设计
一)、协议 API与用户通信协议,通常使用HTTP(S)协议。
二)、域名应该尽量将API部署在专用域名之下。 如:http://api.zyz.com 如果确定API很简单,不会有大规模扩充。可以考虑放在 主域名之下。如:http://www.zyz.com/api/
三)、版本 应该将API的版本号放入URL。
如:http://api.zyz.com/v1/ 也有将版本号放在HTTP的头信息中,但不如放在URL中方便直观,Github就是这么做的。
四)、路径路径又称"终点"(endpoint),表示API的具体网址。在RESTful架构中,每个网址代表一种资源,所以网址不能有动词,只能有名词。而所用名词往往与数据库表单名对应。
五)、HTTP动词对于资源的具体操作类型,由HTTP动词表示。
HTTP常用动词:
- GET(SELECT) 从服务器取资源
- POST(CREATE or UPDATE) 服务器中创建资源或更新资源
- PUT(UPDATE) 在服务器更新资源(客户端提供改变后的完整资源)
- PATCH(UPDATE) 在服务器更新资源(客户端提供改变的属性)
- DELETE(DELETE) 从服务器删除资源 - HEAD 获取资源的元数据
- OPTHONS 获取信息,关于资源的那些属性是客户端可以改变的例如:
- GET /students 获取所有学生
- POST /student 新建学生
- GET /students/id 获取某一个学生
- PUT /students/id 更新某个学生的信息(需要提供学生的全部信息)
- PATHC /students/id 更新某个学生的信息(需要提供学生变更信息) - DELETE /students/id 删除某个学生
六)、过滤信息当记录数量过多,服务器不可能将它们返回给用户。
APIT应该提供参数,过滤返回结果。 ?limit=10 ?offset=10 ?page=2&per_page=10 ?sortby=name&order=desc ?student_id=id
七)、状态码
服务器向用户返回的状态码和提示信息。
00 OK - [GET]:服务器成功返回用户请求的数据 201 CREATED -[POST/PUT/PATCH]:用户新建或修改数据成功
202 Accepted - [*] :表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:表示数据删除成功
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误
401 Unauthorized - [*] :表示用户没有权限(令牌,用户名,密码错误)
403 Forbidden - [*]:表示用户得到授权,但是访问是被禁止的
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录
406 Not Acceptable - [*]:用户请求格式不可得 410 Gone - [GET] :用户请求的资源被永久移除,且不会再得到的
422 Unprocesable entity -[POST/PUT/PATCH]:当创建一个对象时,发生一个验证错误
500 INTERNAL SERVER EROR - [*] :服务器内部发生错误
2xx —— 正确的响应
3xx —— 重定向
4xx —— 客户端错误
5xx —— 服务端错误
八)、返回结果
错误处理:
如果状态码是4xx,就应该向客户端返回出错信息,一般来说,返回信息中将error作为键名
针对不同操作,服务器向用户返回的结果应符合一下规范:
GET /collection:返回资源对象的列表(数组,集合)
GET /collection/id:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/id:返回完整的资源对象
PATCH /collection/id:返回完整的资源对象 DELETE /collection/id:返回一个空文档注意:服务器返回的数据格式,应该尽量是JSON
九)、原生实现数据和状态的返回@blue.route('/user/',methods=['POST','GET','PUT','DELETE','PATCH'])
def user(userid):
if request.method == 'GET': # 获取用户
pass
elif request.method == 'POST': # 更新或创建用户密码
# 获取数据
username = request.form.get('username')
password = request.form.get('password')
data = {
'msg':'ok',
'status': 201
}\
# 不为空
if not username or not password:
data['msg'] = '参数不正确'
data['status'] = 422 return jsonify(data),422
user = User()
user.u_name = username
user.u_passwd = generate_passwd(password)
try:
db.session.add(user)
db.session.commit()
except Exception as e:
data['msg'] = '用户已存在'
data['status'] = 423
return jsonify(data), 423
return jsonify(data),201
elif request.method == 'DELETE': # 删除用户
pass
elif request.method == 'PUT': # 更新账和密码
pass
elif request.method == 'PATCH': # 修改密码
pass
else:
abort(405)
# 密码加密处理
def generate_passwd(passwd):
hash = hashlib.md5() hash.update(passwd.encode('utf-8'))
return hash.hexdigest()
三、Flask-RESTful插件
Flask-RESTful添加快速构建REST API的支持,也是一个能够和现有的ORM库协同工作的轻量级的扩展。Flask-RESTful鼓励以最小的设置的最佳实践。
一)、基本使用 安装 pip install flask-restful
相关文档:http://www.pythondoc.com/Flask-RESTful/quickstart.html
配置
#ext.py文件中
from flask-restful import Api
api = Api()
使用
# 定义一个资源 # views.py 已经可以替换为apis.py,之前的路由功能换种写法
class HelloWorld(Resource):
def get(self): # get 请求
return {
'msg':'hello world'
}
def post(self): #post请求
return {
'msg':'你好!
'}
#添加一个资源
#ext.py文件中
#add_resource 注册路由到框架上
#如果没有指定 endpoint,Flask-RESTful会根据类名生成一个
#但有时候如 url_for 需要 endpoint ,因次最好明确给 endopint 赋值
api.add_redource(HelloWorld,'/hello/',endopint ='hello')
备注:之前路的操作都是在views.py中,现在只需要提供API借口,所以功能就会不一样。
在项目进行完基本的拆分之后,可以将views.py改变api.py。
注意:Flask-RES-JSON插件,Flask-RESTless插件
二)、带参数操作
#apis.py
class UserAPI(Resource):
def get(self,userid):
str = '(get)userid: %d' % userid
return {
'msg':str
}
def post(self,id):
str = '(post)userid: %d' %id
return {
'msg':str
}
#ext.py 文件
from App.apis import UserAPI api.add_resource(UserAPI,'/user//',endopint='user')
注意:Flask-RESTful提供的最主要的基础是资源(resources)。资源(Resources)是构建在Flask试图之上,只要在你的资源(resource)上定义方法就能够容易地访问 多个HTTP方法。[无需原生操作,因为一个资源而进行不同的判断操作处理]
三)、端点操作(Endopints)
很多时候在一个 API 中,你的资源可以通过多个 URL 访问。
你可以把 多个 URL传给 Api对象 Api.add_resource()方法。每个URL都能访问到你的 Resource.
api.add_resource(HelloWorld,'/hello/','/haha/','/hehe/')
四)、输出格式定制
默认情况下,在你返回的迭代中所有字段将会原样呈现。
实际更多的需要一个字典类型数据,之后通过 JSON序列化即可。
Flask-RESTful 提供了 fields模块和 marshal_with() 装饰器来进行数据格式化。
# @marshal_with(需要返回的数据格式)
如果返回的数据,在预定义的结构中不存在,会被自动过滤掉;
如果返回的数据,在预定义的结构中存在,数据会正常返回;
如果返回的数据比预定义的结构字段少,预定义的字段会显示默认值;
#支持类型
常用基本类型
String
Integer
类表类型
List
级联类型
Nested
结构嵌套
fields.List(fields.Nesred())
# 示例1
"""获取一只猫的数据基本结构
{
'msg':'ok',
'status':200,
'data':{
'id':1,
'name':'TOM',
'color':'红色'
}
}
"""
catmodel_fields = {
'id':fields.Integer,
'name':fields.String,
'color':fields.String
}
onecar_fields = {
'msg': fields.String(default='ok'),
'status': fields.Integer(default=200),
'data': fields.Nested(catmodel_fields) # 嵌套
}
class OneCatResource(Resource):
@marshal_with(onecar_fields)
def get(self):
cat = Cat.query.first()
data = {
# msg 使用默认值,可以省略不写
# status 使用默认只,也可以不写
'data': cat
}
return data
# 示例2
""" 获取所有猫的数据结构
{
'msg':'ok',
'status': 200,
'data': [
{
'id': 1,
'name': 'TOM1',
'color': '红色'
},
{
'id': 2,
'name': 'TOM2',
'color': '红色'
},
{
'id': 3,
'name': 'TOM3',
'color': '红色'
},
...
]
}
"""
catmodel_fields = {
'id': fields.Integer,
'name': fields.String,
'color': fields.String
}
cats_fields = {
'msg': fields.String(default='ok'),
'status': fields.Integer(default=200),
'data': fields.List(fields.Nested(catmodel_fields))
}
class CatResource(Resource):
@marshal_with(cats_fields)
def get(self):
cats = Cat.query.all()
data = {
'msg': 'ok!',
'status': 200,
'data': cats
}
return data
五)、请求参数解析
基本使用
# https://127.0.0.1/showview/?page=1
parser = reqparse.RequestParser()
#接受参数page,类型是str,错误提示help
parser.add_argument('page',type=str,help='请输入页码')
class ShouView(Resource):
def get(self):
parser = parser.parse_args()
c_page = parser.get('page') or 1
def post(self):
parser = parser.parse_args()
c_page = parser.get('page') or 1
必须参数 required = Ture
# 请求参数必须传入
parser.add_argument('page', type=int, help='请输入页码', required=True)
多参数(列表)
# 如果要接受一个键有多个值的话,可以传入 action='append'
# https://127.0.0.1/showview/?name='liming'&page=1&name='zhangsan'
parser.add_argument('name', type=str, action='append')
参数位置
参数位置: form、args、headers、cookies、files(上传文件)
网友评论