返回对象中项目总数的最佳RESTful方法是什么?


138

我正在为一个涉及的大型社交网站开发REST API服务。到目前为止,它的运行状况很好。我可以发出GETPOSTPUTDELETE请求对象的URL和影响我的数据。但是,将分页此数据(一次最多限制30个结果)。

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

目前,我向以下网址结构发出请求:

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

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


Answers:


84

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

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


3
+1:如果要完全限制获取限制,那么绝对是最RESTful的事情。
Donal Fellows 2010年

2
@bzim您将知道有一个下一个要提取的页面,因为有一个带有rel =“ next”的链接。
Darrel Miller 2010年


1
@Darrel-是的,可以通过有效负载中的任何类型的“ next”标志来完成。我只是觉得在响应中包含收集项的总数本身很有价值,并且就像“下一个”标记一样工作。
弗朗西·佩诺夫

5
返回不是项目列表的对象不是REST API的正确实现,但是REST没有提供任何获取部分结果列表的方法。因此,为了尊重这一点,我认为我们应该使用标头来传输其他信息,例如总计,下一页令牌和上一页令牌。我从未尝试过,我需要其他开发人员的建议。
Loenix

74

我更喜欢将HTTP标头用于此类上下文信息。

对于元素总数,我使用X-total-count标头。
有关指向下一页,上一页的链接,请使用http Link标头:http :
//www.w3.org/wiki/LinkHeader

Github用相同的方式做:https : //developer.github.com/v3/#pagination

我认为它比较干净,因为当您返回不支持超链接的内容(即二进制文件,图片)时,也可以使用它。


5
RFC6648不赞成使用非标准参数名称作为字符串前缀的约定X-
JDawg,

70

最近,我一直在对该问题以及其他与REST调页相关的问题进行广泛的研究,并认为在此处添加我的一些发现具有建设性。我正在扩展这个问题,以包括有关分页的思想以及与之密切相关的计数。

标头

寻呼元数据以响应头的形式包含在响应中。这种方法的最大好处是响应有效载荷本身就是实际数据请求者所要求的。对于对寻呼信息不感兴趣的客户端,可以更轻松地处理响应。

在野外使用了一堆(标准和自定义)标头,以返回与分页相关的信息,包括总数。

X总数

X-Total-Count: 234

我在野外发现的一些 API中都使用了它。也有NPM软件包可用于对此标头添加支持,例如回送。一些文章建议也设置此标头。

它通常与Link标头结合使用,这是一个很好的分页解决方案,但缺少总数信息。

链接

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=nextrel=previous等这里的问题是,它缺乏一共有多少记录有信息,这是为什么许多API将此与X-Total-Count标头结合在一起

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

内容范围

Content-Range: items 0-49/234

由名为Range标头的博客文章推动,我选择了您(用于分页)!。作者强烈建议使用RangeContent-Range标头进行分页。当我们仔细阅读这些标头上 RFC时,我们发现RFC确实预期将它们的含义扩展到字节范围之外,并且明确允许这样做。当在items而不是的上下文中使用时bytes,Range标头实际上为我们提供了一种方法,既可以请求一定范围的项目,又可以指示响应项目所涉及的总结果的范围。此标题还提供了一种显示总计数的好方法。这是一个真正的标准,大多数情况下都是一对一地映射到分页。它也用于野外

信封

许多API,包括我们最喜欢的Q&A网站中的 API,都使用一个信封,即数据周围的包装材料,用于添加有关数据的元信息。同样,ODataJsonApi标准都使用响应信封。

这个问题的最大缺点是处理响应数据变得更加复杂,因为必须在信封的某个位置找到实际数据。该信封也有许多不同的格式,您必须使用正确的格式。这表明OData和JsonApi的响应包络截然不同,OData在响应中的多个点混合在元数据中。

单独的端点

我认为其他答案已经足够涵盖了这一点。我没有进行太多调查,因为我同意这样的说法,因为您现在拥有多种类型的端点,这令人困惑。我认为每个端点代表一个(一个或多个)资源是最好的。

进一步的想法

我们不仅需要传达与响应相关的分页元信息,还允许客户端请求特定的页面/范围。有趣的是,从这方面来看,最终得到一个连贯的解决方案。在这里,我们也可以使用标头(Range标头似乎很合适)或其他机制,例如查询参数。有些人主张处理结果作为单独的资源,这可能是有意义的一些使用情况下(例如页面/books/231/pages/52。最后我选择经常使用的请求参数,如野生范围pagesizepage[size]并且limit等除了支持的Range标题(和请求参数以及)。


我对Range标头特别感兴趣,但是我找不到足够的证据证明使用除bytes范围类型以外的任何内容都是有效的。
VisioN

2
我认为最清晰的证据可以在RFC的14.5节中找到:acceptable-ranges = 1#range-unit | "none"我认为此提法明确为除bytes,尽管规范本身仅定义了,但它bytes
Stijn de Witt '18

24

当您不需要实际物品时的替代方法

弗朗西·佩诺夫(Francis Penov)的答案当然是最好的选择,因此您始终会返回项目以及有关所请求实体的所有其他元数据。那就是应该做的方式。

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

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

或类似的东西


10
将这些信息嵌入到标头中的优势在于,您可以发出HEAD请求以仅获取计数。
felixfbecker

1
@felixfbecker正是,感谢您重新发明轮子并使用各种不同的机制使API混乱:)
EralpB

