如何设计实用的 RESTful API

如何设计实用的 RESTful API
原文标题:Best Practices for Designing a Pragmatic RESTful API
原文链接:https://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
作者:Vinay Sahni


当你的数据模型已经开始稳定,你可以为你的 Web 应用程序创建公共 API 了。这是你意识到,一旦 API 发布,就很难对其进行重大更改,所以你希望在发布之前尽可能多地进行更改。现在,互联网上不乏对 API 设计的意见。但是,由于没有一种广泛采用的标准在所有情况下都适用,因此你只有一堆选择:你应该接受什么格式?你应该如何进行身份验证?你的 API 应该进行版本控制吗?

在为 Enchant(Zendesk 的替代方案)设计 API 时,我试图为这些问题找出实用的答案。我的目标是让 Enchant API 易于使用、易于采用,并且足够灵活,能够满足我们自己的用户界面。

API 设计的关键要求

网上许多 API 设计意见都是围绕模糊标准的主观解释所展开的学术讨论,不适用于现实世界。我写这篇文章的目的是为了阐述如何为现代 Web 应用程序设计实用的 API 。为了指导决策过程,我写下了 API 必须努力满足的一些要求:

  • 在合理的情况下应使用 Web 标准
  • 应对开发人员友好,并可以通过浏览器地址栏进行浏览
  • 应简单、直观、一致,要让采用简单且愉快
  • 应提供足够的灵活性来支持 Enchant UI 的大多数功能
  • 应高效的同时与其他要求保持平衡

API 是开发人员的 UI,就像任何 UI 一样,重要的是确保精心考虑过用户体验!

使用和操作 RESTful URL

RESTful 原则获得了广泛采用。这些原则最初由 Roy Fielding 在网络软件架构论文的第五章中介绍。

REST 的关键原则涉及将 API 分成逻辑资源。这些资源使用 HTTP 请求进行操作,其中方法(GET,POST,PUT,PATCH,DELETE)具有特定的含义。

但是我能将什么作为资源呢? 这些应该是从 API 使用者的角度看来合理的名词,而不是动词。这里需要清楚的是:名词是一个事物,动词是对它所做的事情。Enchant 的一些名词可能是 ticket、user 和 customer。

但要注意: 尽管你的内部模型可能与资源很好地映射,但这不一定是一对一的映射。这里的关键是不要将不相关的实现细节泄露给 API。你的 API 资源需要从 API 使用者的角度来看待。

在定义好资源之后,需要确定适用于它们的操作并需要将这些操作映射到你的 API。RESTful 原则提供了使用下列 HTTP 方法来处理 CRUD 操作的策略:

  • GET /tickets - 获取一个票据列表
  • GET /tickets/12 - 获取指定的票据
  • POST /tickets - 创建一个新票据
  • PUT /tickets/12 - 更新票据 #12
  • PATCH /tickets/12 - 部分更新票据 #12
  • DELETE /tickets/12 - 删除票据 #12

RESTful API 的优点在于,你可以利用现有的 HTTP 方法在单个 /tickets 端点上实现重要的功能,而不需要遵循方法命名约定,URL 结构也很清晰明了。因此 RESTful API 非常适用。

API 终端名应该使用复数而不是单数,因为这样会使 API 的使用更简单。尽管我们的语法告诉我们用复数来描述一个资源的单个实例是不对的,但实用的答案是保持 URL  格式的一致性,并始终使用复数。不用处理奇特的复数形式(person/people,goose/geese)会使 API 使用者的生活更轻松,对  API 提供者也更容易实现(因为大多数现代框架都能在同一个控制器下处理 /tickets /tickets/12)。

