174

我正在为我参与的大型社交网络网站开发 REST API 服务。到目前为止,它运行良好。我可以发出GETPOST和请求来反对 URL 并影响我的数据PUTDELETE但是,此数据是分页的(一次限制为 30 个结果)。

但是,通过我的 API 获取成员总数的最佳 RESTful 方式是什么?

目前,我向如下 URL 结构发出请求:

  • /api/members —返回成员列表(如上所述,一次 30 个)
  • /api/members/1 — 影响单个成员,取决于使用的请求方法

我的问题是:我将如何使用类似的 URL 结构来获取我的应用程序中的成员总数?显然,仅请求id字段(类似于 Facebook 的 Graph API)并计算结果将是无效的,因为只会返回 30 个结果的一部分。

4

13 回答 13

98

最近我一直在对这个问题和其他与 REST 分页相关的问题进行广泛的研究,并认为在这里添加我的一些发现是有建设性的。我将这个问题扩大了一点,以包括关于分页的想法以及它们密切相关的计数。

标头

分页元数据以响应标头的形式包含在响应中。这种方法的最大好处是响应负载本身就是请求者请求的实际数据。使对寻呼信息不感兴趣的客户端更容易处理响应。

有一堆(标准和自定义)标头用于返回与分页相关的信息,包括总数。

X-总计数

X-Total-Count: 234

这用于我在野外发现的一些 API 。还有一些NPM 包用于添加对该标头的支持,例如 Loopback。有些文章也建议设置此标头。

它经常与Linkheader结合使用,这是一个非常好的分页解决方案,但缺少总计数信息。

关联

Link: </TheBook/chapter2>;
      rel="previous"; title*=UTF-8'de'letztes%20Kapitel,
      </TheBook/chapter4>;
      rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel

我觉得,通过阅读很多关于这个主题的内容,普遍的共识是使用Link标题rel=next为使用等的客户端提供分页链接rel=previous。这样做的问题是它缺少总记录数的信息,即为什么许多 API 将其与X-Total-Count标头结合在一起。

或者,某些 API (例如JsonApi标准)使用该Link格式,但将信息添加到响应信封而不是标头中。这以增加访问实际数据本身的复杂性(通过添加信封)为代价简化了对元数据的访问(并创建了一个添加总计数信息的位置)。

内容范围

Content-Range: items 0-49/234

由一篇名为Range header 的博客文章推广,我选择你(用于分页)!. 作者为使用RangeContent-Range标头进行分页提供了强有力的理由。当我们仔细阅读有关这些标头 RFC时,我们发现将它们的含义扩展到字节范围之外实际上是 RFC 预期的,并且是明确允许的。当用于items代替的上下文中时bytes, Range 标头实际上为我们提供了一种方法来请求特定范围的项目并指示响应项目涉及的总结果范围。此标题还提供了一种显示总数的好方法。它是一个真正的标准,主要将一对一映射到分页。它也用于野外

信封

许多 API,包括我们最喜欢的问答网站中的API,都使用信封,即数据的包装器,用于添加有关数据的元信息。此外,ODataJsonApi标准都使用响应信封。

这样做的最大缺点(恕我直言)是处理响应数据变得更加复杂,因为必须在信封中的某处找到实际数据。该信封还有许多不同的格式,您必须使用正确的格式。很明显,来自 OData 和 JsonApi 的响应信封完全不同,OData 在响应的多个点混合在元数据中。

单独的端点

我认为这已经在其他答案中得到了足够的覆盖。我没有进行太多调查,因为我同意这令人困惑的评论,因为您现在有多种类型的端点。我认为最好每个端点都代表一个(集合)资源。

进一步的想法

我们不仅要传达与响应相关的分页元信息,还允许客户端请求特定的页面/范围。有趣的是,还关注这方面以最终得出一个连贯的解决方案。这里我们也可以使用 headers(Rangeheader 似乎很合适),或者其他机制,例如查询参数。有些人主张将结果页面视为单独的资源,这在某些用例中可能是有意义的(例如/books/231/pages/52。除了支持标头pagesizepage[size]以及作为请求参数以及)。limitRange

于 2017-05-14T20:56:14.300 回答
93

虽然对 /API/users 的响应是分页的并且只返回 30 条记录,但没有什么可以阻止您在响应中包含记录总数和其他相关信息,如页面大小、页码/偏移量等.

StackOverflow API 是相同设计的一个很好的例子。这是用户方法的文档 - https://api.stackexchange.com/docs/users

于 2010-09-15T08:48:32.637 回答
84

对于这种上下文信息,我更喜欢使用 HTTP 标头。

对于元素的总数,我使用X-total-count标题。
对于下一页、上一页等的链接。我使用 HTTPLink标头:
http ://www.w3.org/wiki/LinkHeader

Github 也是这样做的:https ://docs.github.com/en/rest/overview/resources-in-the-rest-api#pagination

在我看来,它更简洁,因为它也可以在您返回不支持超链接的内容(即二进制文件、图片)时使用。

于 2015-09-05T20:45:37.000 回答
28

不需要实际物品时的替代方案

Franci Penov 的回答肯定是最好的方法,因此您始终返回项目以及有关您请求的实体的所有其他元数据。这就是它应该做的方式。

但有时返回所有数据没有意义,因为您可能根本不需要它们。也许您需要的只是有关您请求的资源的元数据。像总计数或页数或其他东西。在这种情况下,您始终可以让 URL 查询告诉您的服务不要返回项目,而只是返回元数据,例如:

/api/members?metaonly=true
/api/members?includeitems=0

或类似的东西...

于 2015-01-05T10:19:32.840 回答
24

