341

这是这个问题的更通用的重新表述(消除了 Rails 的特定部分)

我不确定如何在 RESTful Web 应用程序中的资源上实现分页。假设我有一个名为 的资源products,您认为以下哪种方法是最好的方法,以及为什么:

1. 只使用查询字符串

例如。http://application/products?page=2&sort_by=date&sort_how=asc
这里的问题是我不能使用整页缓存,而且 URL 不是很干净且易于记忆。

2.使用页面作为资源和查询字符串进行排序

例如。http://application/products/page/2?sort_by=date&sort_how=asc
在这种情况下,看到的问题是它http://application/products/pages/1不是唯一资源,因为使用sort_by=price会产生完全不同的结果,而我仍然无法使用页面缓存。

3. 使用页面作为资源和 URL 段进行排序

例如。http://application/products/by-date/page/2
我个人认为使用这种方法没有问题,但有人警告我这不是一个好方法(他没有给出理由,所以如果你知道为什么不推荐,请告诉我)

任何建议,意见,批评都非常受欢迎。谢谢。

4

13 回答 13

112

我同意 Fionn 的观点,但我会更进一步说,对我来说,页面不是资源,它是请求的属性。这让我只选择了选项 1 查询字符串。感觉刚刚好。我真的很喜欢Twitter API的宁静结构。不太简单,不太复杂,有据可查。无论是好是坏,当我在做某事时以一种方式与另一种方式做某事时,这是我的“首选”设计。

于 2009-04-22T11:14:14.620 回答
69

我认为版本 3 的问题更多是“观点”问题——您将页面视为资源还是页面上的产品。

如果您将页面视为资源,这是一个完美的解决方案,因为对第 2 页的查询将始终产生第 2 页。

但是,如果您将页面上的产品视为资源,您就会遇到问题,即第 2 页上的产品可能会更改(旧产品已删除或其他),在这种情况下,URI 并不总是返回相同的资源。

例如,客户存储了指向产品列表页面 X 的链接,下次打开该链接时,相关产品可能不再出现在页面 X 上。

于 2009-04-22T10:15:59.360 回答
38

HTTP 有很好的 Range 标头,也适合分页。你可以寄

Range: pages=1

只有第一页。这可能会迫使您重新考虑什么是页面。也许客户想要不同范围的物品。Range 标头也可用于声明订单:

Range: products-by-date=2009_03_27-

获得比该日期更新的所有产品或

Range: products-by-date=0-2009_11_30

获取早于该日期的所有产品。'0' 可能不是最好的解决方案,但 RFC 似乎想要范围开始的东西。可能部署的 HTTP 解析器不会解析units=-range_end。

如果标题不是(可接受的)选项,我认为第一个解决方案(全部在查询字符串中)是一种处理页面的方法。但是请规范化查询字符串(按字母顺序排序(键=值)对)。这解决了“?a=1&b=x”和“?b=x&a=1”的微分问题。

于 2009-12-04T19:55:40.320 回答
26

选项 1 似乎是最好的,因为您的应用程序将分页视为一种为同一资源生成不同视图的技术。

话虽如此,URL 方案是相对无关紧要的。如果您将应用程序设计为超文本驱动(因为所有 REST 应用程序都必须按照定义),那么您的客户端将不会自行构建任何 URI。相反,您的应用程序将向客户端提供链接,客户端将跟随它们。

您的客户可以提供的一种链接是分页链接。

所有这一切的令人愉快的副作用是,即使您改变了对分页 URI 结构的看法并在下周实现完全不同的东西,您的客户也可以继续工作而无需任何修改。

于 2009-07-26T17:54:19.257 回答
12

我一直使用选项 1 的风格。缓存不是问题,因为在我的情况下数据经常更改。如果您允许页面的大小是可配置的,那么再次无法缓存数据。

我不觉得网址难以记住或不干净。对我来说,这是对查询参数的一种很好的使用。该资源显然是一个产品列表,查询参数只是告诉您希望列表如何显示 - 排序和哪个页面。

于 2009-05-04T17:09:22.593 回答
10

奇怪的是,没有人指出选项 3 具有特定顺序的参数。 http//application/products/Date/Descending/Name/Ascending/page/2http//application/products/Name/Ascending/Date/Descending/page/2

