这是对该问题的更一般化的表述(省去了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
我个人认为使用此方法没有问题,但有人警告我这不是一个好方法(他没有给出原因,因此,如果您知道为什么不建议这样做,请告诉我)

任何建议,观点,批评都值得欢迎。谢谢。

评论

这是一个很好的问题。

额外的问题:人们通常如何指定页面大小?

不要忘记Matrix参数w3.org/DesignIssues/MatrixURIs.html

#1 楼

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

如果将页面视为资源,则是完美的解决方案,因为查询第2页将始终产生第2页。旧产品删除或其他原因),在这种情况下,URI并不总是返回相同的资源。

例如客户存储指向产品列表页面X的链接,下次打开链接时,所涉及的产品可能不再位于页面X上。

评论


好吧,但是如果您删除某些内容,则同一URI上不应有其他内容。如果删除页面X的所有产品-页面X可能仍然有效,但现在包含页面X + 1中的产品。因此,如果您在“产品资源视图”中看到页面X的URI,则页面X + 1的URI已成为页面X + 1的URI。 ”。

–芬恩
09年4月22日在12:36

>如果您将页面视为资源,则它是一个很好的解决方案,因为对页面2的查询将始终产生页面2。这是否有意义?不管您是什么资源,相同的URL(任何提及页面2的URL)将始终产生页面2。

–temoto
09年12月4日在19:34

将页面视为资源可能应该引入POST / foo / page来创建新页面,对吗?

–temoto
09年12月4日在19:37

您的答案顺利地转到“正确的解决方案是1”,但没有说明。

–temoto
09年12月4日在19:38

在我看来,页面是一个浮动的概念,与基础领域无关。因此,不应将其视为资源。我的意思是说浮动是流动的,页面的概念随上下文而改变;您的API的一个用户可能是一个移动应用程序,每页只能使用2种产品,而另一个是可以消耗整个列表的机器应用程序。简而言之,页面是基础域实体(产品)的“表示形式”,不应作为URL的一部分包含在内;仅作为查询参数。

–金斯
2014年4月29日在18:13

#2 楼

我同意Fionn的观点,但我将进一步说,页面对我而言不是资源,而是请求的属性。这使我只选择了选项1查询字符串。感觉不错。我真的很喜欢Twitter API的结构结构。不太简单,也不太复杂,有据可查。不管是好是坏,当我以一种方式与另一种方式做对时,这是我的“去”设计。

评论


+1:查询字符串不是一流的资源标识符;他们只是澄清了资源的排序和分组。

– S.Lott
2010年3月2日在21:16

@ S.Lott请求是资源。 Fielding在其论文的5.2.1.1节中将所谓的“一流资源”定义为值。此外,在同一部分中,Fielding给出了源代码文件的最新修订,作为资源示例。那怎么可能是资源,而最新的10个产品却是“产品资源请求的属性”?我了解您的观点较为实用,但我认为它的REST风格较差。

– edsioufi
2013年9月5日14:29



请注意,我的评论并不意味着我不同意在URL上使用查询字符串的选择:只要API是超媒体驱动的,两者都是可行的解决方案,就像@RichApodaca在其回答中提到的那样。我只是指出,从REST的角度来看,应该将Page视为一种资源。

– edsioufi
2013年9月5日14:33



#3 楼

HTTP具有出色的Range标头,也适用于分页。您可以发送

Range: pages=1


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

Range: products-by-date=2009_03_27-


获取比该日期或

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


更新的所有产品/>使所有产品早于该日期。 “ 0”可能不是最佳解决方案,但是RFC似乎需要一些用于范围开始的内容。可能部署了HTTP解析器,无法解析unit = -range_end。

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

评论


标题乍一看可能看起来不错,但它们不允许共享页面(例如,通过复制网址)。因此,对于ajax请求,它们可能是一个不错的解决方案(因为无论如何都无法在其当前状态下共享由ajax修改的页面),但是我不会将它们用于常规分页。

–马库斯
2011年4月9日13:00



并且Range标头仅用于字节范围。请参阅[HTTP标头规范](w3.org/Protocols/rfc2616/rfc2616-sec14.html),第14.35节。

–克里斯·威斯汀(Chris Westin)
2012年8月21日17:59

@ChrisWestin w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 HTTP / 1.1在Range(第14.35节)和Content-Range(第14.16节)标头字段中使用范围单位。范围单位=字节单位| other-range-unit也许您是指HTTP / 1.1定义的唯一范围单位是“字节”。 HTTP / 1.1实现可以忽略使用其他单位指定的范围。那与您的陈述不同。

–temoto
2012年9月3日在18:54



@Markus我无法想象共享共享api资源时的用例:)

–JakubKnejzlik
2015年12月26日在12:41

@JakubKnejzlik共享不是问题,但是使用HTTP标头进行分页会阻止使用HATEOAS链接进行分页。

–xarx
18年5月16日在22:03

#4 楼

