哪种 HTTP 重定向状态代码最适合此 REST API 场景?
我正在开发 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已创建
位置:http://api.example.com/v1/item/bar
情况 2:该项目已存在。这就是我正在努力的地方......不确定使用什么是最好的重定向代码。
301 永久移动
? 找到 302
? 303 看看其他
? 307临时重定向
?位置:http://api.example.com/v1/item/foo
我研究了维基百科描述 和 RFC 2616,而且这些似乎都不完美。以下是我在这种情况下寻找的具体特征:
重定向是永久性的,因为 ID 永远不会改变。因此,为了提高效率,客户端可以而且应该向 ID 端点发出所有未来的请求直接地。这表明 301,因为其他三个是临时的。
即使此请求是 POST,重定向也应使用 GET。这建议使用 303,因为从技术上讲,所有其他请求都应该重用 POST 方法。实际上,浏览器将使用 GET 来表示 301 和 302,但这是一个 REST API,而不是供普通用户在浏览器中使用的网站。
它应该广泛使用并且易于使用。具体来说,303 是 HTTP/1.1,而 301 和 302 是 HTTP/1.0。我不确定这是一个多大的问题。
在这一点上,我倾向于 303 只是为了在语义上正确(使用 GET,不要重新 POST),并且只是吸收“临时”部分。但我不确定 302 是否会更好,因为实际上它的行为与 303 相同,但不需要 HTTP/1.1。但如果我沿着这条线走下去,我想知道 301 是否因为同样的原因加上“永久”部分更好。
想法赞赏!
编辑:让我尝试用一个更具体的示例来更好地解释此“获取或创建”操作的语义:URL 缩短。无论如何,这实际上更接近我的应用程序。
对于 URL 缩短器来说,迄今为止最常见的操作是通过 ID 进行检索。例如,对于 http://bit.ly/4Agih5,bit.ly 收到的 ID 为 4Agih5,并且必须重定向用户访问其相应的 URL。
bit.ly 已经有一个 API,但它并不是真正的 RESTful。为了举例,让我编写一个更加 RESTful 的 API。例如,查询 ID 可能会返回有关它的各种信息(例如分析):
GET http://api.bit.ly/item/4Agih5
现在,如果我想向bit.ly 简而言之,我事先不知道我的URL的ID,所以我不能使用PUT。我会使用 POST 代替。
发布http://api.bit.ly/item/
url=http://stackoverflow.com/
(但已编码)
如果 bit.ly 之前没有见过此 URL,它将为其创建一个新 ID,并通过 201 Created 将我重定向到新身份证。但如果它看到了该 URL,它仍然会重定向我而不做任何更改。这样,我可以通过任何一种方式点击该重定向位置来获取缩短的 URL 上的信息/元数据。
就像这个 URL 缩短示例一样,在我的应用程序中,冲突并不重要。一个 URL 对应一个 ID,仅此而已。因此,URL 之前是否被缩短并不重要;无论哪种方式,将客户端指向它的 ID 都是有意义的,无论是否需要首先创建该 ID。
所以我可能不会改变这种方法;我只是问最好的重定向方法。谢谢!
I'm working on a REST API. The key objects ("nouns") are "items", and each item has a unique ID. E.g. to get info on the item with ID foo:
GET http://api.example.com/v1/item/foo
New items can be created, but the client doesn't get to pick the ID. Instead, the client sends some info that represents that item. So to create a new item:
POST http://api.example.com/v1/item/
hello=world&hokey=pokey
With that command, the server checks if we already have an item for the info hello=world&hokey=pokey
. So there are two cases here.
Case 1: the item doesn't exist; it's created. This case is easy.
201 Created
Location: http://api.example.com/v1/item/bar
Case 2: the item already exists. Here's where I'm struggling... not sure what's the best redirect code to use.
301 Moved Permanently
? 302 Found
? 303 See Other
? 307 Temporary Redirect
?Location: http://api.example.com/v1/item/foo
I've studied the Wikipedia descriptions and RFC 2616, and none of these seem to be perfect. Here are the specific characteristics I'm looking for in this case:
The redirect is permanent, as the ID will never change. So for efficiency, the client can and should make all future requests to the ID endpoint directly. This suggests 301, as the other three are meant to be temporary.
The redirect should use GET, even though this request is POST. This suggests 303, as all others are technically supposed to re-use the POST method. In practice, browsers will use GET for 301 and 302, but this is a REST API, not a website meant to be used by regular users in browsers.
It should be broadly usable and easy to play with. Specifically, 303 is HTTP/1.1 whereas 301 and 302 are HTTP/1.0. I'm not sure how much of an issue this is.
At this point, I'm leaning towards 303 just to be semantically correct (use GET, don't re-POST) and just suck it up on the "temporary" part. But I'm not sure if 302 would be better since in practice it's been the same behavior as 303, but without requiring HTTP/1.1. But if I go down that line, I wonder if 301 is even better for the same reason plus the "permanent" part.
Thoughts appreciated!
Edit: Let me try to better explain the semantics of this "get or create" operation with a more concrete example: URL shortening. This is actually much closer to my app anyway.
For URL shorteners, the most common operation by far is retrieving by ID. E.g. for http://bit.ly/4Agih5, bit.ly receives an ID of 4Agih5 and must redirect the user to its corresponding URL.
bit.ly already has an API, but it's not truly RESTful. For the sake of example, let me make up a more RESTful API. For example, querying the ID might return all sorts of info about it (e.g. analytics):
GET http://api.bit.ly/item/4Agih5
Now if I want to submit a new URL to bit.ly to shorten, I don't know the ID of my URL in advance, so I can't use PUT. I'd use POST instead.
POST http://api.bit.ly/item/
url=http://stackoverflow.com/
(but encoded)
If bit.ly hasn't seen this URL before, it'll create a new ID for it and redirect me via 201 Created to the new ID. But if it has seen that URL, it'll still redirect me without making a change. This way, I can hit that redirect location either way to get the info/metadata on the shortened URL.
Like this example of URL shortening, in my app, collisions don't matter. One URL maps to one ID, and that's it. So it doesn't really matter if the URL has been shortened before or not; either way, it makes sense to point the client to the ID for it, whether that ID needs to be created first or not.
So I probably won't be changing this approach; I'm just asking about the best redirect method for it. Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我赞成 303。假设现在 hello=world&hokey=pokey 唯一标识了项目 foo,但后来项目 foo 的 hokey 值更改为“smokey”?现在,这些原始值不再是该资源的唯一标识符。我认为临时重定向是合适的。
I'd argue for 303. Supposing right now hello=world&hokey=pokey uniquely identifies item foo, but later item foo's hokey value changes to "smokey"? Now those original values are no longer a unique identifier for that resource. I'd argue that a temporary redirect is appropriate.
我认为您在这种情况下苦苦挣扎的原因之一是因为(除非我们遗漏了一些关键信息)交互不太符合逻辑。
让我解释一下我为什么这么想。最初的前提是用户请求创建一些东西,并且已经为他们希望创建的资源提供了一些关键信息。
然后,您声明如果该关键信息引用现有对象,那么您希望返回该对象。问题是用户不希望检索现有对象,而是希望创建一个新对象。如果他们无法创建资源,因为资源已经存在或存在密钥冲突,则应告知用户该事实。
当用户尝试创建新对象时选择检索现有对象似乎是一种误导性的方法。
如果资源已存在并包含指向实体正文中现有对象的链接,也许一种替代方法是返回 404 Bad 请求。客户端应用程序可以选择接受错误请求错误,并简单地跟踪现有实体的链接,从而向用户隐藏问题。这将是客户端应用程序的选择,但至少服务器的行为方式清晰。
基于新的例子,让我建议一种完全不同的方法。它可能不适用于您的情况,因为细节决定成败,但也许会有所帮助。
从客户端的角度来看,它实际上对服务器是创建新的缩短的 URL 还是撤回现有的 URL 不感兴趣。事实上,服务器是否需要生成新的ID是一个完全隐藏的实现细节。
隐藏创建过程可能非常有价值。也许服务器可以提前预测很快就会请求与会议等活动相关的大量短网址。它可以在相当长的时间内预先生成这些 URL,以平衡服务器上的负载。
所以,基于这个假设,为什么不直接使用
如果 url 已经存在那么你可能会回来
如果它以前没有,你可能会
意识到这看起来像我们通过创建一些响应来破坏 GET 的规则GET,但我相信在这种情况下它没有任何问题,因为客户端没有要求创建缩短的 URL,并且实际上并不关心任何一种方式。它是幂等的,因为调用它多少次并不重要。
一个我不知道答案的有趣问题是代理是否会缓存初始 GET 和重定向。这可能是一个有趣的属性,因为其他用户对同一 URL 的未来请求可能永远不需要实际到达原始服务器,代理可以完全处理该请求。
I think one of the reasons that you are struggling with this scenario is because (unless we are missing some key information) the interaction is not very logical.
Let me explain why I think this. The initial premise is that the user is requesting to create something and has provided some key information for the resource they wish to create.
You then state that if that key information refers to an existing object then you wish to return that object. The problem is that the user did not wish to retrieve an existing object they wished to create a new one. If they cannot create the resource because either it already exists or there is a key collision then the user should be informed of that fact.
Choosing to retrieve an existing object when the user has attempted to create a new one seems to be a misleading approach.
Maybe one alternative would be to return a 404 Bad request if the resource already exists and include a link to the existing object in the entity body. The client application could choose to swallow the bad request error and simply follow the link to the existing entity and by doing so hide the issue from the user. That would be the choice of the client application, but at least the server is behaving in a clear manner.
Based on the new example, let me suggest a completely different approach. It may not work in your case, as always the devil is in the details, but maybe it will be helpful.
From the client's perspective it really has no interest in whether the server is creating a new shortened URL or pulling back an existing one. In fact, whether the server needs to generate a new ID or not is an implementation detail that is completely hidden.
Hiding the creation process could be very valuable. Maybe the server can predict in advance that lots of short urls will soon be requested related to a event such as a conference. It could pre-generate these urls in quite periods to balance the load on its servers.
So, based on that assumption, why not just use
If the url already existed then you might get back
If it previously didn't, you might get
I realize that this looks like we are breaking the rules of GET by creating something in response to a GET, but I believe in this case there is nothing wrong with it because client did not ask for the shortened URL to be created and really does not care either way. It is idempotent because does not matter how many times you call it.
One interesting question that I don't know the answer to is whether proxies will cache the initial GET and redirect. That might be an interesting property as future requests by other users for the same url may never need to actually get to the origin server, the proxy could handle the request completely.
POST 不支持“查找或创建”方法。服务器无法告诉客户端“我会创建它,但它已经存在。在此处查找现有条目”。由于请求不成功,所有 2xx 代码均不起作用。所有 3xx 代码都不起作用,因为其目的不是将 POST 重定向到新资源。 303 也不合适,因为没有任何改变(参见 303 规范)。
您可以做的是向客户端提供一个与 PUT 一起使用的表单或模板,告诉客户端如何构建 PUT URI。如果 PUT 结果为 200,则客户端知道该资源已存在;如果返回 201,则表明已创建新资源。
例如:
URI 模板:http://service/items/{key}
或
您也可以执行“创建但执行”操作不使用 If-None-Match 替换“如果存在”:
Jan
POST does not support a 'lookup or create' approach. The server cannot tell the client "I would create that, but it already existed. Look here for the existing entry". None of the 2xx codes work because the request is not successful. None of the 3xx codes work, because the intention is not to redirect the POST to a new resource. And 303 is also not appropriate since nothing changed (see 303 spec).
What you could do is provide a form or template to the client to be used with PUT that tells the client how to construct the PUT URI. If the PUT results in a 200 the client knows the resource existed and if 201 is returned that a new resource has been created.
For example:
Template for URI: http://service/items/{key}
or
You can also do a 'create but do not replace if exists' using If-None-Match:
Jan
从客户的角度来看,我认为您可以为案例 2 发送一个 201 ,就像案例 1 一样,因为现在记录已“创建”给客户。
From the client's point of view, I would think that you could just send a 201 for case 2 the same as for case 1 as to the client the record is now "created".
HTTP 1.1。规范 (RFC 2616) 建议 303:
303 See Other
可以在不同的 URI 下找到对请求的响应,并且
应使用该资源上的 GET 方法进行检索。这个方法
存在主要是为了允许 POST 激活脚本的输出
将用户代理重定向到选定的资源。新的 URI 不是
替换最初请求的资源的参考。
HTTP 1.1. Spec (RFC 2616) suggests 303:
303 See Other
The response to the request can be found under a different URI and
SHOULD be retrieved using a GET method on that resource. This method
exists primarily to allow the output of a POST-activated script to
redirect the user agent to a selected resource. The new URI is not a
substitute reference for the originally requested resource.