我正在开发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
使用该命令,服务器会检查我们是否已有信息hello=world&hokey=pokey
的项目。所以这里有两种情况。
案例1:该项目不存在;它被创造了。这种情况很容易。
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 descriptions和RFC 2616,但这些似乎都不完美。以下是我在这种情况下寻找的具体特征:
重定向是永久性的,因为ID永远不会改变。因此,为了提高效率,客户端可以而且应该直接向ID端点发出所有未来的请求。这表明301,因为其他三个是暂时的。
重定向应该使用GET,即使此请求是POST。这表示303,因为所有其他人技术上应该重新使用POST方法。在实践中,浏览器将使用GET for 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会收到ID为4Agih5的内容,并且必须将用户重定向到相应的网址。
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。
所以我可能不会改变这种方法;我只是问它最好的重定向方法。谢谢!
答案 0 :(得分:11)
我认为303.现在假设hello = world&amp; hokey = pokey唯一标识项目foo,但后来项目foo的hokey值变为“smokey”?现在,这些原始值不再是该资源的唯一标识符。我认为临时重定向是合适的。
答案 1 :(得分:10)
我认为你正在努力解决这个问题的原因之一是因为(除非我们缺少一些关键信息),这种互动不是很合理。
让我解释为什么我这么想。最初的前提是用户正在请求创建某些内容,并为他们希望创建的资源提供了一些关键信息。
然后,您声明如果该关键信息引用现有对象,那么您希望返回该对象。问题是用户不希望检索他们希望创建新对象的现有对象。如果他们无法创建资源,因为它已经存在或存在密钥冲突,那么应该告知用户该事实。
当用户尝试创建新对象时,选择检索现有对象似乎是一种误导性方法。
如果资源已经存在并且包含指向实体主体中现有对象的链接,则可能有一种替代方法是返回404 Bad请求。客户端应用程序可以选择吞下错误请求错误,只需按照指向现有实体的链接,这样就可以隐藏用户的问题。这将是客户端应用程序的选择,但至少服务器的行为方式清晰。
基于新的例子,让我建议一种完全不同的方法。它可能不适用于你的情况,因为魔鬼总是在细节中,但也许它会有所帮助。
从客户的角度来看,它对服务器是创建新的缩短的URL还是撤回现有的URL并不感兴趣。实际上,服务器是否需要生成新ID是一个完全隐藏的实现细节 隐藏创建过程可能非常有价值。也许服务器可以提前预测将很快请求与诸如会议之类的事件相关的许多短网址。它可以在相当长的时间内预先生成这些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和重定向。这可能是一个有趣的属性,因为其他用户对同一网址的未来请求可能永远不需要实际到达原始服务器,代理可以完全处理请求。
答案 2 :(得分:3)
POST不支持“查找或创建”方法。服务器无法告诉客户端“我会创建它,但它已经存在。请查看现有条目”。 2xx代码都不起作用,因为请求不成功。 3xx代码都不起作用,因为目的不是将POST重定向到新资源。并且303也不合适,因为没有任何改变(参见303规范)。
您可以做的是向客户端提供一个表单或模板,以便与PUT一起使用,告诉客户端如何构造PUT URI。如果PUT导致200,则客户端知道存在的资源,并且如果返回201则表示已创建新资源。
例如:
URI的模板:http://service/items/ {key}
[数据]
201创建
或
[数据]
200好
您还可以使用If-None-Match:
执行“创建但不替换if exists”
PUT http://service/items/456
If-None-Match: *
[data]
412 Precondition failed
扬
答案 3 :(得分:2)
从客户的角度来看,我认为您可以为案例2发送 201 ,与案例1相同,对于客户端,记录现在已“创建”。
答案 4 :(得分:2)
HTTP 1.1。 Spec(RFC 2616)建议303:
303见其他
可以在不同的URI下找到对请求的响应 应该使用该资源上的GET方法检索。这种方法 存在主要是为了允许输出POST激活的脚本 将用户代理重定向到选定的资源。新的URI不是 替换最初请求的资源的参考。