ChatGPT解决这个技术问题 Extra ChatGPT

REST Web 应用程序中的分页

这是对 this question 的更通用的重新表述(消除了 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
我个人认为使用这种方法没有问题,但有人警告我这不是一个好方法(他没有给出理由,所以如果你知道为什么不推荐,请告诉我)

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

这是一个很好的问题。
额外的问题:人们通常如何指定页面大小?
不要忘记矩阵参数 w3.org/DesignIssues/MatrixURIs.html

V
Vinod

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


+1:查询字符串不是一流的资源标识符;他们只是澄清资源的排序和分组。
@S.Lott 请求 资源。您所谓的“一流资源”在 section 5.2.1.1 of his dissertation 中由 Fielding 定义为 。此外,在同一部分中,Fielding 将源代码文件的最新版本 作为资源示例。这怎么可能是一个资源,但 最新的 10 个产品 是“产品资源请求的属性”?我知道您的观点更实用,但我认为它不那么 RESTful。
请注意,我的评论并不意味着我不同意在 URL 上使用查询字符串的选择:只要 API 是超媒体驱动的,两者都是可行的解决方案,正如@RichApodaca 在他的回答中提到的那样。我只是指出,从 REST 的角度来看,页面应该被视为一种资源。
B
Ben

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

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

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

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


好吧,但是如果您删除某些内容,则同一个 URI 上不应该有其他内容。如果您删除页面 X 的所有产品 - 页面 X 可能仍然有效,但现在包含页面 X + 1 中的产品。因此,如果您在“产品资源视图”中看到页面 X 的 URI 已成为页面 X + 1 的 URI ”。
> 如果您将页面视为资源,这是一个非常好的解决方案,因为对第 2 页的查询总是会产生第 2 页。这是否有意义?无论您是什么资源,相同的 URL(任何提到第 2 页的 URL)都将始终产生第 2 页。
将页面视为资源可能应该引入 POST /foo/page 来创建新页面,对吗?
您的答案顺利地转到“正确的解决方案是 1”,但没有说明。
在我看来,页面是一个浮动的概念,与底层域无关。因此不应被视为一种资源。我的意思是浮动的意思是它是流动的,页面的概念随着上下文的变化而变化;你的 API 的一个用户可能是一个移动应用程序,每页只能消费 2 个产品,而另一个用户是一个机器应用程序,可以消费整个该死的列表。简而言之,页面是底层域实体(产品)的“表示”,不应作为 URL 的一部分包含在内;仅作为查询参数。
t
temoto

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”的微分问题。


标题乍一看可能看起来不错,但它们不允许共享页面(例如通过复制 url)。因此,对于 ajax 请求,它们可能是一个不错的解决方案(因为 ajax 修改的页面无论如何都不能在当前状态下共享),但我不会将它们用于常规分页。
Range 标头仅适用于字节范围。请参阅 [HTTP 标头规范](w3.org/Protocols/rfc2616/rfc2616-sec14.html ),第 14.35 节。
@ChrisWestin w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 HTTP/1.1 在 Range(第 14.35 节)和 Content-Range(第 14.16 节)标头字段中使用范围单位。 range-unit = bytes-unit | other-range-unit 也许您指的是 The only range unit defined by HTTP/1.1 is "bytes". HTTP/1.1 implementations MAY ignore ranges specified using other units. 这与您的陈述不同。
@Markus 我无法想象您共享休息 api 资源时的用例:)
@JakubKnejzlik 共享不是问题,但是使用 HTTP 标头进行分页会阻止使用 HATEOAS 链接进行分页。
R
Rich Apodaca

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

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

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

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


很好地提醒您在 REST Web 服务中使用超媒体(如链接)。
J
John Snyders

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

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


+1 我认为你是对的,我将使用查询参数(选项 1)
“我不觉得 URL 很难记住”。这种观察在 REST 应用程序中是没有用的,因为这些应用程序通常应该只有一个书签……如果用户(或客户端应用程序)试图“记住”该 URL,这是 API 不平静的一个好兆头。
T
TEHEK

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

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

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

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


这是两种不同的顺序。第一个按日期降序排序,仅按名称升序打破平局;第二个按名称升序排序,仅按日期降序打破平局。
事实上,这里给出的两个示例 URL 不仅在书写上有所不同,而且在含义上也有所不同。由于表示路径,因此不能保证您在先左转和后右转时会找到相同的东西,反之亦然。话虽如此,作为 URL 路径部分的排序参数比 URL 参数具有形式上的优势,URL 参数应该可以交换而不改变整体含义,但确实受到编码陷阱的影响,如这里所说。
M
Mario Arturo

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

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 中有一些针对更具体案例的其他建议。


S
Sorter

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

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

限制:用于项目计数。

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

offset = offset + limit

下一页。

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

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


S
Steve Willcock

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

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

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

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

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


第一个问题,对多列进行排序,在我看来适用于所有 3 种方法。所以这对他们中的任何一个人来说都不是真正的利弊。关于第二个问题:这不会发生在任何资源上吗?例如,也可以编辑/删除产品。
我认为对多列进行排序对于所有 3 种方法来说确实是一个“骗局”,因为 url 变得更大且更难以管理 - 因此我正在考虑转向基于表单的页面/排序参数的一个原因。对于第二个问题,我认为像产品 ID 这样的唯一持久标识符与产品的临时列表之间存在根本的概念差异。对于已删除的产品,一条消息(例如“系统中不存在该产品”)会告诉您有关该产品的具体信息。
从路由中删除所有分页和排序信息是好的。并将其推入 POST 参数是不好的。你好?问题是关于 REST 的。我们不使用 POST 来缩短 REST 中的 URL。动词是有道理的。
就个人而言,我不会将表单参数用于查询,因为它几乎需要 POST 或 PUT HTTP 方法(因为现在请求中有一个正文)。 GET 在我看来更适合使用,因为 POST 和 PUT 都意味着修改资源。因此,当需要按多列排序时,我会向 URL 添加更多查询参数。
i
insane.dreamer

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

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


关于您提到更容易猜测,这应该无关紧要。如果构建超媒体 API,用户永远不必猜测 URI。
A
Alex

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


E
Eugene

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

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

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

http://application/products?skip=20&limit=20&sort=+field1-field2

S
Susanta Ghosh

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

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