在您的应用程序将分页视为一种用于产生相同资源的不同视图的技术的情况下,选项1似乎是最好的。如果您将应用程序设计为超文本驱动的(因为所有REST应用程序必须根据定义),那么您的客户端将不会自行构造任何URI。相反,您的应用程序将提供指向客户端的链接,客户端将跟随它们。

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

所有这些的副作用是,即使您改变了对分页URI结构的看法并在下周实施了完全不同的操作,您的客户也可以继续工作而无需进行任何修改。

评论


提醒您在REST Web服务中使用超媒体之类的链接。

– Paul D. Eden
2012年2月17日在16:42

#5 楼

我一直使用选项1的样式。由于我的情况下数据经常更改,因此缓存并不是一个问题。如果您允许页面的大小是可配置的,那么数据将无法再次缓存。对我来说,这是查询参数的一种很好的用法。该资源显然是产品列表,查询参数只是在告诉您要如何显示列表-排序以及在哪个页面。

评论


+1我认为您是对的,我将使用查询参数(选项1)

– andi
09年5月4日在19:07

“我觉得该网址不容易记住”。这种观察在REST应用程序中是没有用的,因为它们通常只应具有一个书签...如果用户(或客户端应用程序)试图“记住” URL,则表明该API并不宁静。

– edsioufi
2013年9月5日14:53



#6 楼

奇怪的是没有人指出选项3具有特定顺序的参数。应用程序/产品/名称/升序/日期/降序/页面/ 2

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

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

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

评论


这是两个不同的顺序。第一个按日期降序排序,仅按名字升序打断关系。第二种按名称升序排序,仅按日期降序打破联系。

– Imran Rashid
2015年2月3日下午17:00

实际上,此处给出的两个示例URL不仅在书写上有所不同,而且在含义上也有所不同。由于表示路径,因此不能保证先左后右都可以找到相同的东西,反之亦然。话虽如此,排序参数作为URL路径的一部分比URL参数具有形式上的优势,URL参数应该可以互换地交换而不改变整体含义,但确实存在编码陷阱的问题,如此处所述。

–克里斯蒂安·戈施(Christian Gosch)
15年3月10日在15:51

#7 楼

我更喜欢使用查询参数offset和limit。

偏移量:用于集合中项目的索引。

限制:用于项目计数。

客户端可以简单地继续更新偏移量,如下所示。



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

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

#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/*

更具体的情况。

#9 楼

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

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

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

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

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

评论


我认为,第一个问题是对多列进行排序,适用于所有3种方法。因此,对于任何一个人来说,这都不是真正的利弊。关于第二个问题:这不会发生在任何资源上吗?例如,产品也可以被编辑/删除。

– andi
09年4月22日在11:32

我认为对于所有3种方法,在多列上进行排序实际上是一个“骗局”,因为url变得越来越大且更难以管理-因此,我正在考虑转向基于表单的页面/排序参数的原因之一。对于第二个问题,我认为像产品ID这样的唯一持久标识符与产品的暂态列表之间存在根本的概念差异。对于已删除的产品,例如“该产品在系统中不存在”告诉您有关该产品的一些具体信息。

– Steve Willcock
09年4月22日在12:44

从路由中删除所有分页和排序信息是很好的。并将其推送到POST参数中是不好的。你好?问题是关于REST。我们不使用POST只是为了使REST中的URL更短。动词是有道理的。

–temoto
09年12月4日在19:11

就个人而言,我不会在查询中使用表单参数,因为它几乎需要POST或PUT HTTP方法(因为请求中现在有一个正文)。 GET在我看来更喜欢使用更合适的方法,因为POST和PUT都暗示着修改资源。因此,当需要按多列进行排序时,我将向URL添加更多查询参数。

– Paul D. Eden
2012-2-17在16:37



#10 楼

我倾向于同意“页面”并不是真正的资源。另一方面,选项3更干净,更易于阅读,并且用户更容易猜出,甚至在必要时也可以键入。我在选项1和3之间感到困惑,但是没有任何理由不使用选项3。

尽管它们看起来不错,但正如有人提到的,使用隐藏参数的缺点是,与查询字符串或网址段不同的是,用户无法添加书签或直接链接到特定页面。取决于应用程序,这可能是问题,也可能不是问题,只是一些需要注意的问题。

评论


关于您更容易猜到的提法,这无关紧要。如果构建超媒体API,则用户永远不要猜测URI。

–J.R. Garcia
2012年7月25日的16:00

#11 楼

我之前使用过解决方案3(我写了很多django应用)。而且我认为这没有任何问题。它与其他两个一样可生成(以防您需要进行大量刮擦等操作),并且看起来更干净。另外,您的用户可以猜测url(如果它是面向公众的应用程序),并且人们喜欢能够直接转到他们想要的地方,并且url猜测可以增强功能。

#12 楼

我在我的项目中使用以下网址:或者,如果我需要更大的灵活性,请使用:

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


#13 楼

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

RecordKey是包含顺序表的表的列DB中的值。它用于一次仅从DB提取一页数据。
pageSize用于确定要提取多少条记录。 sort用于按升序或降序对记录进行排序。