Restful API:互联网软件架构的设计风格
Restful API 是一种针对网络应用程序的设计风格和开发方式,它并非一个强制的标准,而是一套设计原则和规范。 当一个架构符合 REST 的约束条件和原则时,我们称之为 RESTful 架构。 这种架构风格旨在简化不同软件/程序在网络(例如互联网)中的信息传递。 目前,它已成为 Web 应用程序最流行的 API 设计风格之一。
1. API (Application Programming Interface) - 应用程序编程接口
API,即应用程序编程接口,它定义了不同软件系统之间进行通信必须遵循的规则。 开发者通过创建 API,使得他们的应用程序可以与其他应用程序以编程方式进行通信。 我们可以将 Web API 想象成客户端和网络资源之间的一扇大门。 例如,一个天气预报网站可以通过 API 将天气数据提供给其他程序(如手机上的天气应用)使用。
2. Rest (Representational State Transfer) - 表现层状态转化
REST 的全称是 Representational State Transfer,中文通常翻译为"表现层状态转化"或"表述性状态转移"。 这个术语由 Roy Fielding 在 2000 年的博士论文中首次提出,他也是 HTTP 协议的主要编写者之一。 REST 隐藏的理念是更好地利用 Web 现有的特征和能力,遵循现有 Web 标准中的准则和约束。
要深入理解 REST,我们需要拆解它的三个核心概念:资源 (Resource) 、表现层 (Representational) 和 状态转化 (State Transfer)。
1. Resource - 资源
在 REST 架构中,所有的一切都被视为"资源"。 资源可以是任何有价值的信息或实体,例如一个文档、一张图片、一段视频,或者一个用户对象等。
-
URI (Uniform Resource Identifier) - 统一资源标识符: 每个资源在网络上都有一个唯一的标识符,这个标识符就是 URI。 URI 是一个用于标识某一互联网资源名称的字符串。 它的主要目的是唯一地标识一个资源,但不一定需要指明如何定位或获取这个资源。
-
URL (Uniform Resource Locator) - 统一资源定位符: URL 是 URI 的一个子集,它不仅标识了一个资源,还提供了找到该资源的方法(即定位)。 一个典型的 URL 包含协议(如 HTTP)、服务器地址和资源路径。 在 REST 服务中,服务器通常使用 URL 来识别资源。
URI 与 URL 的关系可以理解为: 所有的 URL 都是 URI,但并非所有的 URI 都是 URL。 简单来说,URI 像是给资源起了一个唯一的名字,而 URL 则像是给出了这个资源的详细地址。
设计原则: 在 RESTful API 的设计中,URI 应该只表示资源的名词,而不应包含动词。 并且,推荐使用复数形式来表示资源的集合。
- 推荐的 URI 示例:
/users
,/users/123
- 不推荐的 URI 示例:
/getAllUsers
,/createUser
2. Representational - 表现层
"表现层"指的是资源呈现给客户端的具体形式。 资源本身和它在客户端与服务器之间传输的表示是相互独立的。 例如,服务器内部可能以数据库记录的形式存储用户信息,但在响应客户端请求时,可以将其表现为 JSON、XML 或 HTML 等格式。
客户端和服务器通过 HTTP 的 Content-Type
和 Accept
头字段来进行内容协商,以确定使用哪种数据格式进行通信。
Content-Type
:定义了请求发送的数据格式。Accept
:定义了客户端期望接收的响应格式列表。
目前,JSON 是 RESTful API 中最常用的数据交换格式。
3. State Transfer - 状态转化
"状态转化"指的是客户端通过对资源执行操作,来改变资源的状态。 在 REST 中,这种操作是通过标准的 HTTP 方法(也称为 HTTP 动词)来实现的。
核心原则:无状态 (Stateless) 这是 REST 架构的一个核心原则。 无状态意味着服务器不会保存客户端的会话状态信息。 从客户端到服务器的每个请求都必须包含理解和处理该请求所需的所有信息。 这样做的好处是可以提高系统的可伸缩性和可靠性,因为任何服务器都可以处理任何请求。
-
REST 的核心是:通过 HTTP 动词(方法)对资源状态进行转移。
-
常用 HTTP 方法及语义:
GET
------ 获取资源POST
------ 新建资源PUT
------ 更新资源(整体更新)PATCH
------ 局部更新资源DELETE
------ 删除资源
例如:
GET /users
→ 获取用户列表POST /users
→ 创建新用户GET /users/123
→ 获取 id=123 的用户PUT /users/123
→ 更新 id=123 的用户(整体覆盖更新)DELETE /users/123
→ 删除 id=123 的用户
通过将这些动词与代表资源的 URI 结合,客户端就可以对服务器上的资源进行操作,从而实现"状态转化"。例如,向 /users
这个 URI 发送一个 POST 请求,就代表着要创建一个新用户,这就会导致服务器上资源状态的改变。 好的,我们来对这份详细的 Restful API 设计规范进行扩充和深化,使其内容更丰富、更具实践指导意义。
Restful API 具体设计规范
一份良好设计的 Restful API 能够让开发者轻松理解和使用,以下是各项规范的详细说明。
1、协议 (Protocol)
始终使用 HTTPS 协议。
安全性是 API 设计的基石。HTTPS 协议通过 SSL/TLS 对 HTTP 通信进行加密,能有效防止数据在传输过程中被窃取或篡改,这对于保护用户凭证、API 密钥、令牌以及其他敏感数据至关重要。 在生产环境中,应强制所有 API 请求都通过 HTTPS 进行,并将来自 HTTP 的请求重定向到 HTTPS。
2、域名 (Domain)
API 的入口点应该清晰易辨。通常有两种主流的做法:
-
使用子域名 (Subdomain):
https://api.kaivon.com
- 优点: 这是更清晰和专业的做法。它将 API 服务与主网站服务在逻辑上完全分开,便于进行独立的部署、扩展和安全管理。
- 缺点: 可能需要额外的 DNS 配置和 SSL 证书管理。
-
使用路径 (Path):
https://www.kaivon.com/api/
- 优点: 配置简单,不需要额外的 DNS 设置。
- 缺点: API 和主应用部署在一起,耦合度较高,不利于独立扩展。
推荐做法: 对于正式的、对外提供服务的 API,优先选择子域名的方式。
3、版本 (Versioning)
当 API 需要进行不兼容的重大更新时,版本控制是必不可少的,它可以确保旧版本的客户端应用不会因为 API 的变更而中断服务。 有多种流行的版本控制方法:
-
在 URI 路径中加入版本号 (推荐):
https://api.kaivon.com/v1/
- 优点: 非常直观和明确,开发者在浏览器或日志中一眼就能看出使用的是哪个版本的 API。 这是最常见和易于理解的方式。
- 缺点: 有人认为这不够"纯粹",因为它改变了资源的 URI。
-
通过查询参数 (Query Parameter):
https://api.kaivon.com/blogs?version=1
- 优点: 实现简单。
- 缺点: 版本信息不够突出,容易被忽略,且可能使 URL 变得混乱。
-
通过自定义请求头 (Custom Header): 在 HTTP Header 中添加如
Accept-Version: v1
这样的字段。- 优点: 保持 URI 的纯净,不包含版本信息。
- 缺点: 对客户端开发者不够直观,需要通过工具才能查看和设置请求头。
最佳实践: 将版本号放在 URI 路径中是最清晰、最被广泛接受的做法。
4、路径 (Path) / 端点 (Endpoint)
路径应该用来表示资源 (Resource) ,并且总是使用名词而不是动词。
- 使用复数名词: 建议统一使用复数形式来表示资源集合,这样能保持 API 的一致性。 例如,使用
/blogs
而不是/blog
。 - 示例:
- 获取所有文章:
https://api.kaivon.com/v1/blogs
- 获取 ID 为
123
的特定文章:https://api.kaivon.com/v1/blogs/123
- 获取所有文章:
- 关联资源: 对于有关联关系的资源,可以通过层级路径来表示。例如,获取某篇文章下的所有评论:
GET /blogs/123/comments
5、HTTP 方法 (HTTP Methods)
使用标准的 HTTP 方法来表示对资源执行的操作 (Action)。
- GET (SELECT): 获取资源
GET /blogs
: 获取所有文章的列表。GET /blogs/123
: 获取 ID 为123
的单篇文章。
- POST (CREATE): 新增资源
POST /blogs
: 创建一篇新文章。请求体 (body) 中包含新文章的数据。
- PUT (UPDATE): 完整更新资源
PUT /blogs/123
: 完整替换 ID 为123
的文章。请求体需要包含该文章的所有属性。
- PATCH (UPDATE): 部分更新资源
PATCH /blogs/123
: 只更新 ID 为123
的文章的部分属性。例如,只修改文章的标题。请求体只需包含要更改的字段。
- DELETE (DELETE): 删除资源
DELETE /blogs/123
: 删除 ID 为123
的文章。
6、数据过滤、排序和分页 (Filtering, Sorting & Pagination)
对于返回资源集合的请求 (如 GET /blogs
),应该提供机制来筛选、排序和分页,以避免一次性返回大量数据,造成服务器和客户端的压力。 这些操作通过查询参数 (Query Parameters) 实现。
-
过滤 (Filtering): 允许客户端根据字段值筛选结果。
GET /blogs?state=published
: 获取所有状态为"已发布"的文章。GET /blogs?author_id=5
: 获取 ID 为 5 的作者的所有文章。
-
排序 (Sorting): 允许客户端指定返回结果的排序方式。
GET /blogs?sort=-published_at
: 按发布时间降序排列 (-
通常表示降序)。GET /blogs?sort=title
: 按标题升序排列。
-
分页 (Pagination): 当数据量很大时,分批次返回数据。
- 基于偏移量 (Offset-based):
?limit=10&offset=20
: 返回从第 21 条记录开始的 10 条数据。这是最简单的方式。
- 基于页码 (Page-based):
?page=3&per_page=10
: 返回第 3 页的数据,每页 10 条。对用户更友好。
- 基于游标 (Cursor-based):
?limit=10&after_cursor=xxxxx
: 返回指定游标之后的数据。这种方式在频繁更新的数据集上性能更稳定。
- 基于偏移量 (Offset-based):
综合示例: GET /blogs?state=published&sort=-published_at&page=2&per_page=10
这个请求的含义是:获取已发布文章的列表,按发布时间倒序排列,并返回第二页的结果,每页显示 10 篇。
7、状态码 (Status Codes)
HTTP 状态码清晰地表明了请求的结果,客户端需要根据状态码来判断如何处理响应。
-
2xx: 成功 (Success)
200 OK
: 请求成功。适用于 GET、PUT、PATCH 请求。201 Created
: 资源创建成功。适用于 POST 请求。204 No Content
: 请求成功,但响应体中没有内容。适用于 DELETE 请求。
-
4xx: 客户端错误 (Client Error)
400 Bad Request
: 请求无效,例如请求参数格式错误或缺失。401 Unauthorized
: 未经授权,需要用户进行身份验证。403 Forbidden
: 服务器理解请求,但拒绝执行。用户可能没有访问该资源的权限。404 Not Found
: 请求的资源不存在。
-
5xx: 服务器错误 (Server Error)
500 Internal Server Error
: 服务器内部发生了未知错误。
8、返回结果 (Response Body)
-
统一数据结构: 建议所有响应都遵循一个统一的结构,将实际数据包装在一个
data
字段中。这为将来添加元数据(如分页信息)提供了便利。json{ "data": { "id": 123, "title": "My First Blog", "content": "..." } }
-
根据方法返回不同内容:
- GET: 对于集合,
data
字段是一个数组;对于单个资源,data
是一个对象。 - POST: 返回
201 Created
状态码,并在data
字段中包含新创建的资源对象。 - PUT / PATCH: 返回
200 OK
状态码,并在data
字段中包含更新后的完整资源对象。 - DELETE: 返回
204 No Content
状态码,响应体为空。
- GET: 对于集合,
-
错误信息: 对于失败的请求 (4xx, 5xx),响应体应包含清晰的错误信息。
json{ "error": { "code": "INVALID_PARAMETER", "message": "The 'title' field is required." } }
9、返回的数据格式 (Data Format)
始终使用 JSON (JavaScript Object Notation)。
JSON 是目前 Web API 的事实标准。它比 XML 更轻量、更易于人类阅读,并且能被几乎所有现代编程语言轻松解析。
总结
一个设计良好的 RESTful API 应该遵循以下三个核心原则:
- 看 URL 就知道要操作什么资源 (What): 路径清晰地指向一个或一组名词资源。
- 看 HTTP Method 就知道要干什么 (How): HTTP 动词明确地定义了对资源的操作。
- 看 HTTP Status Code 就知道结果如何 (Result): 状态码准确地反馈了请求的最终状态。