当一个关系只能存在于另一个资源中时,RESTful 原则提供了有用的指导。让我们用一个例子来看下。在 Enchant 中,一张机票包含多条消息。这些消息可以逻辑地映射到 /tickets 终端,如下所示:

  • GET /tickets/12/messages - 获取票 #12 的消息列表
  • GET /tickets/12/messages/5 - 获取票 #12 的消息 #5
  • POST /tickets/12/messages - 在票 #12 中创建新消息
  • PUT /tickets/12/messages/5 - 更新票 #12 的消息 #5
  • PATCH /tickets/12/messages/5 - 部分更新票 #12 的消息 #5
  • DELETE /tickets/12/messages/5 - 删除票 #12 的消息 #5

备选方案 1:如果一个关系可以独立于资源存在,那么在资源的输出表示中包含一个标识符是有意义的。API 使用者然后必须访问关系的终端。

备选方案 2:对于常常与资源一起请求的独立存在的关系,API 可以提供自动嵌入关系表示的功能,以避免对 API 的第二次访问。这样可以使 API 更清晰,并且只需要对服务器发送一次请求。我更喜欢这种方法。

对于不适合 CRUD 的操作该怎么办?

这就要分情况讨论了。有几种方法:

  1. 重组操作,使其看起来像资源的字段。如果操作不使用参数,则此方法有效。例如,激活操作可以映射到布尔 activated 字段,并通过 PATCH 更新资源。
  2. 将其视为符合 RESTful 原则的子资源。例如,GitHub 的 API 允许你使用 PUT /gists/:id/star 为要点加注星标,使用 DELETE /gists/:id/star 取消加注星标。
  3. 有时你真的没有办法将操作映射到合理的 RESTful 结构。例如,将多资源搜索应用于特定资源的端点实际上没有意义。在这种情况下,/search 将最有意义,即使它不是资源。这没关系——从 API 使用者的角度做正确的事,并确保清楚地记录下来以避免混淆。

随时随地使用 SSL

没有任何例外,就应该始终使用 SSL。如今,你的 Web API 可以从任何有互联网的地方访问(如图书馆、咖啡店、机场等)。但是并不是所有这些地方都是安全的。许多地方根本不加密通信,如果认证凭据被劫持,就会轻松被人窃听或冒充他人。所以应始终使用  SSL 保护通信安全。

一直使用 SSL 的另一个优势是保证的加密通信简化了身份验证工作,你可以使用简单的访问令牌,而无需对每个 API 请求进行签名。

要注意的一点是对 API URL 的非 SSL 访问。不要将这些重定向到它们的 SSL 对应物。而是抛出一个硬错误!当自动重定向存在时,配置不当的客户端可能会在未加密的端点上无意间泄露请求参数。硬错误确保提前捕获此错误并正确配置客户端。

文档

API 的好坏取决于它的文档。文档应易于找到并可以公开访问。大多数开发人员在尝试任何集成工作之前都会查看文档。当文档隐藏在 PDF 文件中或需要登录时,不仅难以找到还不易搜索。

文档应显示完整请求/响应周期的示例。最好是可粘贴的示例请求——可以粘贴到浏览器中的链接或可以粘贴到终端中的 curl 示例。GitHub 和 Stripe 在这方面做得很好。

一旦发布公共 API,你就承诺不会在未经通知的情况下破坏任何内容。文档必须包括任何废弃计划和有关外部可见 API 更新的详细信息。更新应通过博客(即更改日志)或邮件列表(最好是两者都有)发布。

版本控制

对 API 进行版本控制是你必须要做的。版本控制可以帮助你更快地迭代,并防止无效请求访问更新的端点。它能让 API 版本的任何重大转换都变得丝滑,因为你可以在一段时间内继续提供旧的 API 版本。

关于 API 版本是应该包含在 URL 中还是在标头中,存在不同意见。从学术上讲,应该在标头中。但是,为了确保浏览器可以跨版本浏览资源并拥有更简单的开发人员体验,版本号包含在 URL 中更好。

我非常喜欢 Stripe 对 API 版本控制的方法,URL 中包含主要版本号(v1),但是 API 具有基于日期的子版本,可以使用自定义 HTTP 请求标头选择。在这种情况下,主版本提供整个 API 的结构稳定性,而子版本则处理较小的更改(字段弃用,端点更改等)。

