12

我正在开发一个 REST API。关键对象(“名词”)是“项目”,每个项目都有一个唯一的 ID。例如,获取 ID 为 foo 的项目的信息:

GET http://api.example.com/v1/item/foo

可以创建新项目,但客户无法选择 ID。相反,客户端会发送一些代表该项目的信息。所以要创建一个新项目:

POST http://api.example.com/v1/item/
hello=world&hokey=pokey

使用该命令,服务器会检查我们是否已经有 info 的项目hello=world&hokey=pokey。所以这里有两种情况。

情况一:商品不存在;它被创建了。这个案子很简单。

201 Created
Location: http://api.example.com/v1/item/bar

情况2:该项目已经存在。这就是我苦苦挣扎的地方……不确定要使用的最佳重定向代码是什么。

301 Moved Permanently? 302 Found? 303 See Other? 307 Temporary Redirect? Location: http://api.example.com/v1/item/foo

我研究了Wikipedia 描述RFC 2616,但这些似乎都不是完美的。以下是我在这种情况下寻找的具体特征:

重定向是永久性的,因为 ID 永远不会改变。因此,为了提高效率,客户端可以并且应该直接向 ID 端点发出所有未来的请求。这表明 301,因为其他三个是临时的。

重定向应该使用 GET,即使这个请求是 POST。这表明 303,因为从技术上讲,所有其他人都应该重用 POST 方法。在实践中,浏览器将使用 GET 获取 301 和 302,但这是一个 REST API,而不是普通用户在浏览器中使用的网站。

它应该是广泛可用且易于使用的。具体来说,303 是 HTTP/1.1,而 301 和 302 是 HTTP/1.0。我不确定这是多大的问题。

在这一点上,我倾向于 303 只是为了在语义上正确(使用 GET,不要重新发布)并且只是在“临时”部分吸收它。但我不确定 302 是否会更好,因为实际上它与 303 的行为相同,但不需要 HTTP/1.1。但是如果我沿着这条线走,我想知道301是否因为同样的原因加上“永久”部分更好。

想法赞赏!


编辑:让我试着用一个更具体的例子来更好地解释这个“获取或创建”操作的语义:URL 缩短。无论如何,这实际上更接近我的应用程序。

对于 URL 缩短器,迄今为止最常见的操作是按 ID 检索。例如对于http://bit.ly/4Agih5,bit.ly 接收到 4Agih5 的 ID 并且必须将用户重定向到其相应的 URL。

bit.ly 已经有一个 API,但它并不是真正的 RESTful。为了举例,让我编写一个更 RESTful 的 API。例如,查询 ID 可能会返回有关它的各种信息(例如分析):

GET http://api.bit.ly/item/4Agih5

现在如果我想提交一个新的 URL 到 bit.ly 来缩短,我事先不知道我的 URL 的 ID,所以我不能使用 PUT。我会改用 POST 。

POST http://api.bit.ly/item/
url=http://stackoverflow.com/(但已编码)

如果 bit.ly 之前没有看到此 URL,它将为其创建一个新 ID,并通过 201 Created 将我重定向到新 ID。但如果它看到了那个 URL,它仍然会重定向我而不做任何更改。这样,我可以通过任何一种方式点击该重定向位置以获取缩短 URL 上的信息/元数据。

就像这个 URL 缩短示例一样,在我的应用程序中,冲突并不重要。一个 URL 对应一个 ID,仅此而已。所以 URL 之前是否被缩短并不重要;无论哪种方式,将客户端指向它的 ID 都是有意义的,无论该 ID 是否需要首先创建。

所以我可能不会改变这种方法;我只是在询问最好的重定向方法。谢谢!

4

5 回答 5

11

我认为您在这种情况下苦苦挣扎的原因之一是因为(除非我们缺少一些关键信息)交互不是很合乎逻辑。

让我解释一下我为什么这么认为。最初的前提是用户请求创建一些东西,并为他们希望创建的资源提供了一些关键信息。

