首页 文章

在休息集合中分页

提问于
浏览
121

我有兴趣将直接REST接口暴露给JSON文档集合(想想CouchDBPersevere) . 我遇到的问题是如果集合很大,如何处理集合根上的 GET 操作 .

作为一个例子假装我将每行作为文档公开(不一定是这样的表,只是一个相当大的'documents'集合的具体例子) . 该集合将在 /db/questions 提供,通常的CRUD api GET /db/questions/XXXPUT /db/questions/XXXPOST /db/questions 正在播放中 . 获取整个集合的标准方法是 GET /db/questions 但是如果天真地将每一行转储为JSON对象,那么您将获得相当大的下载和服务器上的大量工作 .

解决方案当然是分页 . Dojo通过一个聪明的RFC2616兼容扩展使用带有自定义范围单元 itemsRange 标头,在_1419508中解决了这个问题 . 结果是 206 Partial Content 仅返回请求的范围 . 这种方法优于查询参数的优点是它为查询留下了查询字符串(例如 GET /db/questions/?score>200 或某些,以及是的,它们被编码为 %3E ) .

这种方法完全涵盖了我想要的行为 . 问题是RFC 2616指定206响应(强调我的):

请求必须包含一个Range头字段(第14.35节),表示所需的范围,并且可能包含一个If-Range头字段(第14.27节)以使请求成为条件 .

这在 Headers 使用 Headers 的上下文中是有意义的,但是是一个问题因为我希望206响应是默认处理天真客户端/随机人员探索 .

我已经仔细研究了RFC,寻找解决方案,但对我的解决方案一直不满意,并对SO对这个问题的看法感兴趣 .

我有过的想法:

  • 使用 Content-Range Headers 返回 200 ! - 如果一个更明显的指标表明响应只是部分内容,我更不愿意 .

  • 返回 400 Range Required - 所需 Headers 没有特殊的400响应代码,因此必须手动使用和读取默认错误 . 这也使得通过Web浏览器(或像Resty这样的其他客户端)进行探索变得更加困难 .

  • 使用查询参数 - 标准方法,但我希望允许查询la Persevere,这会切入查询命名空间 .

  • 只需返回 206 ! - 我认为大多数客户都不会反对RFC中的MUST

  • 扩展规格!返回 266 Partial Content - 行为与206完全相同,但是响应的是一个不得包含 Range 标头的请求 . 我认为266足够高,我不应该知道这是否被视为禁忌 .

我认为这是一个相当普遍的问题,我希望以某种事实上的方式看待这一点,所以我或其他人不会重新发明轮子 .

当集合很大时,通过HTTP公开完整集合的最佳方法是什么?