API 从来都不会完全稳定。变化是不可避免的。重要的是如何管理这种变化。对于许多 API 而言,可以接受良好文档化和宣布的多月弃用计划。这取决于给定行业和可能使用 API 的消费者的合理性。

结果过滤、排序和搜索

在 URL 上尽量保持简洁。复杂的结果过滤、排序要求以及高级搜索(仅限于单种资源)都可以很容易地作为基本 URL 上的查询参数来实现。让我们详细地看看这些:

过滤

对于每个实现过滤的字段使用唯一的查询参数。例如,当从 /tickets 端点请求票列表时,你可能希望仅将其限制在 open 状态。这可以通过 GET /tickets?state=open  这样的请求来实现。在这里,state 是一个查询参数,用于实现过滤。

排序

与过滤类似,可以使用通用参数 sort 描述排序规则。通过让 sort 参数接受由逗号分隔的字段列表,每个字段都有可能带有一元负号来暗示降序排序顺序,来容纳复杂的排序要求。让我们看看一些示例:

  • GET /tickets?sort=-priority - 按照优先级的降序获取票列表
  • GET /tickets?sort=-priority,created_at - 按照优先级的降序获取票列表。在特定优先级内,优先订购较旧的票

搜索

有时基本过滤器不够用,就需要全文搜索的能力。也许你已经在使用 ElasticSearch  或另一种基于 Lucene 的搜索技术。当使用全文搜索作为检索特定类型资源的实例的机制时,可以将其作为资源端点上的查询参数在 API 上公开。比方说q,搜索查询应直接传递给搜索引擎,API 输出应与正常列表结果的格式相同。将这些结合起来,我们可以构建像这样的查询:

  • GET /tickets?sort=-updated_at - 检索最近更新的票
  • GET /tickets?state=closed&sort=-updated_at - 检索最近关闭的票
  • GET /tickets?q=return&state=open&sort=-priority,created_at - 检索提及“return”这个词的优先级最高的 open 票

常见

查询的别名为了使 API 对普通消费者的体验更加愉快,要考虑将一组条件打包到易于访问的 RESTful 路径中。例如,上面的最近关闭的票查询可以打包为  GET /tickets/recently_closed

限制 API 返回字段

API 使用者并不一定需要资源的完整表示。能够选择和选择返回的字段有助于让 API 使用者最大限度地减少网络流量并加速他们对 API 的使用。可以使用一个字段查询参数,其中包含要包含的字段的逗号分隔列表。例如,以下请求将只获取足以显示排序的打开的工单列表的信息:

GET /tickets?fields=id,subject,updated_at&state=open&sort=-updated_at

注意:此方法也可以与相关资源的自动加载结合使用:

GET /tickets?embed=customer&fields=id,customer.id,customer.name

更新和创建应返回资源表示

PUT、POST 或 PATCH 调用可能会修改未包含在提供的参数中的底层资源的字段(例如:created_at 或  updated_at 时间戳)。为防止 API 使用者必须再次访问 API 以获取更新的表示,应让 API 将更新的(或创建的)表示作为响应的一部分返回。

在发生 POST 并导致创建的情况下,使用 HTTP 201 状态码,并包含指向新资源 URL 的 Location 标头。这两者都应附加在包含新创建的资源表示作为响应主体的响应中。

应该使用 HATEOAS 吗?

关于 API 使用者是否应该创建链接或是否应将链接提供给 API,存在很多不同的意见。RESTful 设计原则指定了 HATEOAS,其大致意思是与端点的交互应在输出表示的元数据中定义,而不是基于带外信息。

尽管网络通常遵循 HATEOAS 类型的原则(我们访问网站的首页,并根据页面上看到的内容点击链接),但我认为我们尚未准备好在 API 上使用 HATEOAS。浏览网站时,在运行时决定将点击哪些链接。但是,使用 API 时,决定发送哪些请求是在编写 API 集成代码时做出的,而不是在运行时做出的。难道可以将决策延迟到运行时吗?当然可以,但是沿着这条路走并没有多大收益,因为代码仍然无法在不打破的情况下处理显著的  API 变化。尽管如此,我认为 HATEOAS  是有前途的,但尚未准备好普及。必须付出更多努力来定义围绕这些原则的标准和工具,才能充分发挥其潜力。

