题
我正在为我参与的大型社交网站开发REST API服务。到目前为止,它的运作良好。我可以发行 GET
, POST
, PUT
和 DELETE
请求反对URL并影响我的数据。但是,该数据进行分页(一次限制为30个结果)。
但是,通过我的API获取成员总数的最佳宁静方法是什么?
目前,我向以下URL结构发出请求:
- /API/成员- 返回成员列表(上述30个)
- /API/成员/1- 与单个成员有关,具体取决于使用的要求
我的问题是:然后,如何使用类似的URL结构来获取应用程序中的成员总数?显然只要求 id
字段(类似于Facebook的图API),并且仅返回30个结果,计算结果将无效。
解决方案
虽然对/api/用户的响应进行了分页,并且仅返回30个记录,但没有什么可以阻止您在响应中包括记录总数以及其他相关信息,例如页面大小,页码/偏移等等。 。
Stackoverflow API是相同设计的一个很好的例子。这是用户方法的文档 - https://api.stackexchange.com/docs/users
其他提示
我更喜欢将HTTP标头用于这种上下文信息。
对于我使用的元素总数 X-total-count
标题。
对于下一个上一页等的链接等。我使用http Link
标题:
http://www.w3.org/wiki/linkheader
Github也这样做: https://developer.github.com/v3/#pagation
我认为它更干净,因为当您返回不支持超链接的内容时也可以使用它(即二进制文件,图片)。
最近,我一直对与此相关的问题进行了一些广泛的研究,并认为在这里添加一些发现是建设性的。我将问题扩展到一些问题,以包括有关分页的想法以及与计数有关的观点。
标题
分页元数据以响应标头的形式包括在响应中。这种方法的最大好处是,响应有效载荷本身只是实际数据请求者所要求的。使处理对分页信息不感兴趣的客户的响应更加容易。
野外使用了一堆(标准和自定义)标题,以返回分页相关的信息,包括总数。
x-total计数
X-Total-Count: 234
这是在 一些 蜜蜂 我在野外发现。也有 NPM软件包 为了将此标头的支持添加到EG loopback。一些 文章 也建议设置此标头。
它通常与 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=next
, rel=previous
等等的问题是,它缺乏有关总记录的信息,这就是为什么许多API与此结合的信息 X-Total-Count
标题。
或者,有些API和 jsonapi 标准,使用 Link
格式化,但将信息添加到响应信封中,而不是将信息添加到标题中。这简化了对元数据的访问(并创建一个添加总数信息的地方),以增加访问实际数据本身的复杂性(通过添加信封)为代价。
内容范围
Content-Range: items 0-49/234
由名为的博客文章促进 射程头,我选择您(分页)!. 。作者为使用 Range
和 Content-Range
分页的标题。当我们仔细阅读时 这 RFC 在这些标题上,我们发现将其含义扩展到字节范围之外实际上是RFC的预期,并且明确允许。当在上下文中使用 items
代替 bytes
, ,范围标头实际上为我们提供了一种要求一定范围的项目,并指出响应项目与之相关的总结果的范围。该标头还提供了一种很好的方式来显示总数。这是一个真正的标准,主要是一对一地映射到分页。也是 在野外使用.
信封
许多API,包括 来自我们最喜欢的问答网站的一个 使用 信封, ,围绕用于添加数据的元信息的数据包装器。还, 奥达塔 和 jsonapi 标准都使用响应信封。
这个(恕我直言)的最大缺点是,由于必须在信封中的某个地方找到实际数据,因此处理响应数据变得更加复杂。此外,该信封也有许多不同的格式,您必须使用正确的格式。它说,来自Odata和Jsonapi的响应包膜大不相同,ODATA在响应中的多个点中混合了元数据。
单独的端点
我认为其他答案已经足够涵盖了这一点。我没有进行太多调查,因为我同意这一评论,因为您现在有多种类型的端点,因此这令人困惑。我认为,如果每个端点代表资源的集合,那是最好的。
进一步的想法
我们不仅必须传达与响应相关的分页元信息,而且还允许客户请求特定的页面/范围。有趣的是,查看这方面最终获得了连贯的解决方案。在这里我们也可以使用标题( Range
标头似乎非常合适)或其他机制,例如查询参数。有些人主张将结果视为单独资源,这在某些用例中可能是有意义的(例如 /books/231/pages/52
. 。我最终选择了一系列常用的请求参数,例如 pagesize
, page[size]
和 limit
除了支持 Range
标题(以及作为请求参数)。
您可以响应HEAD请求,将计数作为自定义HTTP标头返回。这样,如果客户只需要计数,则无需返回实际列表,也不需要额外的URL。
(或者,如果您从端点到端点的受控环境中,则可以使用自定义的HTTP动词,例如计数。)
当您不需要实际物品时替代
弗朗西·佩诺夫的答案 当然是最好的方法,因此您始终返回物品以及所有有关您的实体的其他元数据。那就是应该这样做的方式。
但是有时返回所有数据没有意义,因为您可能根本不需要它们。也许您需要的只是关于您要求的资源的元数据。例如总数或页数或其他内容。在这种情况下,您总是可以让URL查询告诉您的服务不要返回项目,而只是元数据,例如:
/api/members?metaonly=true
/api/members?includeitems=0
或类似的东西...
我建议添加标题相同,例如:
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
对于Swagger文件:
从“ 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实现可能会忽略使用其他单元指定的范围。
指示:使用自定义范围单元不违反协议,但可能会忽略。
这样,我们将不得不将接受范围设置为“成员”或任何范围的单位类型。此外,还将内容范围设置为当前范围。 (看: https://www.w3.org/protocols/rfc2616/rfc2616-sec3.html#sec3.12)
无论哪种方式,我都会坚持RFC7233的推荐(https://tools.ietf.org/html/rfc7233#page-8)发送206而不是200:
如果所有前提条件都是正确的,则服务器支持范围
目标资源的标题字段,指定范围为
有效且令人满意(如第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
那新的终点>/api/成员/count刚刚调用成员并返回结果呢?
似乎最容易添加
GET
/api/members/count
并返回成员的总数
有时,框架(例如$ resource/angularjs)需要一个数组作为查询结果,而您实际上不能像 {count:10,items:[...]}
在这种情况下,我将“计数”存储在ResponseHeaders中。
PS实际上,您可以使用$ Resource/angularjs来做到这一点,但需要进行一些调整。
当请求分页数据时,您知道(通过明确的页面大小参数值或默认页面大小值)页面大小,因此您知道是否在响应中获得了所有数据。当响应中的数据少于页面大小时,您就会获取整个数据。返回完整页面后,您必须再次要求另一页。
我更喜欢有单独的端点(或带有参数countonly的相同端点)。因为您可以通过显示正确启动的Progressbar来准备长/耗时的过程。
如果您想在每个响应中返回数据量,则应该有pageSize,也应提及。老实说,最好的方法也是重复请求过滤器。但是反应变得非常复杂。因此,我更喜欢专用端点,而是返回计数。
<data>
<originalRequest>
<filter/>
<filter/>
</originalReqeust>
<totalRecordCount/>
<pageSize/>
<offset/>
<list>
<item/>
<item/>
</list>
</data>
我的库阿奇,更喜欢countonly参数而不是现有端点。因此,当指定时,响应仅包含元数据。
端点?filter =值
<data>
<count/>
<list>
<item/>
...
</list>
</data>
端点?filter = value&countonly = true
<data>
<count/>
<!-- empty list -->
<list/>
</data>