1
@EralpB感谢您重新发明轮子并弄乱了API!HEAD在HTTP中指定。metaonly还是includeitems不是。
felixfbecker

2
@felixfbecker仅“完全”是为您服务的,其余的是给OP的。对困惑感到抱歉。
EralpB

REST就是关于利用HTTP并尽可能地将其用于预期的目的。在这种情况下,应使用内容范围(RFC7233)。体内的解决方案不好,尤其是因为它不适用于HEAD。按照此处的建议创建新标题是不必要的,也是错误的。
万斯·希普利

23

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

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


4
“自定义HTTP标头”?那会有点令人惊讶,这反过来与我认为RESTful API应该是相反的。最终,这应该不足为奇。
Donal Fellows 2010年

21
@Donal我知道。但是所有好的答案都已经被采纳。:(
bzlm

1
我也知道,但有时您只需要让其他人来回答即可。或者以其他方式使您的贡献更好,例如详细解释为什么应该以最佳方式而不是其他方式进行。
Donal Fellows 2010年

4
在受控环境中,这很可能不足为奇,因为它很可能在内部使用,并且基于开发人员的API政策。我会说这在某些情况下是一个很好的解决方案,值得在这里作为可能的不寻常解决方案的说明。
James Billingham

1
我非常喜欢将HTTP标头用于此类操作(它确实在其中)。在这种情况下,标准的Link标头可能是合适的(Github API使用此标头)。
Mike Marcacci 2014年


7

从“ X-”开始,不推荐使用前缀。(请参阅:https//tools.ietf.org/html/rfc6648

我们发现“接受范围”是映射分页范围的最佳选择:https : //tools.ietf.org/html/rfc7233#section-2.3 由于“范围单位”可能是“字节”或“令牌”。两者都不代表自定义数据类型。(请参阅:https : //tools.ietf.org/html/rfc7233#section-4.2)仍然指出

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

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

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

无论哪种方式,我都会坚持RFC7233的建议(https://tools.ietf.org/html/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

3

似乎最简单的方法就是添加一个

GET
/api/members/count

并返回会员总数


11
这不是一个好主意。您有义务向客户提出2个在其页面上建立分页的请求。第一个请求获取资源列表,第二个请求计算总数。
Jekis 2013年

我认为这是个好方法...您还可以返回结果列表作为json并在客户端检查集合的大小,因此这种情况是愚蠢的示例...此外,您可以先设置/ api / members / count然后再设置/ api /成员偏移量= 10&限制= 20?
米哈尔Ziobro

1
还请记住,很多分页类型都不需要计数(例如无限滚动)-为什么在客户可能不需要它时要计算它
tofarr

2

关于一个新端点> / api / members / count呢,该端点仅调用Member.Count()并返回结果


27
给计数一个明确的端点使其成为一个独立的可寻址资源。它可以工作,但会对您的API新手产生有趣的问题-集合成员的数量是否是与集合分离的资源?我可以使用PUT请求更新它吗?它是否存在于空集合中,或者仅在其中有项目时才存在?如果members可以通过对的POST请求创建集合/api,那么也会/api/members/count产生副作用,还是我必须做一个明确的POST请求才能创建它,然后再请求它?:-)
弗朗西·佩诺夫(Francis Penov)2010年

2

有时框架(例如$ resource / AngularJS)需要一个数组作为查询结果,并且您无法真正获得响应,{count:10,items:[...]}在这种情况下,我在responseHeaders中存储了“ count”。

PS实际上,您可以使用$ resource / AngularJS进行此操作,但需要进行一些调整。


这些调整是什么?他们将会有所帮助这样一个问题:stackoverflow.com/questions/19140017/...
JBCP

角犯规需要一个数组作为查询结果,你只需要与选择的对象属性配置资源:isArray: false|true
雷米Becheras


-1

请求分页数据时,您知道(通过显式页面大小参数值或默认页面大小值)页面大小,因此您知道是否获得了所有数据作为响应。如果响应的数据少于页面大小,那么您将获得全部数据。返回整页时,您必须再次询问另一页。

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

如果要在每个响应中返回datasize,则应该有pageSize,还提到了偏移量。老实说,最好的方法是也重复一个请求过滤器。但是响应变得非常复杂。因此,我更喜欢专用端点来返回计数。

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

我的想法,比现有端点更喜欢使用countOnly参数。因此,指定后,响应仅包含元数据。

端点?过滤器=值

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

端点?filter = value&countOnly = true

<data>
  <count/>
  <!-- empty list -->
  <list/>
</data>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.