目前,最好假设用户可以访问文档,并在输出表示中包含资源标识符,API 使用者将在构建链接时使用这些标识符。坚持使用标识符有几个优势——网络流动的数据被最小化,API 使用者存储的数据也被最小化(因为他们存储的是小的标识符,而不是包含标识符的 URL)。

此外,鉴于此帖子建议在 URL 中使用版本号,因此长期而言,API 使用者存储资源标识符而不是 URL 更有意义。毕竟,标识符在版本之间是稳定的,但表示它的 URL 却不是!

仅 JSON 响应

XML 不是 API 的好选择。它冗长,难以解析,难以阅读,其数据模型与大多数编程语言模型的数据不兼容,当输出表示的主要需求是内部表示的序列化时,它的扩展性优势也毫无意义。

我不会花太多努力来解释这一点。要注意的关键是,今天你很难找到任何仍支持 XML 的主要 API。你也不应该这样做。

尽管如此,如果你的客户群包括大量企业客户,你可能会发现自己不得不支持 XML。如果必须这样做,你会面临一个新问题:

媒体类型应该基于 Accept 头还是基于 URL 进行更改?

为了确保浏览器可探索性,它应该在 URL 中。这里最明智的选择是在端点 URL 后面添加 .json.xml 扩展名。

字段名称的 snake_case 与 camelCase

在使用 JSON(JavaScript 对象表示法)作为主要表示格式时,“正确”的做法是遵循 JavaScript  命名约定,也就是字段名称使用驼峰命名法(camelCase)。如果你选择在各种语言中构建客户端库,最好使用这些语言的惯用命名约定:C#和 Java 的驼峰命名法(camelCase),Python 和 Ruby 的下划线命名法(snake_case)

深度思考:我一直觉得下划线命名法(snake_case)比 JavaScript 的驼峰命名法(camelCase)更容易阅读。直到现在,我才有证据证明我的直觉是正确的。根据 2010 年关于驼峰命名法和下划线命名法(PDF)的眼动跟踪研究下划线命名法比驼峰命名法容易阅读 20%! 这对可读性的影响会影响API的可探索性和文档中的示例。

许多流行的 JSON API 使用下划线命名法。我怀疑这是由于服务器端序列化库遵循底层语言的命名约定。也许我们需要让 JSON 序列化库处理命名约定转换。

默认启用美化打印并确保支持 gzip

从浏览器中查看提供空白压缩输出的 API 并不是很有趣。尽管可以提供某种查询参数(例如 ?pretty=true)来启用美化打印,但默认情况下进行美化打印的 API 更容易使用。额外数据传输的成本微不足道,尤其是与不实现 gzip 的成本相比。

考虑一些用例:如果 API 使用者正在调试并且将从 API 接收到的数据打印出来,那么默认情况下它就是可读的。或者,如果消费者抓取了他们的代码生成的  URL,并直接从浏览器中访问它——那么默认情况下它就是可读的。这些都是小事。这些小事使 API 使用起来感觉舒适!

但是额外的数据传输呢?

让我们用一个真实的例子来看看。我从 GitHub 的 API 中提取了一些数据,它默认使用美化打印。我还将进行一些gzip比较:

$ curl https://api.github.com/users/veesahni > with-whitespace.txt
$ ruby -r json -e 'puts JSON.parse(STDIN.read)' < with-whitespace.txt > without-whitespace.txt
$ gzip -c with-whitespace.txt > with-whitespace.txt.gz
$ gzip -c without-whitespace.txt > without-whitespace.txt.gz

