REST 是 Representational State Transfer 的缩写,意为「表述性状态传递」。近些年来 REST 似乎已经变成了大部分 Web API 的设计风格。在本文中我们将简单地使用 Python 和 Flask 框架的 RESTful 扩展来搭建一个基本的、可拓展的 RESTful API 架构。
让我们先来了解一下 REST
REST 是一种软件架构设计风格,可以降低开发的复杂性,提高系统的可伸缩性,如果一个架构符合 REST 风格,就称它为 RESTful 架构。REST 风格的最初目的是适应万维网的 HTTP 协议,所以 RESTful 架构可以充分的利用 HTTP 协议的各种功能,是 HTTP 协议的最佳实践。下面列出了 REST 风格的主要特征:
- Client-Server:客户端与服务器分离,有助于提高用户界面的跨平台可移植性,也有助于提高服务器模块的可扩展性。
- Stateless 无状态:服务器不会存储客户端上一次请求的信息用来给下一次使用,所从客户端的每个请求都必须包含服务器理解请求所必需的所有信息。
- Uniform Interface 统一接口:服务器和客户端的通信方法及接口必须是统一的,以资源为基础。这简化了系统架构,减少了资源之间的耦合性,可以让各个模块各自独立的进行改进。
- Cacheability 可缓存:管理良好的缓存机制可以减少客户端与服务器之间的交互,甚至完全避免客户端与服务器之间的交互,这进一步提了高性能和可扩展性。
- Layered System 分层系统:客户端一般不知道自己连接到了什么层级的服务器,或许是中间服务器。这样通过负载均衡和共享缓存的机制可以提高系统的可扩展性,也便于安全策略的部署。
- Code on Demand 按需编码:服务器可以将能力扩展到客户端,比如通过发送可执行代码给客户端的方式临时性地扩展功能或者定制功能。这个功能不是必须的。
RESTful API 的核心概念就是「资源」,其可以用 URI 来表示。客户端使用 HTTP 协议定义的方法来发送请求到这些 URIs,这些方法可以完美地实现「资源」的新建、读取、更新和删除。HTTP 的标准方法如下:
方法 | 行为 |
---|---|
GET | 读取(Read)。 |
POST | 新建(Create)。 |
PUT | 更新(Update)。 |
PATCH | 更新(Update),通常是部分更新。 |
DELETE | 删除(Delete)。 |
除了客户端使用 HTTP 的标准方法进行请求,服务端也使用 HTTP 的状态码来回应。五大类状态码覆盖了绝大部分可能遇到的情况(不过 API 通常用不到 1xx 状态码),每一种状态码都有约定的解释,客户端只需查看状态码,就可以判断出发生了什么情况。
REST 风格没有特定的数据格式,但是我们通常使用 JSON 作为数据交换的格式。那么既然我们已经了解了一个 RESTful API 应该如何请求和回应,就让我们以一个 Task List 为例,设计这个「资源」的 RESTful API 操作:
URI | 方法 | 作用 |
---|---|---|
/tasks | GET | 获取完整的 Task List 内容。 |
/tasks | POST | 新建一个 Task 。 |
/task/:id | GET | 获取指定 Task 的内容。 |
/task/:id | PUT | 更新指定 Task 的内容。 |
/task/:id | DELETE | 删除指定 Task 。 |
如果你还想了解更多和 RESTful API 设计有关的内容,可以在这个网站中了解到更多详情,包括请求以及响应的具体设计,和无状态设计下的认证解决方案: RESTful API : 一种流行的 API 设计风格。
安装 Flask 以及 RESTful 拓展
首先,理所当然你需要安装 Python(本文使用 Python 3.9.12,各小版本之间区别不大)。如此,你可以直接用 pip 安装所需要的各种依赖(本文使用 Flask 2.1.2 和 Flask-RESTful 0.3.9):
pip install flask-restful
若你的开发环境与 pip 默认源的网络连接较为缓慢,这里建议使用 清华大学 TUNA 协会提供的镜像站。使用如下命令修改 pip 的下载源:
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
下面的代码即为一个最小的 Flask RESTful API,你可以试着运行一下来确认所有需要的依赖都已经正确安装:
from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def get(self):
return {'message': 'Hello, world!'}
api.add_resource(HelloWorld, '/')
if __name__ == '__main__':
app.run(debug=True)
如果一切正常,你可以在控制台看到类似下面的输出,现在你可以启动你的网页浏览器,输入 http://localhost:5000 看看这个 Flask RESTful API 的效果。
$ python flask_min.py
* Serving Flask app 'flask_min' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 496-573-979
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
RESTful API 项目的文件组织
有许多不同的方式来组织一个 Flask RESTful 应用程序的文件结构,这里我们选择一个易于扩展的文件组织方式,其最基本的思路就是把整个程序分为三个主要部分:路由、资源、以及公共的基础部分。下面是这种目录结构的一个例子:
app/
__init__.py
api.py # Flask 实列以及路由等
resources/ # 各个资源对象
__init__.py
hello.py
common/ # 公共的基础部分
__init__.py
utils.py
在 common 文件夹中,可能只包含各种辅助函数,用来满足你的应用程序的公共需求。而在 resource 文件夹中,只有你的各种资源对象。这里是 hello.py 可能的样子:
from flask_restful import Resource
class Home(Resource):
def get(self):
return {'message': 'Hello, world!'}
那么与之对应的 api.py 就可能就像是这样:
from flask import Flask
from flask_restful import Api
import app.resources as Resources
app = Flask(__name__)
api = Api(app)
api.add_resource(Resources.Home, '/')
因为你可能编写一个特别大型或者复杂的 API,这个文件会是一个包含所有路由以及资源的复杂列表,你也可以使用这个文件来设置任何的配置值(before_request,after_request)。基本上,这个文件其实不止用来路由,而是配置你的整个 API。
编写一个 RESTful API 资源
接下来我们着手实现文章开头设计的 Task List ,以此为例展示如何编写 RESTful API 资源。首先显然我们需要一种方式来存储任务,最直接的方式就是建立一个小型的数据库,但是数据库并不是本文的主体,所以这里我们直接把任务列表存储在内存中,在以后的文章我们会讲到在 Flask 使用数据库。
那么我们在 resources 下新建一个资源,它的内容如下:
from flask_restful import Resource
tasks = [
{
'id': 1,
'title': 'Learn Python',
'description': 'Learning basic Python knowledge.',
'done': True
}, {
'id': 2,
'title': 'Learn Flask-RESTful',
'description': 'Learning how to write RESTful API with Flask.',
'done': False
}
]
class Tasks(Resource):
def get(self):
return tasks
然后我们将这个资源添加到路由中去,并且启动服务器,你或许需要这样的代码:
from app.api import app
if __name__ == '__main__':
app.run(debug=True)
接下来是测试我们写好的内容。在这里我们使用 Python 的 requests 来发起对 RESTful API 的请求:
>>> from requests import *
>>> get('http://127.0.0.1:5000/tasks').json()
[{'id': 1, 'title': 'Learn Python', 'description': 'Learning basic Python knowledge.', 'done': True}, {'id': 2, 'title': 'Learn Flask-RESTful', 'description': 'Learning how to write RESTful API with Flask.', 'done': False}]
现在,我们已经成功地实现了我们的 RESTful API 的一个函数——获取全部任务,现在我们开始编写对于单独一个任务的资源对象及其操作,那么就需要从 URI 中获取参数,具体路由以及代码如下:
# 路由
api.add_resource(Resources.Task, '/task/<int:task_id>')
#资源
task_example = {'id': None, 'title': None, 'description': None, 'done': None}
class Tasks(Resource):
def get(self):
return tasks
def post(self):
if not request.json and not 'title' in request.json:
return {'error': 'Invalid request'}, 400
task = task_example.copy()
task['id'] = len(tasks) + 1
task['title'] = request.json['title']
task['description'] = request.json.get('description', task['description'])
task['done'] = False
tasks.append(task)
return task, 201
class Task(Resource):
def get(self, task_id):
task = list(filter(lambda t: t['id'] == task_id, tasks))
if len(task) == 0:
return task_example, 404
return task[0]
def put(self, task_id):
if not request.json:
return {'error': 'Invalid request'}, 400
task = list(filter(lambda t: t['id'] == task_id, tasks))
if len(task) == 0:
return task_example, 404
task[0]['title'] = request.json.get('title', task[0]['title'])
task[0]['description'] = request.json.get('description', task[0]['description'])
task[0]['done'] = request.json.get('done', task[0]['done'])
return task[0], 201
def delete(self, task_id):
task = list(filter(lambda t: t['id'] == task_id, tasks))
if len(task) == 0:
return '', 404
tasks.remove(task[0])
return '', 204
接下来我们启动服务器,用 requests 来测试一下新增加的内容:
>>> post('http://127.0.0.1:5000/tasks', data = json.dumps({'title': 'New task'}), headers = {'content-type': "application/json"}).json()
{'id': 3, 'title': 'New task', 'description': None, 'done': False}
>>> put('http://127.0.0.1:5000/task/3', data = json.dumps({'done': True}), headers = {'content-type': "application/json"}).json()
{'id': 3, 'title': 'New task', 'description': None, 'done': True}
>>> delete('http://127.0.0.1:5000/task/3')
<Response [204]>
至此,我们已经成功实现了对于一个资源的常用操作。
尾声
本文主要介绍了 REST 设计风格以及 Python Flask RESTful 框架的简单使用,事实上我们在真正编写一个 API Service 时还会有许多需求,比如:权限认证、调用数据库、自定义错误处理。我们或许在日后的文章中会讲到这些内容。
参考资料
表现层状态转换 - 维基百科,自由的百科全书
RESTful API 一种流行的 API 设计风格
Designing a RESTful API with Python and Flask
flask-restful.readthedocs.io/en/latest/