超长 URI 的 REST 最佳实践

Posted

技术标签:

【中文标题】超长 URI 的 REST 最佳实践【英文标题】:REST best-practice for overlong URIs 【发布时间】:2011-01-05 11:14:34 【问题描述】:

我有 REST 服务,应该通过 GET 接收很长的查询。例如,我想查询具有 许多 个地理坐标的服务,以了解有关所有这些坐标的信息。

1) 我的第一个想法是使用长 URI 和 servlet 容器的 increase the max URI length。

看起来像这样:

GET http://some.test/myresource?query=really big JSON object

但是,由于旧的代理服务器,超过 2 KB 的 URI 似乎不可靠(对吗?)。

2) 我的解决方法是先通过 POST 创建一个临时资源,然后在实际的 GET 请求中使用该资源的 URI 作为参数。看起来像这样:

POST http://some.test/temp
Request Body: really big JSON object

201 Created Location: http://some.test/temp/12309871

GET http://some.test/myresource?query=http://some.test/temp/12309871

3) 使用 GET 请求的正文。我已经阅读了使用 GET 请求的主体进行查询是否是个好主意的问题的答案,共识是:不。甚至 Roy Fielding 都说这是bad idea。

4) 另一种方法是将 POST 解释为“创建查询结果资源”并在请求后删除该资源。但我认为这不是 RESTful 并且是个坏主意。

有没有更好的方法来处理带有 GET 请求的大查询?

【问题讨论】:

是的,带有PUT。请参阅我的答案以获得更深入的解释 【参考方案1】:

使用PUT

为什么?原因如下:

仅仅因为动词PUT“可能更新”资源,并不意味着它将或必须改变资源的底层状态。 PUT 的 API 端不应创建新的资源标识符 (url)。是的,从技术上讲,带有客户端指定标识符的 PUT 是可能的,但在这种情况下,您正在访问现有资源。 PUTGET 类似,它应该是幂等的,这意味着无论您多久调用一次,请求的结果都将始终相同,并且没有副作用。

PUT 表示您将资源数据放入现有资源。就文档/博客文章世界中的文章或帖子而言,这就像将某个文档的新修订版上传到现有资源 URL。如果您将相同的修订上传到相同的 URL,则您返回的资源不会发生任何变化。

在您的情况下,地理数据是您正在上传的一些新资源数据,每次您发出相同的请求时,您返回的结果应该是相同的。

GET 动词用于请求的更纯粹的方法可能是:

为查询资源类型创建端点 将查询详细信息的 JSON 集发布到查询资源端点并获取查询资源的标识符(假设它返回的查询 ID 为 123) 向获取请求提交查询标识符http://some.test/myresource?query_id=123 删除查询资源123

我认为纯方法比在正文中使用 PUT 和查询资源数据的开销要大得多。

【讨论】:

我认为这个答案是合理的。它是务实的,并且与基本的 REST 原则保持一致。【参考方案2】:

我认为 REST 的全部意义在于处理“文档”(或类似的东西)。请求的 URI 部分用于唯一地识别要处理的资源。相比之下,正文部分是文档的“内容”部分。

因此,请使用请求的“正文”部分。

另请注意,“GET”请求的语义不应该用于“PUTTING”或“POSTING”文档(与上面的“查询”示例相关的评论,它似乎“创建”了一个对象) .

无论如何,正如您所指出的,URI 部分是有限的(我相信这是有充分理由的)。


如果您关心缓存,那么使用 ETag/Last-Modified 字段(结合“条件 GET”有助于实现此目的。

【讨论】:

这一切都在规则之内,但是使用 GET 请求的主体有一些严重的缺点。一些在这个 SO 帖子中进行了解释:***.com/questions/978061 如果您指的是缓存以降低传输成本:无论如何,您都应该使用用于此目的的 Etag/Last-modified 标头字段。 “条件 GET”有助于实现此目的。【参考方案3】:

第二个选项略有不同。为自己创建一个名为 QueryMaker 的处理器资源。将您的参数发布给它,让它将您重定向到一个临时查询资源,该资源将返回您的结果。

POST /QueryMaker
Body: Big Json representation of parameters

303: See Other
Location: http://example.org/TemporaryQueries/123213

【讨论】:

【参考方案4】:

如果您使用 GET 请求发送大对象,则说明您没有正确使用 REST。

GET 应该用于检索 资源(通过某种独特的 标识符) POST 应该用于 创建资源(与内容 在体内) PUT 应用于 更新资源(使用 正文中的内容) DELETE 应该用于删除资源

如果您遵循这些准则,您将永远不必拥有过长的 URI。

这里有一些最佳实践 REST 指南:http://www.xml.com/pub/a/2004/08/11/rest.html

【讨论】:

您可能需要为通过 GET 检索资源的方式指定参数(例如,“包含空字段”或“压缩数据”或“打开以供编辑”)这里的问题是,如何传递这些参数,而不是使用什么 http 动词。 指定参数很好,但是如果你需要大约 2KB 的参数,那么你的 REST 服务设计得很糟糕! 我会认真考虑您的服务设计。如果您真的需要 >2 Kb 的参数,那么我认为您正在做的事情是相当明智的 - 即将您的 params 对象视为不同类型的资源,您可以在此处创建并使用这些资源。 POST 绝对不是为了更新资源。它可以这样使用,但这不是它的意思。它的意思是“嘿,我要发布的资源,拿走我给你的东西,然后用它做点什么,我会等待回复”。没有别的了。 这里有一个简单的 GET 示例,它有一个很长的参数 - 您传入一个 SQL 选择,它将确定要返回的数据行。 SQL 选择很容易超过 2K。但它是一个简单的请求,为查询返回选择的数据。所以它应该是 GET,但不能。【参考方案5】:

开放Web上URL长度的最大限制其实是IE,constraints them to 2083 characters。

一些代理(例如,除了最新版本的 Squid 之外的所有代理)会将它们限制为大约 4k,尽管这是 moving towards 8k slowly。

您的#2 解决方法是一个很好的方法,具体取决于您的用例。

某些实现可能允许在 GET 上发送主体,而其他实现则不允许,因此从互操作性和理论上的原因来看,这不是一个好主意。最重要的是,缓存如何知道将什么用作键?

【讨论】:

【参考方案6】:

您不能只发送带有 GET 请求正文的大 JSON 数据,而不是创建临时资源吗?

虽然它不是 100% 洁净的,但我发现它与 firefox 和 IE 和 IMO 配合得很好,但查询字符串不优雅,并且通常会暴露不属于 URI 的实现细节。如果您需要最新数据,请确保添加缓存破坏者查询字符串参数,因为服务器在确定是否可以返回缓存响应时会忽略数据。

请参阅here,了解在 GET 请求正文中填充数据的优缺点。

【讨论】:

感谢您提供讨论链接。似乎 GET + 请求正文不是一个好主意。 GET 技术上没有正文,但 REST 技术上也没有 SEARCH,所以.... +1

以上是关于超长 URI 的 REST 最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

Restful最佳实践

三分钟彻底了解Restful最佳实践

Restful三分钟彻底了解Restful最佳实践

Restful三分钟彻底了解Restful最佳实践

Restful Api 最佳实践

执行3小时超长SQL的分析优化过程:从索引遇见IS NULL,到最佳实践