输出文件具有以下大小:

  • without-whitespace.txt - 1221 字节
  • with-whitespace.txt - 1290 字节
  • without-whitespace.txt.gz - 477 字节
  • with-whitespace.txt.gz- 480 字节

在这个例子中,当不使用 gzip 时,空格增加了输出大小的 5.7%,当使用 gzip 时增加了 0.6%。另一方面,gzip 本身提供了超过 60% 的带宽节省。由于美化打印的成本相对较小,最好默认启用美化打印并确保支持 gzip 压缩!

默认情况下不使用信封

许多 API 将其响应包装在信封中,如下所示:

{
  "data" : {
    "id" : 123,
    "name" : "John"}
}

这样做使得包含其他元数据或分页信息变得容易,一些 REST 客户端不允许轻松访问 HTTP 头部和 JSONP  请求无法访问 HTTP 标头。但是,随着像 CORS 和 RFC 5988 的 Link header  这样的标准被迅速采用,封装正开始变得不必要。

我们可以通过默认情况下保持无信封,并在特殊情况下使用信封来使 API 免受未来的影响。

在特殊情况下如何使用信封?

有 2 种情况需要使用信封——如果 API 需要支持跨域请求的 JSONP,或者客户端无法处理 HTTP 标头。

为了支持跨域 JSONP:这些请求带有一个附加的查询参数(通常命名为 callback 或  jsonp),表示回调函数的名称。如果存在此参数,API 应该切换到完整信封模式,在该模式下总是返回 200 HTTP 状态码,并在 JSON  有效负载中传递真实的状态码。将会随响应一起传递的任何其他 HTTP 标头应映射到 JSON 字段,如下所示:

callback_function({
  status_code: 200,
  next_page: "https://..",
  response: {
    ... actual JSON response body ... 
  }
})

为了支持有限的 HTTP 客户端:允许使用特殊查询参数 ?envelope=true,该参数将在没有 JSONP 回调函数的情况下触发信封。

JSON 编码的 POST、PUT 和 PATCH 主体

如果你采用这种方法,那么你就已经接受了 JSON 作为所有 API 输出的格式。让我们考虑使用 JSON 作为 API 输入。

许多 API 在 API 请求体中使用 URL 编码。URL 编码就是使用与将数据编码到 URL 查询参数中相同的约定来对键值对进行编码的过程。这种方式简单易行,广泛支持,并且能够完成工作。

然而,URL 编码有一些问题——它没有数据类型的概念。这迫使 API 从字符串中解析整数和布尔值。此外,它没有真正的层次结构概念。尽管有一些约定可以通过键值对构建一些结构(例如在键后面附加 [] 表示数组),但这与 JSON 的本机层次结构相比还是很少见的。

如果 API 简单,我想 URL 编码或许可以满足要求。但是我认为,它与输出格式不一致。

对于基于 JSON 的 API,你应该在 API 输入中也使用 JSON。

接受 JSON 编码的 POST、PUT 和 PATCH 请求的 API 也应该要求设置 Content-Type 头为 application/json,或者抛出 415 Unsupported Media Type HTTP 状态码。

分页

一般来说,喜欢使用信封的 API 通常会在信封本身中包含分页数据。我并不怪他们,毕竟直到最近,还没有更好的选择。今天包含分页详细信息的正确方法是使用 RFC 8288 引入的链接标头。

使用 Link 标头的 API 可以返回一组准备好的链接,这样 API 使用者就不必自己构建链接。当分页是基于光标时,这尤其重要。下面是从 GitHub 的文档中捕获的使用 Link 标头正确使用的示例:

Link:  <https://api.github.com/user/repos?page=3&per_page=100>;  rel="next",  <https://api.github.com/user/repos?page=50&per_page=100>;  rel="last" 

但这不是一个完整的解决方案,因为许多 API 确实喜欢返回其他分页信息,如可用结果总数的计数。需要发送计数的 API 可以使用类似 X-Total-Count 的自定义 HTTP 标头。

自动加载相关资源表示

