本文共 7810 字,大约阅读时间需要 26 分钟。
前三篇博文介绍了如果实现 GET 请求, 接下来继续实现 POST 创建数据请求.
from flask.ext.restful import reqparsepost_post_parser = reqparse.RequestParser()post_post_parser.add_argument( 'title', type=str, required=True, help='Title is required!')post_post_parser.add_argument( 'text', type=str, required=True, help='Text is required!')post_post_parser.add_argument( 'tags', type=str, action='append')post_post_parser.add_argument( 'token', type=str, required=True, help='Auth Token is required to create posts.')
NOTE 1: add_argument 的关键字参数 action='append'
指定了传入的参数会转换为以字典为元素的列表数据类型. 这是为了便于创建 post.tags 对象.
NOTE 2: 定义 token 参数是为了后期的身份认证做准备
class PostApi(Resource): """Restful API of posts resource.""" ... def post(self, post_id=None): """Can be execute when receive HTTP Method `POST`. """ if post_id: abort(400) else: args = parsers.post_post_parser.parse_args(strict=True) new_post = Post() new_post.title = args['title'] new_post.date = datetime.datetime.now() new_post.text = args['text'] new_post.user = user if args['tags']: for item in args['tags']: tag = Tag.query.filter_by(name=item).first() # If the tag already exist, append. if tag: new_post.tags.append(tag) # If the tag not exist, create the new one. # Will be write into DB with session do. else: new_tag = Tag() new_tag.name = item new_post.tags.append(new_tag) db.session.add(new_post) db.session.commit() return (new_post.id, 201)
NOTE 1: post() 返回了一个 Tuple 类型对象, 第二个元素会作为 Response Hander 中的 HTTP status_int 状态码.
需要注意的是, 对外开发的 RESTful API 一定要非常注重安全, 所有从外部对数据库的写入操作请求都必须进行身份认证.
身份认证的功能我们仍然可以由 Flask-Login 来支持, 但很明显的, 这并不符合 REST 的无状态约束. 所以我们在这里引入 Token 的概念, 外部请求如果希望通过 RESTful API 执行写数据库操作时, 必须携带用户登录信息, 通过身份认证之后, 再由服务端发放一段时间内有效的 Token. 身份认证在整个项目中也应当作为一种资源来定义.
######################################################### User's HTTP Request Parser########################################################user_post_parser = reqparse.RequestParser()user_post_parser.add_argument( 'username', type=str, required=True, help='Username is required!')user_post_parser.add_argument( 'password', type=str, required=True, help='Password is required!')
NOTE : auth 的解析器只需要用户名和密码两个参数.
from itsdangerous import TimedJSONWebSignatureSerializer as Serializerfrom flask import abort, current_appfrom flask.ext.restful import Resourcefrom jmilkfansblog.controllers.flask_restful import parsersfrom jmilkfansblog.db.sqlalchemy.models import Userclass AuthApi(Resource): """Restful api of Auth.""" def post(self): """Can be execute when receive HTTP Method `POST`.""" args = parsers.user_post_parser.parse_args() user = User.query.filter_by(username=args['username']).first() # Check the args['password'] whether as same as user.password. if user.check_password(args['password']): # serializer object will be saved the token period of time. serializer = Serializer( current_app.config['SECRET_KEY'], expires_in=600) return { 'token': serializer.dumps({ 'id': user.id})} else: abort(401)
NOTE 1: Token 使用 Python 内建的 itsdangerous 库来实现, itsdangerous.TimedJSONWebSignatureSerializer() 的第一个参数需要传入 app 对象的私钥, 该私钥在之前的 Flask-WTForm 已经定义在 config.py 中了. 第二个参数定义了 Token 的有效时间.
NOTE 2: 我们使用 post() 方法来实现用户身份验证和发放 Token
def create_app(object_name): ... restful_api.add_resource( AuthApi, '/api/auth', endpoint='restful_api_auth') restful_api.init_app(app)
...class User(db.Model): """Represents Proected users.""" ... @staticmethod @cache.memoize(60) def verify_auth_token(token): """Validate the token whether is night.""" serializer = Serializer( current_app.config['SECRET_KEY']) try: # serializer object already has tokens in itself and wait for # compare with token from HTTP Request /api/posts Method `POST`. data = serializer.loads(token) except SignatureExpired: return None except BadSignature: return None user = User.query.filter_by(id=data['id']).first() return user
NOTE: 使用 itsdangerous.TimedJSONWebSignatureSerializer.loads() 来进行 Token 验证, 如果验证失败的话, 我们直接返回 None
现在我们拥有了生成 Token 和验证 Token 的支撑, 最后我们在 PostApi.post() 中加入 Token 验证机制.
vim jmilkfansblog/controllers/flask_restful/posts.py
def post(self, post_id=None): """Can be execute when receive HTTP Method `POST`. """ if post_id: abort(400) else: args = parsers.post_post_parser.parse_args(strict=True) # Validate the user identity via token(/api/auth POST). # Will be create the post(/api/posts POST), if pass with validate token. user = User.verify_auth_token(args['token']) if not user: abort(401) ...
(env) jmilkfan@JmilkFan-Devstack:/opt/JmilkFan-s-Blog$ curl -d "title=Just Test POST" -d "text=Hello world" -d "tags=Python" http://localhost:8089/api/posts{ "message": { "token": "Auth Token is required to create posts." }}
(env) jmilkfan@JmilkFan-Devstack:/opt/JmilkFan-s-Blog$ curl -d "username=" -d "password= " http://localhost:8089/api/auth{ "token": "eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ4MzM1MjkwNSwiaWF0IjoxNDgzMzUyMzA1fQ.eyJpZCI6IjY1Y2I5NzkyLWI4NzYtNDllNy1iMmM1LTQ2NDY4NjI0MTk5ZSJ9.hYpczUEZUalgzutyyIViheBd_jnnCmegvp4sazHIEoA"}
(env) jmilkfan@JmilkFan-Devstack:/opt/JmilkFan-s-Blog$ curl -d "title=Just Test" -d "text=Hello" -d "tags=Python" -d "token=eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ4MzM1MjkwNSwiaWF0IjoxNDgzMzUyMzA1fQ.eyJpZCI6IjY1Y2I5NzkyLWI4NzYtNDllNy1iMmM1LTQ2NDY4NjI0MTk5ZSJ9.hYpczUEZUalgzutyyIViheBd_jnnCmegvp4sazHIEoA" http://localhost:8089/api/posts"1746b650-bcab-436b-82ab-7411e252b576"
数据库记录 :
mysql> select * from posts;+--------------------------------------+-----------+-----------------+---------------------+--------------------------------------+| id | title | text | publish_date | user_id |+--------------------------------------+-----------+-----------------+---------------------+--------------------------------------+| 1746b650-bcab-436b-82ab-7411e252b576 | Just Test | Hello | NULL | 65cb9792-b876-49e7-b2c5-46468624199e || 1af8f334-c9ac-4eba-bdca-4dda597aba70 | 333333333 |22222
| 2016-12-17 22:39:16 | 65cb9792-b876-49e7-b2c5-46468624199e || 29bab6a0-6a0f-48f1-a088-6c271cebe906 | 222222 |222222
| 2016-12-27 22:35:00 | 65cb9792-b876-49e7-b2c5-46468624199e || 9c25d00e-49a7-4369-ac83-c0aca046ba73 | Just Test | Hello | NULL | 65cb9792-b876-49e7-b2c5-46468624199e |+--------------------------------------+-----------+-----------------+---------------------+--------------------------------------+
转载地址:http://hfhpo.baihongyu.com/