
今天在X(Twitter)的时间线上,我看到一篇推文,原作者提到,他重做API设计时,将所有GET方法替换为POST方法,并称RestFul API的设计思想是“八股文”。
我个人感觉作者的这种表述有些极端,因为缺乏具体的业务场景描述,这种说法可能会对经验不足的初学者产生误导。
在这篇推文的回复中,不少人赞成去除GET方法,并列举了一些业务场景。我把这些论点大致做了总结:
提示 GET方法的局限性
参数传递限制: GET 方法在传递参数方面限制较大,所有参数必须包含在 URL 中。
无法携带请求体: GET 请求不能包含请求体(Request Body),数据只能通过 URL 参数传递。
URL 长度限制: URL 最大长度通常限制在 2000 字符左右,可能无法满足复杂查询需求。
敏感信息暴露: URL 中的参数可能会被记录在 Web 服务器日志中,存在泄露敏感信息的风险。
查询语法复杂: 随着项目复杂度的增加,GET 请求的查询参数可能会变得繁杂,难以管理。
尽管这些GET方法的局限性确实存在,但我个人倾向在设计RESTful API时,尽量遵循其设计原则会更有利于API的可维护性和可扩展性。
以下,我将从 RESTful API 的定义、核心原则以及常见问题入手,探讨是否应该仅使用 POST 方法。
什么是RESTful API?
要评估是否应该仅使用POST方法,首先需要先回到RESTful API的定义。
重要 RESTful API 是一种遵循 REST(Representational State Transfer,表述性状态转移)架构风格的应用程序编程接口(API)。它为 Web 应用程序提供了一种通过互联网相互通信的标准化方式。
REST 的核心思想和原则
REST 并非一项具体的技术标准,而是一系列设计原则和约束。主要包括:
客户端-服务器(Client-Server):
客户端和服务器的关注点分离。客户端负责用户界面,服务器负责数据存储和处理。这种分离提高了客户端的跨平台可移植性和服务器组件的可扩展性。
无状态(Stateless):
服务器不保存客户端的上下文信息。每个来自客户端的请求都必须包含服务器处理该请求所需的所有信息。这使得服务器可以独立处理每个请求,减轻了服务器负载,并提高了系统的可伸缩性和可靠性。
可缓存(Cacheable):
服务器的响应可以被客户端或中间层(如代理服务器)缓存。这有助于减少网络流量,提高性能和用户体验。
统一接口(Uniform Interface):
这是 REST 架构风格的核心特征,通过标准化的接口简化架构并解耦系统。统一接口包括以下子原则:
- 资源标识(Resource Identification): 每个资源都应该有一个唯一的标识符,通常是 URI (Uniform Resource Identifier)。这些资源应该使用名词而非动词。例如,使用
/users/123
而不是/getUser?id=123
。 - 通过表述对资源的操作(Manipulation of resources through representations): 客户端通过资源的表述(例如 JSON 或 XML 格式的数据)来操作资源。
- 自描述消息(Self-descriptive Messages): 每个消息(请求或响应)都应包含足够的信息,接收方无需参考外部文档即可理解它。
- 超媒体作为应用状态引擎(Hypermedia as the Engine of Application State - HATEOAS): 响应中应包含链接,以引导客户端发现其他可用的资源和操作。
分层系统(Layered System):
客户端无需知道是否直接连接到最终服务器,还是通过中间层(如负载均衡器)。这种分层设计提高了系统的灵活性和可扩展性。
RESTful API 的核心思想
RESTful API 遵循 REST 原则,核心在于利用 HTTP 方法表示对资源的操作。标准的 HTTP 方法与 CRUD(创建、读取、更新、删除)操作对应如下:
- GET: 用于读取资源。无论执行多少次,结果都应该是相同的(幂等性)。
- POST: 用于创建新资源。也可用于某些更新操作或执行特定动作。
- PUT: 用于完整更新现有资源。如果资源不存在,有时也可以用于创建资源(取决于具体实现)。
- DELETE: 用于删除资源。
- PATCH: 用于部分更新现有资源。
这些方法为资源的操作提供了清晰的语义,是 RESTful API 设计的基础。
RESTful API 实现中的常见问题
虽然以上的核心思想和设计原则都看起来很清晰明了,但在实际设计 RESTful API 时,开发者常常还是会偏离 REST 原则。以下是一些常见问题及改进建议。
资源标识包含动词
REST原则推荐使用名词标识资源,例如 /users
而不是 /get-users
。对于简单的 CRUD 操作,这种方式易于实现:
资源标识 | 操作 | 说明 |
---|---|---|
/users | GET | 获取所有用户 |
/users/{id} | GET | 获取指定用户 |
/users | POST | 创建用户 |
/users/{id} | PUT | 修改用户 |
/users/{id} | DELETE | 删除用户 |
然而,在处理复杂操作(如“激活用户”,可能包括发送邮件、初始化数据、记录日志等一系列复杂的业务逻辑)时,开发者可能倾向于使用动词。例如:
警告 “不推荐”
POST /users/{id}/activate
POST请求体中会包含用户激活时所需要的信息
这种设计直观,但在项目规模扩大时,动词的使用可能导致 API 风格不统一。
换一种思路
将“激活用户”视为新资源 /userActivations
,设计如下:
- POST /userActivations : 创建激活请求,请求体包含用户信息。
- 响应:
- 201 Created: 表示激活请求已成功创建。响应体中可以包含新激活请求的详细信息。
- 202 Accepted: 如果激活是异步的、需要时间处理的过程,返回 202 表示服务器已接受请求进行处理,但尚未完成。
客户端后续可以通过
GET /userActivations/{newRequestID}
查询激活状态。
这种设计遵循 REST 原则,保持接口统一性,同时支持复杂操作。
是否使用多级URL
在资源标识设计中,是否采用多级 URL 存在争议。例如,获取某作者的某篇文章:
- 方式一:
GET /authors/{id}?articleid={articleid}
使用查询参数 - 方式二:
GET /authors/{id}/articles/{articleid}
多级URL
虽然“方式一”,使得资源标识URL看起来更简单,灵活性更大,但我更推崇第二种方式。因为它清晰地表达了资源之间的层级关系,如“文章从属于作者”。并且在服务端也可以通过路由,轻松管理访问权限。
然而,如果数据的层级关系复杂时,也可能会出现过度嵌套,例如:
- 方式一:
GET /authors/{id}/articles?categoryid={categoryid}
- 方式二:
GET /authors/{id}/categories/{categoryid}/articles
使用那种方式,就需要根据场景权衡。如果希望按照严格的资源层级关系访问数据,可以选择“方法二”。如果调用的位置希望有灵活性,之后也可能增加其他查询文章的条件,则可以选择“方法一”。
HTTP响应码的使用
RESTful API利用HTTP方法标识对资源的操作,也利用HTTP响应码表示资源状态。但实际API设计中,开发者通常有两种做法:
- 服务导向 将API视为服务,服务可用返回
200 OK
,不可用则返回404
、500
等。响应体内自定义状态码:
{
"code": 201,
"message": "Response Message",
"data": {},
}
- 资源导向: 将API视作资源访问,HTTP响应码直接反映资源状态,
200 OK
表示成功,404
表示资源不存在,201
表示创建成功,400
表示错误请求等。响应体仅用于数据传输。
不难看出,方法二更符合REST原则,URL是资源标识,以404
响应码为例,不论是网络原因,还是服务器原因,还是业务处理逻辑原因,统一表达了资源不存在。
应该仅仅只使用POST方法吗?
是否在 RESTful API 中仅使用 POST 方法,取决于具体场景。如果项目更适合 RPC(远程过程调用)而非 REST,还是改用RPC更好。在 RESTful API 场景中,应该优先遵循 REST 原则。
针对GET方法的局限性,逐一分析如下:
- URL长度限制: GET请求的URL长度受限。对于复杂的查询,如果仅仅将JSON结构的查询参数数列化,转base64后,在Query参数中传递,可能会超过长度限制。更好的方案,应该首先优化查询参数,然后实现从JSON结构到Query参数的映射转换。
- 敏感信息暴露: GET 请求的 URL 可能被记录在日志中,泄露敏感数据。解决方法是使用加密方法,将敏感数据加密处理后再进行传输。
- 复杂查询: GET 请求的查询参数可能随项目复杂化而变得繁琐。建议区分查询(Query)和过滤(Filter):查询由 API 提供支持,过滤可由客户端实现,减少查询参数数量,也减小服务端负担。
尽管 GET 方法存在局限性,RESTful API 的设计仍应优先遵循其核心原则:使用标准 HTTP 方法、名词化资源标识和统一接口。
这些原则有助于构建可维护、可扩展的 API。对于复杂查询或敏感信息场景,POST 方法是合理的补充,但不应完全取代 GET 方法。在设计 API 时,开发者应根据业务场景权衡 REST 原则与实践灵活性,确保 API 设计兼顾规范与实用性。