您可以将计数作为自定义 HTTP 标头返回以响应 HEAD 请求。这样,如果客户只想要计数,您不需要返回实际列表,也不需要额外的 URL。

(或者,如果您处于从端点到端点的受控环境中,则可以使用自定义 HTTP 动词,例如 COUNT。)

于 2010-09-15T08:57:07.000 回答
15

我建议添加相同的标题,例如:

HTTP/1.1 200

Pagination-Count: 100
Pagination-Page: 5
Pagination-Limit: 20
Content-Type: application/json

[
  {
    "id": 10,
    "name": "shirt",
    "color": "red",
    "price": "$23"
  },
  {
    "id": 11,
    "name": "shirt",
    "color": "blue",
    "price": "$25"
  }
]

详情请参阅:

https://github.com/adnan-kamili/rest-api-response-format

对于招摇文件:

https://github.com/adnan-kamili/swagger-response-template

于 2016-08-02T13:42:37.517 回答
12

从“X-”开始,前缀已被弃用。(见:https ://www.rfc-editor.org/rfc/rfc6648 )

我们发现“接受范围”是映射分页范围的最佳选择:https ://www.rfc-editor.org/rfc/rfc7233#section-2.3 因为“范围单位”可能是“字节”或“令牌”。两者都不代表自定义数据类型。(见:https ://www.rfc-editor.org/rfc/rfc7233#section-4.2 )不过,据说

HTTP/1.1 实现可以忽略使用其他单位指定的范围。

这表明:使用自定义范围单位不违反协议,但可以忽略它。

这样,我们必须将 Accept-Ranges 设置为“成员”或任何我们期望的范围单位类型。此外,还将 Content-Range 设置为当前范围。(见:https ://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 )

无论哪种方式,我都会坚持 RFC7233 ( https://www.rfc-editor.org/rfc/rfc7233#page-8 ) 的建议发送 206 而不是 200:

如果所有先决条件都为真,服务器支持目标资源的 Range
头字段,并且指定的范围是
有效且可满足的(如第 2.1 节中定义的),服务器应该
发送 206(部分内容)响应有效载荷包含一个
或多个部分表示,这些部分表示对应
于第 4 节中定义的请求的可满足范围。

因此,我们将有以下 HTTP 标头字段:

对于部分内容:

206 Partial Content
Accept-Ranges: members
Content-Range: members 0-20/100

完整内容:

200 OK
Accept-Ranges: members
Content-Range: members 0-20/20
于 2019-03-04T16:31:42.353 回答
3

一个新的端点 > /api/members/count 怎么样,它只是调用 Members.Count() 并返回结果

于 2010-09-15T08:45:20.697 回答
3

似乎最容易添加一个

GET
/api/members/count

并返回成员总数

于 2010-09-15T08:46:15.470 回答
2

有时框架(如 $resource/AngularJS)需要一个数组作为查询结果,而在这种情况下你不能真正得到响应{count:10,items:[...]},我将“count”存储在 responseHeaders 中。

PS 实际上你可以用 $resource/AngularJS 做到这一点,但它需要一些调整。

于 2013-12-15T22:40:00.697 回答
0

您可以将counts其视为一种资源。该 URL 将是:

/api/counts/member
于 2020-03-25T16:33:14.657 回答
0

关于设计 REST API 以返回多个对象的计数的有趣讨论: https ://groups.google.com/g/api-craft/c/qbI2QRrpFew/m/h30DYnrqEwAJ?pli=1

作为 API 使用者,我希望每个计数值都表示为可计数资源的子资源(即 GET /tasks/count 表示任务计数),或者表示为与相关元数据相关的更大聚合中的字段资源(即 GET /tasks/metadata)。通过在同一父资源(即 /tasks)下确定相关端点的范围,API 变得直观,并且可以(通常)从其路径和 HTTP 方法推断端点的目的。

额外的想法:

  1. 如果每个单独的计数仅与其他计数结合使用(例如,对于统计信息仪表板),您可能会公开一个单一的端点,该端点一次聚合并返回所有计数。
  2. 如果您有一个用于列出所有资源的现有端点(即 GET /tasks 用于列出所有任务),则计数可以作为元数据包含在响应中,作为 HTTP 标头或响应正文。这样做会在 API 上产生不必要的负载,根据您的用例,这可能可以忽略不计。
于 2020-10-27T11:24:33.153 回答
-1

请求分页数据时,您知道(通过显式页面大小参数值或默认页面大小值)页面大小,因此您知道是否收到所有数据响应。当响应的数据少于页面大小时,您将获得整个数据。当返回一整页时,您必须再次请求另一页。

我更喜欢有单独的 count 端点(或具有参数 countOnly 的相同端点)。因为您可以通过显示正确启动的进度条来为最终用户准备长时间/耗时的过程。

如果你想在每个响应中返回 datasize,应该还有 pageSize,offset 提到。老实说,最好的方法也是重复请求过滤器。但反应变得非常复杂。所以,我更喜欢专用端点来返回计数。

<data>
  <originalRequest>
    <filter/>
    <filter/>
  </originalReqeust>
  <totalRecordCount/>
  <pageSize/>
  <offset/>
  <list>
     <item/>
     <item/>
  </list>
</data>

我的 Couleage,更喜欢 countOnly 参数而不是现有端点。因此,当指定时,响应仅包含元数据。

端点?过滤器=值

<data>
  <count/>
  <list>
    <item/>
    ...
  </list>
</data>

端点?filter=value&countOnly=true

<data>
  <count/>
  <!-- empty list -->
  <list/>
</data>
于 2014-07-18T09:19:31.080 回答