有许多情况下,API 使用者需要加载与正在请求的资源相关(或从中引用)的数据。如果允许在源资源旁边按需返回和加载相关数据,就可以大大提高效率,而不是要求消费者多次访问 API 获取此信息。

但是,由于这违反了某些 RESTful 原则,我们可以通过仅基于 embed(或 expand)查询参数来最小化偏差。在这种情况下,embed 将是要嵌入的字段的逗号分隔列表。可以使用点符号来引用子字段。例如:

GET /tickets/12?embed=customer.name,assigned_user

这将返回带有嵌入的附加详细信息的票据,例如:

{
  "id" : 12,
  "subject" : "I have a question!",
  "summary" : "Hi, ....",
  "customer" : {
    "name" : "Bob"},
  assigned_user: {
   "id" : 42,
   "name" : "Jim",
  }
}

当然,实现这种东西的能力实际上取决于内部的复杂性。这种嵌入很容易导致 N+1 选择问题。

覆盖 HTTP 方法

一些 HTTP 客户端只能处理简单的 GET 和 POST 请求。为了提高这些受限客户端的可访问性,API 需要一种方法来覆盖 HTTP 方法。尽管这里没有任何严格的标准,但流行的约定是接受一个请求头 X-HTTP-Method-Override,其中包含 PUT、PATCH 或 DELETE 的字符串值。

注意:覆盖头应该只能在 POST 请求上接受。GET 请求绝对不应该更改服务器上的数据!

速率限制

为了防止滥用,在 API 上添加某种形式的限速是标准做法。RFC 6585 引入了 HTTP 状态码 429 Too Many Requests 来适应这一点。

但是,在消费者实际达到限制之前通知他们的限制可能非常有用。目前这个领域缺乏标准,但有许多使用 HTTP 响应标头的流行惯例。至少包括以下标头:

  • X-Rate-Limit-Limit - 当前周期中允许的请求数
  • X-Rate-Limit-Remaining - 当前周期中剩余的请求数
  • X-Rate-Limit-Reset - 当前周期剩余的秒数

为什么使用 X-Rate-Limit-Reset 中的剩余秒数而不是时间戳?

时间戳包含各种有用但不必要的信息,例如日期和可能的时区。API 使用者只想知道他们什么时候可以再次发送请求,而剩余秒数的回答可以最小化他们端上的额外处理。它还避免了与时钟偏差有关的问题。

一些 API 使用 UNIX 时间戳(自 epoch 以来的秒数)作为 X-Rate-Limit-Reset。不要这样做。

为什么使用 UNIX 时间戳作为 X-Rate-Limit-Reset 是不好的实践?

HTTP 规范已经指定使用 RFC 1123 日期格式(目前用于 Date、If-Modified-Since 和  Last-Modified HTTP 标头)。如果我们要指定一个新的 HTTP 标头,它应该遵循 RFC 1123 约定,而不是使用 UNIX 时间戳。

认证

RESTful API 应该是无状态的。这意味着请求身份认证不应依赖于 cookie 或会话。相反,每个请求应携带某种身份认证凭据。

通过始终使用 SSL,可以将认证凭据简化为随机生成的访问令牌,该令牌将以 HTTP Basic Auth 的用户名字段的形式发送。优点是它完全可以在浏览器中使用,如果服务器从服务器接收到 401 Unauthorized  状态码,浏览器将弹出提示框询问凭据。

然而,仅在可以让用户从管理界面复制令牌到 API 使用者环境的情况下,才能使用这种令牌基本认证方法。在不可能的情况下,应使用 OAuth 2 为第三方提供安全令牌传输。 OAuth 2 使用持有者令牌,并且还依赖 SSL 作为其基础传输加密。

需要支持 JSONP 的 API 需要第三种认证方法,因为 JSONP 请求无法发送 HTTP Basic Auth 凭据或持有者令牌。在这种情况下,可以使用特殊查询参数 access_token。注意:使用查询参数作为令牌存在固有的安全问题,因为大多数 Web  服务器都会将查询参数存储在服务器日志中。

