我有兴趣将直接REST接口暴露给JSON文档集合(想想CouchDB或Persevere).我遇到的问题是GET
如果集合很大,如何处理集合根上的操作.
作为一个例子假装我暴露StackOverflow的Questions
表,其中每一行都作为文档公开(不一定是这样的表,只是一个相当大的'文档'集合的具体例子).收集将在可提供/db/questions
与通常的CRUD API GET /db/questions/XXX
,PUT /db/questions/XXX
,POST /db/questions
是在玩.获取整个集合的标准方法是,GET /db/questions
但如果天真地将每一行转储为JSON对象,那么您将获得相当大的下载和服务器上的大量工作.
解决方案当然是分页.Dojo 通过一个巧妙的RFC2616兼容扩展使用带有自定义范围单元的标头,在其JsonRestStore中解决了这个问题.结果是只返回请求的范围.这种方法优于查询参数的优点是它为查询留下了查询字符串(例如,或某些,以及是的,它被编码).Range
items
206 Partial Content
GET /db/questions/?score>200
%3E
这种方法完全涵盖了我想要的行为.问题是RFC 2616指定206响应(强调我的):
该请求必须具有包含Range头部字段(部分14.35),指示在期望的范围,并且可以具有包括一个If-Range头字段(部分14.27),以使所述请求为条件.
这在标题使用标题的上下文中是有意义的,但是是一个问题因为我希望206响应是默认处理天真客户端/随机人员探索.
我已经仔细研究了RFC,寻找解决方案,但对我的解决方案一直不满意,并对SO对这个问题的看法感兴趣.
我有过的想法:
返回200
了Content-Range
头! - 我不认为这是错的,但我更喜欢一个更明显的指标,即响应只是部分内容.
返回400 Range Required
- 所需标头没有特殊的400响应代码,因此必须手动使用和读取默认错误.这也使得通过Web浏览器(或像Resty这样的其他客户端)进行探索变得更加困难.
使用查询参数 - 标准方法,但我希望允许查询la Persevere,这会切入查询命名空间.
回来206
吧! - 我认为大多数客户都不会惊慌失措,但我宁愿不反对RFC中的MUST
扩展规格!返回266 Partial Content
- 行为与206完全相同,但是响应于不得包含Range
标题的请求.我认为266足够高,我不应该遇到碰撞问题,这对我来说很有意义,但我不清楚这是否被视为禁忌.
我认为这是一个相当普遍的问题,我希望以某种事实上的方式看待这一点,所以我或其他人不会重新发明轮子.
当集合很大时,通过HTTP公开完整集合的最佳方法是什么?
我真的不同意你们中的一些人.我已经为我的REST服务工作了几个星期.我最终做的很简单.我的解决方案只对REST人们称之为集合的内容有意义.
客户端必须包含一个"范围"标题,以指示他所需的集合的哪个部分,或者当请求的集合太大而无法在单个往返中检索时,准备好处理413 REQUESTED ENTITY TOOALOOAL错误.
服务器发送206 PARTIAL CONTENT响应,Content-Range标头指定已发送资源的哪个部分,以及ETag标头以标识集合的当前版本.我通常使用类似Facebook的ETag {last_modification_timestamp} - {resource_id},我认为集合的ETag是它包含的最近修改过的资源的ETag.
要请求集合的特定部分,客户端必须使用"Range"标头,并使用从先前执行的请求获得的集合的ETag填充"If-Match"标头,以获取同一集合的其他部分.因此,服务器可以在发送所请求的部分之前验证集合是否未更改.如果存在更新版本,则返回412 PRECONDITION FAILED响应以邀请客户端从头开始检索集合.这是必要的,因为它可能意味着可能在当前请求的部分之前或之后添加或删除了某些资源.
我使用ETag/If-Match与Last-Modified/If-Unmodified-Since一起优化缓存.浏览器和代理可能依赖于它们中的一个或两个来进行缓存算法.
我认为URL应该是干净的,除非它包含搜索/过滤查询.如果你考虑一下,搜索只不过是一个集合的局部视图.而不是汽车/搜索?q =宝马类型的URL,我们应该看到更多的汽车?制造商=宝马.
我的直觉是HTTP范围扩展不是为您的用例而设计的,因此您不应该尝试.部分响应意味着206
,并且206
只有在客户要求时才发送.
您可能想要考虑一种不同的方法,例如在Atom中使用的方法(设计中的表示可能是部分的,并且返回状态200
,并且可能是分页链接).请参阅RFC 4287和RFC 5005.
您还可以返回Accept-Ranges
,并Content-Ranges
与200
响应代码.这两个响应标头为您提供了足够的信息来推断206
响应代码明确提供的相同信息.
我会Range
用于分页,并让它简单地返回200
一个平原GET
.
这感觉100%RESTful 并且不会使浏览变得更加困难.
编辑:我写了一篇关于此的博客文章:http://otac0n.com/blog/2012/11/21/range-header-i-choose-you.html
如果有多个回复页面,并且您不想立即提供整个集合,这是否意味着有多个选择?
在请求中/db/questions
,返回300 Multiple Choices
带有Link
指定如何到达每个页面的标头以及带有URL列表的JSON对象或HTML页面.
Link: <>; rel="http://paged.collection.example/relation/paged" Link: <>; rel="http://paged.collection.example/relation/paged" ...
Link
每个结果页面都有一个标题(空字符串表示当前URL,每个页面的URL相同,只是使用不同的范围访问),并且根据即将发布的Link
规范将关系定义为自定义的.这种关系可以解释您的自定义266
或违反206
.这些标题是您的机器可读版本,因为您的所有示例都需要了解客户端.
(如果您坚持使用"范围"路线,我相信您自己的2xx
返回代码,正如您所描述的那样,将是最好的行为.您应该为您的应用程序执行此操作,这样["HTTP状态代码是可扩展的. "],你有充分的理由.)
300 Multiple Choices
说你应该为身体提供一种让用户代理选择的方式.如果您的客户理解,则应使用Link
标题.如果是用户手动浏览,可能是一个HTML页面,其中包含指向特殊"分页"根资源的链接,该资源可以根据URL处理该特定页面的呈现? /humanpage/1/db/questions
还是像那样可怕的东西?
对Richard Levasseur的帖子的评论提醒我一个额外的选择:Accept
标题(第14.1节).回到oEmbed规范出来的时候,我想知道为什么没有完全使用HTTP,并且使用它们编写了一个替代方案.
保留初始天真HTTP 300 Multiple Choices
的Link
标题和HTML页面GET
,但不要使用范围,让新的分页关系定义Accept
标头的使用.您的后续HTTP请求可能如下所示:
GET /db/questions HTTP/1.1 Host: paged.collection.example Accept: application/json;PagingSpec=1.0;page=1
该Accept
标题允许您定义该类型(你的页面数)可接受的内容类型(JSON的回报),再加上可扩展的参数.从我的oEmbed文章中重复我的笔记(这里不能链接到它,我会在我的个人资料中列出),你可以非常明确地提供一个规范/关系版本,以防你需要重新定义page
参数意味着什么在将来.