用 Flask 来写个轻博客 (35) — 使用 Flask-RESTful 来构建 RESTful API 之四

简介: 目录目录前文列表POST 请求身份认证测试前文列表用 Flask 来写个轻博客 (1) — 创建项目 用 Flask 来写个轻博客 (2) — Hello World! 用 Flask 来写个轻博客 (3) — (M)VC_连接 MySQ...

目录

前文列表

用 Flask 来写个轻博客 (1) — 创建项目
用 Flask 来写个轻博客 (2) — Hello World!
用 Flask 来写个轻博客 (3) — (M)VC_连接 MySQL 和 SQLAlchemy
用 Flask 来写个轻博客 (4) — (M)VC_创建数据模型和表
用 Flask 来写个轻博客 (5) — (M)VC_SQLAlchemy 的 CRUD 详解
用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)
用 Flask 来写个轻博客 (7) — (M)VC_models 的关系(many to many)
用 Flask 来写个轻博客 (8) — (M)VC_Alembic 管理数据库结构的升级和降级
用 Flask 来写个轻博客 (9) — M(V)C_Jinja 语法基础快速概览
用 Flask 来写个轻博客 (10) — M(V)C_Jinja 常用过滤器与 Flask 特殊变量及方法
用 Flask 来写个轻博客 (11) — M(V)C_创建视图函数
用 Flask 来写个轻博客 (12) — M(V)C_编写和继承 Jinja 模板
用 Flask 来写个轻博客 (13) — M(V)C_WTForms 服务端表单检验
用 Flask 来写个轻博客 (14) — M(V)C_实现项目首页的模板
用 Flask 来写个轻博客 (15) — M(V)C_实现博文页面评论表单
用 Flask 来写个轻博客 (16) — MV(C)_Flask Blueprint 蓝图
用 Flask 来写个轻博客 (17) — MV(C)_应用蓝图来重构项目
用 Flask 来写个轻博客 (18) — 使用工厂模式来生成应用对象
用 Flask 来写个轻博客 (19) — 以 Bcrypt 密文存储账户信息与实现用户登陆表单
用 Flask 来写个轻博客 (20) — 实现注册表单与应用 reCAPTCHA 来实现验证码
用 Flask 来写个轻博客 (21) — 结合 reCAPTCHA 验证码实现用户注册与登录
用 Flask 来写个轻博客 (22) — 实现博客文章的添加和编辑页面
用 Flask 来写个轻博客 (23) — 应用 OAuth 来实现 Facebook 第三方登录
用 Flask 来写个轻博客 (24) — 使用 Flask-Login 来保护应用安全
用 Flask 来写个轻博客 (25) — 使用 Flask-Principal 实现角色权限功能
用 Flask 来写个轻博客 (26) — 使用 Flask-Celery-Helper 实现异步任务
用 Flask 来写个轻博客 (27) — 使用 Flask-Cache 实现网页缓存加速
用 Flask 来写个轻博客 (29) — 使用 Flask-Admin 实现后台管理 SQLAlchemy
用 Flask 来写个轻博客 (30) — 使用 Flask-Admin 增强文章管理功能
用 Flask 来写个轻博客 (31) — 使用 Flask-Admin 实现 FileSystem 管理
用 Flask 来写个轻博客 (32) — 使用 Flask-RESTful 来构建 RESTful API 之一
用 Flask 来写个轻博客 (33) — 使用 Flask-RESTful 来构建 RESTful API 之二
用 Flask 来写个轻博客 (34) — 使用 Flask-RESTful 来构建 RESTful API 之三

POST 请求

前三篇博文介绍了如果实现 GET 请求, 接下来继续实现 POST 创建数据请求.

  • 执行 Create 操作, 就肯定少不了要向服务端传入数据. 所以第一步当然就是定义解析器了.
    vim jmilkfansblog/controllers/flask_restful/parsers.py
from flask.ext.restful import reqparse

post_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 参数是为了后期的身份认证做准备

  • 在资源来 PostApi 中实现 post()方法
    vim jmilkfansblog/controllers/flask_restful/posts.py
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. 身份认证在整个项目中也应当作为一种资源来定义.

  • 首先还是定义 auth 解析器来接受用户信息
    vim jmilkfansblog/controllers/flask_restful/parsers.py
########################################################
# 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 的解析器只需要用户名和密码两个参数.

  • 创建认证资源 auth 的资源类
    vim jmilkfansblog/controllers/flask_restful/auth.py
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

from flask import abort, current_app
from flask.ext.restful import Resource

from jmilkfansblog.controllers.flask_restful import parsers
from jmilkfansblog.db.sqlalchemy.models import User


class 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

  • 定义资源 auth 的路由
    vim jmilkfansblog/__init__.py
def create_app(object_name):
    ...
    restful_api.add_resource(
        AuthApi,
        '/api/auth',
        endpoint='restful_api_auth')
    restful_api.init_app(app)
  • 为 User 对象实现 Token 验证方法
    需要注意的是: 这个 Token 应该是有时限的, 如果永久生效则会非常危险. 所以我们还需要对有时限的 Token 进行校验, 确定其没有失效.
    vim jmilkfansblog/models.py
...

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)
        ...

测试

  • 不加 Token 的 POST 请求
(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."
    }
}
  • 创建 Token