12 回答

  • 1

    在我看来,最好的方法是将范围包含为查询参数 . 例如, GET /db/questions/?date>mindate&date<maxdate . 在没有查询参数的GET到/ db / questions /后,使用 Location: /db/questions/?query-parameters-to-retrieve-the-default-page 返回303 . 然后提供一个不同的URL,消费者通过该URL来获取有关集合的统计信息(例如,如果他/她想要整个集合,则使用哪些查询参数);

  • 3

    我的直觉是HTTP范围扩展不是尝试 . 部分响应意味着 206 ,并且只有在客户要求时才能发送 206 .

    您可能需要考虑一种不同的方法,例如Atom中的一种方法(设计中的表示可能是部分的,并且返回状态为 200 ,可能还有分页链接) . 见RFC 4287RFC 5005 .

  • 0

    我真的不同意你们中的一些人 . 我已经为我的REST服务工作了几个星期 . 我最终做的很简单 . 我的解决方案只对REST人们称之为集合的内容有意义 .

    客户端必须包含一个“范围” Headers ,以指示他所需的集合的哪个部分,或者当请求的集合太大而无法在单个往返中检索时,准备好处理413 REQUESTED ENTITY TOOALOOAL错误 .

    服务器发送206 PARTIAL CONTENT响应,Content-Range标头指定已发送资源的哪个部分,以及ETag标头以标识集合的当前版本 . 我通常使用类似Facebook的ETag - ,我认为集合的ETag是它包含的最近修改过的资源的ETag .

    要请求集合的特定部分,客户端必须使用“Range” Headers ,并使用“ETag”填充“If-Match” Headers . 从先前执行的获取相同集合的其他部分的请求获得的集合 . 因此,服务器可以在发送所请求的部分之前验证集合是否未更改 . 如果存在更新版本,则返回412 PRECONDITION FAILED响应以邀请客户端从头开始检索集合 . 这是必要的,因为它可能意味着可能在当前请求的部分之前或之后添加或删除了某些资源 .

    我使用ETag / If-Match与Last-Modified / If-Unmodified-Since一起优化缓存 . 浏览器和代理可能依赖于它们中的一个或两个来进行缓存算法 .

    我认为URL应该是干净的,除非它包含搜索/过滤查询 . 如果你考虑一下,搜索只不过是一个集合的局部视图 . 而不是汽车/搜索?q =宝马类型的URL,我们应该看到更多的汽车?制造商=宝马 .

  • 1

    虽然可以为此目的使用Range标头,但我认为这不是意图 . 它似乎是为处理片状连接以及限制数据而设计的(因此如果缺少某些内容或者大小太大而无法处理,客户端可以请求部分请求) . 你正在破坏分页可能在通信层用于其他目的 . 处理分页的“正确”方式是返回的类型 . 您应该返回一个新类型,而不是返回问题对象 .

    所以,如果问题是这样的:

    <questions> <question index=1></question> <question index=2></question> ... </questions>

    新类型可能是这样的:

    <questionPage> <startIndex>50</startIndex> <returnedCount>10</returnedCount> <totalCount>1203</totalCount> <questions> <question index=50></question> <question index=51></question> .. </questions> <questionPage>

    当然,您可以控制媒体类型,这样您就可以使“页面”成为适合您需求的格式 . 如果你使make是通用的,你可以在客户端上有一个解析器来处理所有类型的分页 . 我认为这更符合HTTP规范的精神,而不是为其他东西捏造Range参数 .

  • 5

    如果有多个回复页面,并且您不想立即提供整个集合,这是否意味着有多个选择?

    /db/questions 的请求中,返回带有 Link Headers 的 300 Multiple Choices ,指定如何访问每个页面以及带有URL列表的JSON对象或HTML页面 .

    Link: <>; rel="http://paged.collection.example/relation/paged"
    Link: <>; rel="http://paged.collection.example/relation/paged"
    ...
    

    每个结果页面都有一个 Link 标头(空字符串表示当前URL,每个页面的URL相同,只是使用不同的范围访问),并且关系定义为a custom one per the upcoming Link spec . 这种关系可以解释您的自定义 266 ,或者您违反了 206 . 这些 Headers 是您的机器可读版本,因为您的所有示例都需要了解客户端 .

    (如果你坚持使用"range"路由,我相信你自己的 2xx 返回代码,正如你所描述的那样,这将是最好的行为 . 你应该为你的应用程序这样做["HTTP status codes are extensible."],你有充分的理由 . )

    300 Multiple Choices 说你还应该为身体提供一种让用户代理选择的方式 . 如果您的客户理解,则应使用 Link 标头 . 如果是用户手动浏览,可能是一个HTML页面,其中包含指向特殊"paged"根资源的链接,该资源可以根据URL处理该特定页面的呈现? /humanpage/1/db/questions 还是像那样可怕的东西?


    对Richard Levasseur的帖子的评论提醒我一个额外的选择: Accept Headers (第14.1节) . 回到oEmbed规范出来的时候,我想知道为什么没有完全使用HTTP,并且使用它们编写了一个替代方案 .

    保留 300 Multiple ChoicesLink 标头和初始天真HTTP GET 的HTML页面,但是不要使用范围,让新的分页关系定义 Accept 标头的使用 . 您的后续HTTP请求可能如下所示:

    GET /db/questions HTTP/1.1
    Host: paged.collection.example
    Accept: application/json;PagingSpec=1.0;page=1
    

    Accept 标头允许您定义可接受的内容类型(您的JSON返回),以及该类型的可扩展参数(您的页码) . 从我的oEmbed写作中重复我的笔记(可以在我的 Profiles 中列出它),你可以非常明确地提供一个规范/关系版本,以防你需要重新定义 page 参数在将来意味着什么 .

  • 32

    您仍然可以使用 200 响应代码返回 Accept-RangesContent-Ranges . 这两个响应标头为您提供了足够的信息来推断显式提供的相同信息 .

    我会使用 Range 进行分页,并让它只返回一个 200 为普通的 GET .

    这感觉100%RESTful and 不会让浏览变得更加困难 .

    编辑:我写了一篇关于此的博文:http://otac0n.com/blog/2012/11/21/range-header-i-choose-you.html

  • 3

    您可能会考虑使用像Atom Feed Protocol这样的模型,因为它有一个理智的HTTP模型集合以及如何操作它们(疯狂意味着WebDAV) .

    Atom Publishing Protocol定义了集合模型和REST操作,您可以使用RFC 5005 - Feed Paging and Archiving来浏览大集合 .

    从Atom XML切换到JSON内容不应该影响这个想法 .

  • 1

    编辑:

    在考虑了一下之后,我倾向于同意Range Headers 不适合分页 . 逻辑是,Range标头用于服务器的响应,而不是应用程序 . 如果您提供了100兆字节的结果,但服务器(或客户端)一次只能处理1兆字节,那就是Range标头的用途 .

    我也认为是一个子集资源是它自己的资源(类似于关系代数 . ),所以它值得在URL中表示 .

    所以基本上,我放弃了关于使用 Headers 的原始答案(下面) .


    我认为您回答了自己的问题,或多或少 - 使用内容范围返回200或206,并可选择使用查询参数 . 我会嗅探用户代理和内容类型,并根据这些检查查询参数 . 否则,需要范围 Headers .

    你本质上有相互冲突的目标 - 让人们使用他们的浏览器进行探索(不容易自定义 Headers ),或者强迫人们使用可以设置 Headers 的特殊客户端(不允许他们探索) .

    您可以根据请求向他们提供特殊客户端 - 如果它看起来像普通浏览器,请发送一个小的ajax应用程序来呈现页面并设置必要的 Headers .

    当然,还有关于URL是否应包含此类事物的所有必要状态的争论 . 使用 Headers 指定范围可被某些人视为“不安宁” .

    顺便说一句,如果服务器可以使用“Can-Specify:Header1,header2”标头进行响应,并且Web浏览器会显示UI,那么用户可以根据需要填写值,这样会很好 .

  • 21

    我认为这里的真正问题是规范中没有任何内容告诉我们在面对413时请求如何进行自动重定向 - 请求实体太大 .

    我最近在解决这个问题时遇到了困难,我在RESTful Web Services一书中寻找灵感 . 就个人而言,由于 Headers 要求,我认为206不合适 . 我的想法也让我达到了300,但我认为对于不同的mime类型更多,所以我查看了Richardson和Ruby在附录B,第377页中对该主题的看法 . 他们建议服务器选择首选表示并用200发送回来,基本上忽略了它应该是300的概念 .

    这也与我们从原子中获得的下一个资源的链接概念相吻合 . 我实现的解决方案是将“next”和“previous”键添加到我发回的json Map 中并完成它 .

    后来我开始思考可能要做的事情是将307 - 临时重定向发送到类似/ db / questions / 1,25的链接 - 将原始URI保留为规范资源名称,但它可以让您一个适当命名的下属资源 . 这是我想从413中看到的行为,但307似乎是一个很好的妥协 . 尽管如此,还没有在代码中尝试过这个 . 更好的方法是将重定向重定向到包含最近提问的实际ID的URL . 例如,如果每个问题都有一个整数ID,并且系统中有100个问题,并且您希望显示最近的十个问题,那么对/ db / questions的请求应该是307d / db / questions / 100,91

    这是一个非常好的问题,谢谢你的提问 . 你向我证实,我花了好几天时间思考它并不疯狂 .

  • 5

    您可以检测 Range 标头,并模拟Dojo(如果存在),并模仿Atom(如果不存在) . 在我看来,这巧妙地划分了用例 . 如果您正在响应应用程序中的REST查询,则希望使用 Range 标头对其进行格式化 . 如果您正在响应休闲浏览器,那么如果您返回分页链接,它将让该工具提供一种简单的方法来探索该集合 .

  • 3

    范围 Headers 的一个大问题是很多公司代理会过滤它们 . 我建议改用查询参数 .

  • 0

    随着rfc723xunregistered range units do go against an explicit recommendation in the spec 的出版 . 考虑rfc7233(弃用rfc2616):

    New range units ought to be registered with IANA”(以及对HTTP Range Unit Registry的引用) .

相关问题