然后,您声明如果该关键信息引用了现有对象,那么您希望返回该对象。问题是用户不希望检索他们希望创建新对象的现有对象。如果他们因为资源已经存在或存在密钥冲突而无法创建资源,则应告知用户这一事实。

当用户尝试创建新对象时选择检索现有对象似乎是一种误导性方法。

如果资源已经存在并在实体主体中包含指向现有对象的链接,则一种替代方法可能是返回 404 Bad 请求。客户端应用程序可以选择吞下错误的请求错误,并简单地点击指向现有实体的链接,这样可以向用户隐藏问题。这将是客户端应用程序的选择,但至少服务器的行为方式很清晰。


基于新示例,让我提出一种完全不同的方法。它可能不适用于您的情况,因为魔鬼总是在细节中,但也许会有所帮助。

从客户端的角度来看,它对服务器是创建一个新的缩短 URL 还是撤回一个现有的 URL 真的没有兴趣。实际上,服务器是否需要生成新的 ID 是一个完全隐藏的实现细节。
隐藏创建过程可能非常有价值。也许服务器可以提前预测,很快就会请求与会议等事件相关的大量短 url。它可以在相当长的时间内预先生成这些 url 以平衡其服务器上的负载。

所以,基于这个假设,为什么不直接使用

GET /ShortUrl?longUrl=http://www.example.org/en/article/something-that-is-crazy-long.html&suggestion=crazyUrl

如果网址已经存在,那么您可能会回来

303 See Other
Location: http://example.org/ShortUrl/3e4tyz

如果以前没有,您可能会得到

303 See Other
Location: http://example.org/ShortUrl/crazyurl

我意识到这看起来像我们通过创建响应 GET 的东西来打破 GET 规则,但我相信在这种情况下它没有任何问题,因为客户端没有要求创建缩短的 URL,实际上也没有无论哪种方式都关心。它是幂等的,因为无论您调用多少次都无关紧要。

一个我不知道答案的有趣问题是代理是否会缓存初始 GET 并重定向。这可能是一个有趣的属性,因为其他用户对同一 url 的未来请求可能永远不需要实际到达源服务器,代理可以完全处理请求。

于 2010-03-11T22:05:41.903 回答
11

我赞成 303。假设现在 hello=world&hokey=pokey 唯一标识项目 foo,但后来项目 foo 的 hokey 值更改为“smokey”?现在,这些原始值不再是该资源的唯一标识符。我认为临时重定向是合适的。

于 2010-03-11T18:24:41.957 回答
3

POST 不支持“查找或创建”方法。服务器无法告诉客户端“我会创建它,但它已经存在。在此处查找现有条目”。2xx 代码都不起作用,因为请求不成功。3xx 代码都不起作用,因为其目的不是将 POST 重定向到新资源。而且 303 也不合适,因为没有任何改变(参见 303 规范)。

您可以做的是向客户端提供一个表单或模板以与 PUT 一起使用,告诉客户端如何构造 PUT URI。如果 PUT 结果为 200,则客户端知道资源存在,如果返回 201,则表明已创建新资源。

例如:

URI 模板:http://service/items/ {key}

http://service/items/456

[数据]

201 已创建

或者

http://service/items/456

[数据]

200 好的

您还可以使用 If-None-Match 执行“创建但如果存在则不替换”:


PUT http://service/items/456
If-None-Match: *

[data]

412 Precondition failed 

于 2010-03-11T19:57:38.157 回答
2

从客户的角度来看,我认为您可以只为案例 2 发送一个201,与案例 1 相同,因为现在“创建”了记录。

于 2012-01-10T22:26:34.487 回答
2

HTTP 1.1。规范(RFC 2616)建议 303:

303 查看其他

可以在不同的 URI 下找到对请求的响应,并且应该使用该资源上的 GET 方法来检索。此方法的存在主要是为了允许 POST 激活脚本的输出将用户代理重定向到选定的资源。新的 URI 不是原始请求资源的替代引用。

于 2014-05-28T12:30:10.650 回答