(env) jmilkfan@JmilkFan-Devstack:/opt/JmilkFan-s-Blog$ curl -d "username=<username>" -d "password=<password>" http://localhost:8089/api/auth{
    "token": "eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ4MzM1MjkwNSwiaWF0IjoxNDgzMzUyMzA1fQ.eyJpZCI6IjY1Y2I5NzkyLWI4NzYtNDllNy1iMmM1LTQ2NDY4NjI0MTk5ZSJ9.hYpczUEZUalgzutyyIViheBd_jnnCmegvp4sazHIEoA"
}
  • 加 Token 的 POST 请求
(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 | <p>22222</p>
  | 2016-12-17 22:39:16 | 65cb9792-b876-49e7-b2c5-46468624199e |
| 29bab6a0-6a0f-48f1-a088-6c271cebe906 | 222222    | <p>222222</p>
 | 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 |
+--------------------------------------+-----------+-----------------+---------------------+--------------------------------------+
相关文章
|
6月前
|
XML JSON API
识别这些API接口定义(http,https,api,RPC,webservice,Restful api ,OpenAPI)
本内容介绍了API相关的术语分类,包括传输协议(HTTP/HTTPS)、接口风格(RESTful、WebService、RPC)及开放程度(API、OpenAPI),帮助理解各类API的特点与应用场景。
|
8月前
|
缓存 安全 API
RESTful与GraphQL:电商API接口设计的技术细节与适用场景
本文对比了RESTful与GraphQL这两种主流电商API接口设计方案。RESTful通过资源与HTTP方法定义操作,简单直观但可能引发过度或欠获取数据问题;GraphQL允许客户端精确指定所需字段,提高灵活性和传输效率,但面临深度查询攻击等安全挑战。从性能、灵活性、安全性及适用场景多维度分析,RESTful适合资源导向场景,GraphQL则适用于复杂数据需求。实际开发中需根据业务特点选择合适方案,或结合两者优势,以优化用户体验与系统性能。
|
8月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
7月前
|
缓存 边缘计算 前端开发
从业务需求到技术栈:电商API选型RESTful还是GraphQL?这5个维度帮你决策
在数字经济时代,电商平台的竞争已延伸至用户体验与系统效能。作为连接前后端及各类服务的核心,API接口的架构设计至关重要。本文对比RESTful与GraphQL两大主流方案,从电商场景出发,分析两者的技术特性、适用场景与选型逻辑,帮助开发者根据业务需求做出最优选择。
|
11月前
|
XML JSON API
Understanding RESTful API and Web Services: Key Differences and Use Cases
在现代软件开发中,RESTful API和Web服务均用于实现系统间通信,但各有特点。RESTful API遵循REST原则,主要使用HTTP/HTTPS协议,数据格式多为JSON或XML,适用于无状态通信;而Web服务包括SOAP和REST,常用于基于网络的API,采用标准化方法如WSDL或OpenAPI。理解两者区别有助于选择适合应用需求的解决方案,构建高效、可扩展的应用程序。
|
11月前
|
机器学习/深度学习 设计模式 API
Python 高级编程与实战:构建 RESTful API
本文深入探讨了使用 Python 构建 RESTful API 的方法,涵盖 Flask、Django REST Framework 和 FastAPI 三个主流框架。通过实战项目示例,详细讲解了如何处理 GET、POST 请求,并返回相应数据。学习这些技术将帮助你掌握构建高效、可靠的 Web API。
|
JSON 缓存 JavaScript
深入浅出:使用Node.js构建RESTful API
在这个数字时代,API已成为软件开发的基石之一。本文旨在引导初学者通过Node.js和Express框架快速搭建一个功能完备的RESTful API。我们将从零开始,逐步深入,不仅涉及代码编写,还包括设计原则、最佳实践及调试技巧。无论你是初探后端开发,还是希望扩展你的技术栈,这篇文章都将是你的理想指南。
|
JSON JavaScript 前端开发
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将引导您步入Node.js的奇妙世界,通过实践操作,掌握如何使用这一强大的JavaScript运行时环境构建高效、可扩展的RESTful API。我们将一同探索Express框架的使用,学习如何设计API端点,处理数据请求,并实现身份验证机制,最终部署我们的成果到云服务器上。无论您是初学者还是有一定基础的开发者,这篇文章都将为您打开一扇通往后端开发深层知识的大门。
337 12
|
XML JSON 缓存
深入理解RESTful API设计原则与实践
在现代软件开发中,构建高效、可扩展的应用程序接口(API)是至关重要的。本文旨在探讨RESTful API的核心设计理念,包括其基于HTTP协议的特性,以及如何在实际应用中遵循这些原则来优化API设计。我们将通过具体示例和最佳实践,展示如何创建易于理解、维护且性能优良的RESTful服务,从而提升前后端分离架构下的开发效率和用户体验。
|
监控 安全 API
深入浅出:构建高效RESTful API的最佳实践
在数字化时代,API已成为连接不同软件和服务的桥梁。本文将带你深入了解如何设计和维护一个高效、可扩展且安全的RESTful API。我们将从基础概念出发,逐步深入到高级技巧,让你能够掌握创建优质API的关键要素。无论你是初学者还是有经验的开发者,这篇文章都将为你提供实用的指导和启示。让我们一起探索API设计的奥秘,打造出色的后端服务吧!