指向相同的资源,但具有完全不同的 url。

对我来说,选项 1 似乎是最容易接受的,因为它清楚地将“我想要什么”“我想要什么”分开(它们之间甚至还有问号,哈哈)。可以使用完整的 URL 实现整页缓存(无论如何,所有选项都会遇到同样的问题)。

使用 URL 中的参数方法的唯一好处是干净的 URL。尽管您必须想出一些方法来编码参数并无损解码它们。当然你可以使用 URLencode/decode,但它会让 url 再次变得丑陋 :)

于 2010-03-16T23:26:07.623 回答
8

寻找我遇到的这个网站的最佳实践:

http://www.restapitutorial.com

在资源页面中有一个下载 .pdf 的链接,其中包含作者建议的完整 REST 最佳实践。其中有一个关于分页的部分。

作者建议添加对使用 Range 标头和使用查询字符串参数的支持。

要求

HTTP 标头示例:

Range: items=0-24

查询字符串参数示例:

GET http://api.example.com/resources?offset=0&limit=25

其中offset是开始的项目编号,limit是要返回的最大项目数。

回复

响应应包含一个 Content-Range 标头,指示正在返回的项目数以及尚待检索的总项目数

HTTP 标头示例:

Content-Range: items 0-24/66

Content-Range: items 40-65/*

在 .pdf 中有一些针对更具体案例的其他建议。

于 2016-03-08T16:54:13.517 回答
7

我更喜欢使用查询参数偏移量和限制。

offset:用于集合中项目的索引。

限制:用于项目计数。

客户端可以简单地不断更新偏移量,如下所示

offset = offset + limit

下一页。

该路径被视为资源标识符。并且页面不是资源,而是资源集合的子集。由于分页通常是 GET 请求,因此查询参数最适合分页而不是标题。

参考:https ://metamug.com/article/rest-api-developers-dilemma.html#Requesting-the-next-page

于 2015-07-23T09:05:53.397 回答
5

我目前在我的 ASP.NET MVC 应用程序中使用与此类似的方案:

例如http://application/products/by-date/page/2

具体是:http://application/products/Date/Ascending/3

但是,我对以这种方式在路由中包含分页和排序信息并不满意。

项目列表(在这种情况下为产品)是可变的。即下次有人返回包含分页和排序参数的url时,他们得到的结果可能已经改变。http://application/products/Date/Ascending/3因此,作为指向已定义的、不变的产品集的唯一 url的想法就消失了。

于 2009-04-22T10:16:49.890 回答
1

我倾向于同意 slf 的观点,即“页面”并不是真正的资源。另一方面,选项 3 更简洁,更易于阅读,用户更容易猜到甚至在必要时输入。我在选项 1 和 3 之间纠结,但没有看到任何不使用选项 3 的理由。

此外,虽然它们看起来不错,但正如有人提到的那样,使用隐藏参数而不是查询字符串或 URL 段的一个缺点是用户无法添加书签或直接链接到特定页面。这可能是也可能不是问题,具体取决于应用程序,但只是需要注意的事情。

于 2009-04-22T14:05:42.773 回答
0

我之前使用过解决方案 3(我写了很多 django 应用程序)。而且我不认为这有什么问题。它与其他两个一样可生成(以防您需要进行大规模刮擦等),并且看起来更干净。另外,您的用户可以猜测 url(如果它是面向公众的应用程序),人们喜欢能够直接去他们想要的地方,并且 url-guessing 感觉授权。

于 2009-04-22T11:41:57.470 回答
0

我在我的项目中使用以下网址:

http://application/products?page=2&sort=+field1-field2

这意味着 - “给我页面的第二页按 field1 升序,然后按 field2 降序”。或者,如果我需要更大的灵活性,我会使用:

http://application/products?skip=20&limit=20&sort=+field1-field2
于 2014-07-13T23:59:22.633 回答
0

我使用以下模式来获取下一页记录。 http://application/products?lastRecordKey=?&pageSize=20&sort=ASC

RecordKey 是在 DB 中保存顺序值的表的列。这用于从 DB 一次仅获取一页数据。pageSize 用于确定要获取多少条记录。sort 用于对记录进行升序或降序排序。

于 2020-05-06T05:00:54.307 回答