值得一提的是,上面三种方法只是在 API 边界之间传输令牌的方法。实际的底层令牌本身可以是相同的。

缓存

HTTP 提供了内置的缓存框架,你所要做的就是包含一些额外的出站响应标头,并在接收到一些入站请求标头时进行一些验证。有两种方法:ETag 和 Last-Modified。

ETag

在生成响应时,包含一个包含表示的哈希或校验和的 HTTP 标头  ETag。每当输出表示更改时,此值应更改。如果入站 HTTP 请求包含一个具有匹配 ETag 值的 If-None-Match 标头,则 API 应返回状态码 304 Not Modified,而不是资源的输出表示。

Last-Modified

这与 ETag 基本相同,只是使用时间戳。响应标头 Last-Modified 包含以 RFC 1123 格式的时间戳,该时间戳与 If-Modified-Since 进行验证。请注意,HTTP  规范有 3 种不同的可接受日期格式,服务器应准备接受任何一种。

错误

就像 HTML 错误页面向访问者显示有用的错误信息一样,API 应该以已知的可消耗格式提供有用的错误信息。错误的表示应该与任何资源的表示没有区别,只是拥有自己的一组字段。

API 应始终返回合理的 HTTP 状态码。API 错误通常分为两类:400 系列状态码用于客户端问题,500  系列状态码用于服务器问题。最起码,API 应使所有 400 系列错误都具有可消耗的 JSON  错误表示。如果可能的话(即负载均衡器和反向代理可以创建自定义错误正文),这应扩展到 500 系列状态码。

JSON 错误正文应为开发人员提供几件事情:有用的错误消息、唯一的错误代码(可以在文档中查找更多详细信息)以及可能的详细描述。类似于此的 JSON 输出表示将如下所示:

{
  "code" : 1234,
  "message" : "Something bad happened :(",
  "description" : "More details about the error here"
  }

用于 PUT、PATCH 和 POST 请求的验证错误将需要字段分解。最好使用固定的顶级错误代码模拟验证失败,并在额外的 errors 字段中提供详细的错误,如下所示:

{
  "code" : 1024,
  "message" : "Validation Failed",
  "errors" : [
    {
      "code" : 5432,
      "field" : "first_name",
      "message" : "First name cannot have fancy characters"},
    {
       "code" : 5622,
       "field" : "password",
       "message" : "Password cannot be blank"}
  ]
}

HTTP 状态码

HTTP 定义了一些有意义的状态码,可以从 API 返回。这些状态码可以帮助 API 使用者根据需要路由响应。以下是常用的一些状态码:

  • 200 OK - 对成功的 GET、PUT、PATCH 或 DELETE 的响应。也可用于不会导致创建的 POST 请求。
  • 201 Created - 对导致创建的 POST 的响应。应与指向新资源位置的 Location 标头一起使用。
  • 204 No Content - 对成功但不返回正文的请求的响应(例如 DELETE 请求)。
  • 304 Not Modified - 当 HTTP 缓存标头正在使用时使用。
  • 400 Bad Request - 请求格式不正确,例如正文无法解析。
  • 401 Unauthorized - 未提供或提供无效的身份验证详细信息。如果从浏览器使用 API,也有助于触发身份验证弹出窗口。
  • 403 Forbidden - 当身份验证成功但身份验证用户无权访问资源时。
  • 404 Not Found - 请求不存在的资源时。
  • 405 Method Not Allowed - 请求不允许的 HTTP 方法。
  • 410 Gone - 表示此端点的资源不再可用。对于旧的 API 版本很有用。
  • 415 Unsupported Media Type - 如果在请求中提供了错误的内容类型。
  • 422 Unprocessable Entity - 用于验证错误。
  • 429 Too Many Requests - 当请求因速率限制而被拒绝时。

总结

API 是开发人员的用户界面。我们应花费精力确保它不仅功能强大,并且使用起来是愉快的。

订阅
qrcode

订阅

随时随地获取 Apifox 最新动态