再次理解Flask中RESTfulAPI
再次理解 Flask 中 RESTful API
用 Flask 构建 RESTful 有两种方法,一种是狗书中用到的方法,把视图“包装”成 API —— 视图函数完成 API 所需要的功能,但最后返回的不再是一个 HTML 文件,而是由所需要的数据构成的 JSON。
而另一种方法就是使用 flask-restful 扩展来构建 API 了,利用专有的类来作为 REST 的资源部分,通过资源类的不同方法来处理不同的 http 请求,最后以 JSON 格式返回数据,具体步骤如下:
初始化扩展
from flask_restful import Api rest_api = Api()
自定义资源类以及相应的请求方法(对应了一个路由)
from flask_restful import Resource class PostApi(Resource): # 如果用没有定义的请求方法访问则会返回 405 错误 def get(self): pass def post(self): pass def put(self): pass
初始化 API 对象路由
from .extensions import rest_api from .controller.rest.post import PostApi def create_app(object_name): ... # 如果是多个路由则要分别绑定 rest_api.add_resource(PostApi, '/api/post', '/api/post/<int:post_id>') rest_api.add_resource(AnotherApi, '/api/another') rest_api.init_app(app)
JSON 化输出格式
JSON 化也同样有两种方法:
- 第一种是使用 flask.jsonify() 把数据手动转换成 JSON 对象再返回。
第二种则是使用 field 对象和 mashal_with 函数来进行转换,返回直接返回原来的对象即可。这种方法通过一个由 field 定义的 dict 来说明要转化输出的内容,并且把它传递给 mashal_with 装饰器,最后在返回之前自动转换成相应的 JSON 对象。
from flask_restful import Resource, fields, mashal_with nested_tag_fields = { 'id': fields.Integer(), 'title': fields.String() } post_fields = { # 字符串 field,从对象的 .user.username 中取出值作为值 'author': fields.String(attribute=lambda x: x.user.username), 'title': fields.String(), 'text': HTMLField(), # 自定义 field # list field,转换后成为 JSON list # 内层为一个特殊的 field,是另外一个 JSON 对象,它是用另一个字段对象构成的 'tags': fields.List(fields.Nested(nested_tag_fields)), 'publish_time': fields.DateTime(dt_format='iso8601') } class PostApi(Resource): @marshal_with(post_fields) # 对象的属性会根据这个 dict 来转换 def get(self, post_id=None): if post_id: post = Post.query.get(post_id) if not post: abort(404) return post # 返回的 post 的内容已经被转换格式了 ...
接收请求参数
定义 parser
post_get_parser = reqparse.RequestParser() # 初始化 parser
post_get_parser.add_argument( # 在 parser 中添加参数以及相应规则
'page',
type=int,
location=['args', 'header'],
required=False
)
post_get_parser.add_argument(
'user',
type=str,
location=['json', 'args', 'headers']
)
使用 parser
from .parsers import post_get_parser
class PostApi(Resource):
def get(self, post_id=None):
...
# 使用 parser 解析参数
# 解析出来的可以像 dict 引用
# 例如 args['user']
args = post_get_parser.parse_args()
- 身份验证
使用 access token 来进行身份认证,要完成身份认证要先通过一个 API 来进行登录验证,如果通过就返回 token,之后就根据 token 来进行相应的权限操作。
# parser 也是要定义的
class AuthApi(Resource):
def post(self):
args = user_post_parser.parse_args()
user = User.query.filter_by(
username=args['username']
).one()
# 检验密码
if user.verify_password(args['password']):
# 通过了才生成令牌
s = Serializer(
current_app.config['SECRET_KEY'],
expires_in=600
)
# 返回令牌,注意 dumps 出来的数据是 byte 类型的,要根据编码改成 utf-8 才能成为 JSON 内容
return {"token": s.dumps({'id': user.id}).decode('utf-8')}
else:
abort(401)
验证令牌的话在 User 类中添加一个类方法
class User(db.Model, UserMixin):
...
@staticmethod
def verify_auth_token(token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
data = s.loads(token)
except SignatureExpired:
return None
except BadSignature:
return None
user = User.query.get(data['id'])
return user
要使用的话就在资源类的方法中调用这个类方法来返回 user 对象。