共计 6999 个字符,预计需要花费 18 分钟才能阅读完成。
一、RESTful Api 设计风格
1、什么是 RESTFul
-
简介
REST 即表述性状态传递(英文:Representational State Transfer,简称 REST)是 Roy Fielding 博士在 2000 年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。
RESTFul 是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。遵循 restful 风格开发出来的应用程序接口,就叫 RESTFul API。
RESTFul 的接口都是围绕资源以及对资源的各种操作展开
-
资源
所谓的资源就是在网络上存在的任意实体,哪怕是一条消息。
-
操作
所谓的操作就是对资源的 CURD。在开发者设计良好的前提下,对网络资源的任意的动作都可抽象为对资源的 CURD。RESTFul 对网络资源的操作抽象为 HTTP 的 GET、POST、PUT、DELETE 等请求方法以完成对特定资源的增删改查
2、协议
API 与用户的通信协议总是使用 https 协议
3、域名
应尽量将 API 部署在专用域名下
https://www.xialigang.com
如果确定 API 很简单,不会有进一步扩展,可以放在主域名下
https://www.xialigang.com/api/
4、版本
应该将版本放入 URL
https://www.xialigang.com/api/v1/
可以将版本放入 http 头信息中
5、路径
说明
- 表示 API 的具体地址
注意
- 每个网址代表一种资源,所以网址中不能有动词,只能是名词,而且所用的名词往往与数据库中的表名对应
示例
-
错误示例
https://127.0.0.1/api/v1/getStudents/
-
正确示例
https://127.0.0.1/api/v1/students/
6、使用正确的 HTTP 请求方式
方法 | 行为 | 例子 |
---|---|---|
GET | 获取所有资源 | http://127.0.0.1:5000/api/source |
GET | 获取指定资源 | http://127.0.0.1:5000/api/source/250 |
POST | 创建新的资源 | http://127.0.0.1:5000/api/source |
PUT | 更新指定资源 | http://127.0.0.1:5000/api/source/250 |
DELETE | 删除指定资源 | http://127.0.0.1:5000/api/source/250 |
DELETE | 删除所有资源 | http://127.0.0.1:5000/api/source/ |
7、过滤信息
-
概述
如果资源较多,服务器不能将所有的数据一次全部返回给客户端,API 提供参数,过滤返回结果 -
参数
-
limit 获取多少个资源
GET https://127.0.0.1:5000/api/v1/students/?limit=10
-
offset 偏移多少个资源
GET https://127.0.0.1:5000/api/v1/students/?offset=10 -
page 要获取哪页的资源
-
per_page 每页有多个资源
GET https://127.0.0.1:5000/api/v1/students/?page=1&per_page=5 -
sortby 根据哪个属性进行排序
-
orderby 排序的规则
GET https://127.0.0.1:5000/api/v1/students/?sortby=age&orderby=desc
-
类名小写_运算符_属性 = 值
GET https://127.0.0.1:5000/api/v1/students/?student_gt_age=20
-
8、状态码
状态码 | 请求方式 | 说明 |
---|---|---|
200 | get | OK 服务器成功返回资源 |
201 | post、put、patch | Created 用户新建或者修改资源成功 |
202 | * | Accepted 表示请求已经进入后台排队 |
204 | delete | No Content 用户删除资源成功 |
400 | post、put、patch | Bad Request 用户发出的请求有错误 |
401 | * | Unauthorized 用户没有权限(令牌、用户名、密码错误) |
403 | * | Forbidden 表示用户得到授权(与 401 相对),但是访问是被禁止的 |
404 | * | Not Found 请求针对的是不存在的资源 |
405 | * | Method Not Allowed 用户请求的方式不被允许 |
406 | get | Not Acceptable 用户请求的格式不可得(比如用户请求 json 格式,但是只有 xml 格式) |
410 | get | Gone 用户请求的资源被永久删除,且不可在得到 |
422 | post、put、patch | Unprocessable Entity 创建对象时发生了验证错误 |
500 | * | Internal Server Error 服务器发生错误 |
9、错误处理
如果状态码是 4xx,就应该向用户返回错误信息,一般返回内容中以 error 作为键,错误信息作为值返回
{"error": "参数有误"
}
10、链接相关的资源
-
说明
返回的结果中提供了链接,链向其他 API 方法啊,需要让用户不查看文档(项目文档)就知道下一步该干什么
-
实例
地址
GET /students/<id>/
{"name": "lucky", "age": 50, "link": "https://127.0.0.1/api/v1/test/" }
restful 风格
{"name": "lucky", "age": 50, "link": {"rel": "collection 127.0.0.1:5000" "href": "127.0.0.1/api/v1/test/" "title": "测试界面" "type": "application/json" } }
键
- rel 表示这个 API 与当前网址的关系
- href 表示 API 的路径
- title 表示 API 的标题
- type 表示返回数据的类型
11、工具
-
说明
postman 是一款非常好用的 API 开发测试工具,可以非常方便的模拟各种请求
-
提示
下载安装包,一路 NEXT 完成安装
网址:https://www.postman.com/
二、原生实现 RESTful
1、准备数据
# 测试数据
posts = [
{'id': 1,
'title': 'Python 语法',
'content': '别人都说 python 语法很简单,但是每次问题都出在语法上'
},
{'id': 2,
'title': 'HTML',
'content': '不就是几个标签的问题嘛,但是最好细心点'
}
]
2、获取资源
-
获取所有资源
# 获取资源列表 @app.route('/posts') def get_posts_list(): return jsonify({'posts': posts})
-
获取指定资源
# 获取指定资源 @app.route('/posts/<int:pid>') def get_posts(pid): p = list(filter(lambda p: p['id'] == pid, posts)) if len(p) == 0: abort(404) return jsonify({'posts': p[0]})
3、添加新的资源
# 添加新的资源
@app.route('/posts', methods=['POST'])
def create_posts():
if not request.json or 'title' not in request.json or 'content' not in request.json:
abort(400)
# 创建新资源
p = {'id': posts[-1]['id'] + 1,
'title': request.json['title'],
'content': request.json['content']
}
# 保存资源
posts.append(p)
return jsonify({'posts': p}), 201
4、更新指定的资源
# 修改指定资源
@app.route('/posts/<int:pid>', methods=['PUT'])
def update_posts(pid):
p = list(filter(lambda p: p['id'] == pid, posts))
if len(p) == 0:
abort(404)
if 'title' in request.json:
p[0]['title'] = request.json['title']
if 'content' in request.json:
p[0]['content'] = request.json['content']
return jsonify({'posts': p[0]}), 201
5、删除指定资源
# 删除指定资源
@app.route('/posts/<int:pid>', methods=['DELETE'])
def delete_posts(pid):
p = list(filter(lambda p: p['id'] == pid, posts))
if len(p) == 0:
abort(404)
posts.remove(p[0])
return jsonify({'result': '数据已删除'}), 204
6、删除所有资源
# 删除所有资源
@app.route('/posts', methods=['DELETE'])
def delete_posts(pid):
posts.clear()
return jsonify({'result': '数据已删除'}), 204
7、错误定制
@app.errorhandler(404)
def page_not_found(e):
return jsonify({'error': 'page not found'}), 404
@app.errorhandler(400)
def bad_request(e):
return jsonify({'error': 'bad request'}), 400
三、flask-restful
1、安装
pip install flask-restful
2、创建
from flask_restful import Api
api = Api()
from .ext_api import api
3、加载
from exts import api
api.init_app(app)
4、视图类
from flask_restful import Resource
from myApp.models import User
from flask import request
# 创建用户处理类
class UserApi(Resource):
def get(self, uid):
u = User.query.get(uid)
if u:
return {'code':0, 'error':'','data':{'id':uid, 'uusername':u.uusername, 'uage': u.uage, 'usex': u.usex, 'uinfo': u.uinfo}}
else:
return {'code':1, 'error':'获取失败','data':{}}, 404
def put(self, uid):
u = User.query.get(uid)
if not u:
return {'code':1, 'error':'获取失败','data':{}}, 404
json = request.json
u.uusername = json.get('uusername')
u.uage = json.get('uage')
u.save()
return {'code': 0, 'error': '',
'data': {'id': uid, 'uusername': u.uusername, 'uage': u.uage, 'usex': u.usex, 'uinfo': u.uinfo}}, 201
def delete(self, uid):
u = User.query.get(uid)
if not u:
return {'code': 1, 'error': '获取失败', 'data': {}}, 404
u.delete()
return {'code': 0, 'error': '', 'data': {}}, 204
class UserApiTwo(Resource):
def get(self):
u = User.query.all()
userList = []
for user in u:
userList.append({'id':user.id, 'uusername':user.uusername, 'uage': user.uage, 'usex': user.usex, 'uinfo': user.uinfo})
return {'code': 0, 'error': '', 'data': userList}
def delete(self):
uList = User.query.all()
for u in uList:
u.delete()
return {'code':0, 'error': '', 'data':''}, 204
def post(self):
json = request.json
u = User()
u.uusername = json.get('uusername')
u.uage = json.get('uage')
u.usex = bool(json.get('usex'))
u.uinfo = json.get('uinfo')
print(json)
if u.save():
return {'code': 0, 'error': '',
'data': {'id': u.id, 'uusername': u.uusername, 'uage': u.uage, 'usex': u.usex,'uinfo': u.uinfo}}, 201
return {'code':1 ,'error':'创建失败', 'data':''}, 400
5、配置路由
# 添加资源,可以一个资源指定多个路由地址
api.add_resource(UserAPI, '/users/<int:uid>', '/u/<int:uid>')
api.add_resource(UserListAPI, '/users/')
# 若创建 Api 对象时没有指定 app,那么指定 app 的位置应放在添加资源之后
6、拆分路由和视图
api 目录下一个文件就是一个模型相关的视图类,urls.py 仅做路由的匹配
from myApp.api.api_user import UserApi, UserListAPI
# 配置路由
api.add_resource(UserAPI, '/users/<int:uid>', '/u/<int:uid>')
api.add_resource(UserListAPI, '/users/')
7、添加认证
-
说明
Restful API 不保存状态,无法依赖 Cookie 及 Session 来保存用户信息,自然也无法使用 Flask-Login 扩展来实现用户认证。所以这里,我们就要介绍另一个扩展,Flask-HTTPAuth
-
安装
pip install flask-httpauth
-
示例
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from flask_httpauth import HTTPBasicAuth # 创建对象 auth = HTTPBasicAuth() # 认证的回调函数 @auth.verify_password def verify_password(username_or_token, password): # 去数据库有中进行查找 u = User.query.filter(User.uusername == username_or_token, User.upassword == password).first() print(username_or_token, '=====>',password) if u: # if username_or_token == 'lucky' and password == '123456': g.username = username_or_token return True # 验证 token s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(username_or_token) g.username = data['username'] return True except: return False # 认证错误定制 @auth.error_handler def unauthorized(): return jsonify({'error': 'Unauthorized Access'}), 403
获取 token
# 获取 token @app.route('/get_token') @auth.login_required def generate_token(): s = Serializer(app.config['SECRET_KEY'], expires_in=3600) return s.dumps({'username': g.username})
保护指定的视图类
class UserAPI(Resource): # 添加认证 decorators = [auth.login_required]