我正在构建一个允许客户端存储对象的服务器.这些对象在客户端完全构造,完整的对象ID对于对象的整个生命周期是永久的.
我已经定义了API,以便客户端可以使用PUT创建或修改对象:
PUT /objects/{id} HTTP/1.1 ... {json representation of the object}
{id}是对象ID,因此它是Request-URI的一部分.
现在,我也在考虑允许客户端使用POST创建对象:
POST /objects/ HTTP/1.1 ... {json representation of the object, including ID}
由于POST意味着"附加"操作,我不知道如果对象已经存在该怎么做.我应该将请求视为修改请求还是应该返回一些错误代码(哪个)?
我的感觉是409 Conflict
最合适的,然而,当然在野外很少看到:
由于与资源的当前状态冲突,无法完成请求.此代码仅在预期用户可能能够解决冲突并重新提交请求的情况下才允许.响应主体应该包含足够的信息供用户识别冲突的来源.理想情况下,响应实体将包含足够的信息供用户或用户代理解决问题; 但是,这可能是不可能的,也不是必需的.
冲突最有可能发生在响应PUT请求时.例如,如果正在使用版本控制并且包含PUT的实体更改为与早期(第三方)请求所产生的资源冲突的资源,则服务器可能会使用409响应来指示它无法完成请求.在这种情况下,响应实体可能包含由响应Content-Type定义的格式的两个版本之间的差异列表.
根据RFC 7231,可以使用303 See Other MAY 如果处理POST的结果等同于现有资源的表示.
我个人使用WebDAV扩展422 Unprocessable Entity
.
根据RFC 4918
的
422 Unprocessable Entity
状态代码装置的服务器理解的请求实体的内容类型(因此一个415 Unsupported Media Type
状态码是不适当的),并且请求实体的语法是正确的(因此一个400 Bad Request
状态码是不适当的),但无法处理所包含的指令.
可能在游戏后期,但我在尝试制作REST API时偶然发现了这个语义问题.
为了扩展Wrikken的答案,我认为您可以使用409 Conflict
或403 Forbidden
根据具体情况 - 简而言之,当用户无法解决冲突并完成请求时(例如他们无法发送请求)时,请使用403错误DELETE
请求显式删除资源),或者如果可能的话可以使用409.
10.4.4 403禁止
服务器理解请求,但拒绝履行请求.授权无效,请求不应重复.如果请求方法不是HEAD并且服务器希望公开为什么请求没有得到满足,那么它应该描述实体中拒绝的原因.如果服务器不希望将此信息提供给客户端,则可以使用状态代码404(未找到).
如今,有人说"403"并且出现了权限或身份验证问题,但规范说它基本上是服务器告诉客户端它不会这样做,不要再问它,这就是为什么客户端不应该"T.
至于PUT
vs. POST
... POST
应该用于在用户无法或不应该为资源创建标识符时创建资源的新实例.PUT
在知道资源的标识时使用.
9.6 PUT
...
POST和PUT请求之间的根本区别体现在Request-URI的不同含义上.POST请求中的URI标识将处理所包含实体的资源.该资源可能是数据接受过程,某些其他协议的网关或接受注释的单独实体.相反,PUT请求中的URI标识请求附带的实体 - 用户代理知道URI的用途,并且服务器不得尝试将请求应用于其他资源.如果服务器希望将请求应用于不同的URI,
必须发送301(永久移动)响应; 然后,用户代理可以自己决定是否重定向请求.
这全都与上下文有关,还有谁负责在请求中重复(服务器或客户端或两者)
如果服务器仅指向重复项,请查看4xx:
400错误的请求-服务器由于明显的客户端故障而无法处理请求时
409冲突-如果服务器不处理请求,但原因不是客户端的错误
...
要隐式处理重复项,请查看2XX:
200 OK
创建了201
...
如果服务器期望返回某些内容,请查看3XX:
找到302个
303查看其他
...
当服务器能够指向现有资源时,则意味着重定向。
如果上述还不够,在响应正文中准备一些错误消息始终是一个好习惯。
"302 Found"听起来合情合理.并且RFC 2616说它可以回答除GET和HEAD之外的其他请求(这肯定包括POST)
但是它仍然让访问者通过这个URL来获取RFC的"Found"资源.为了使它直接进入真正的"找到"URL,应该使用"303 See Other",这是有道理的,但强制另一个调用GET以下的URL.好的方面,这个GET是可缓存的.
我想我会用"303见其他".我不知道我是否可以回复身体中发现的"东西",但我想这样做是为了将一次往返保存到服务器上.
更新:重新阅读RFC之后,我仍然认为不存在的 "4XX + 303 Found"代码应该是正确的.但是,"409 Conflict"是现有的最佳答案代码(由@ Wrikken指出),可能包括指向现有资源的Location头.
我认为你不应该这样做.
如您所知,POST是修改集合的,它用于创建新项目.所以,如果你发送id(我认为这不是一个好主意),你应该修改集合,即修改项目,但这是令人困惑的.
用它来添加一个没有id的项目.这是最好的做法.
如果要捕获UNIQUE约束(而不是id),则可以响应409,就像在PUT请求中一样.但不是身份证.
我认为对于REST,你只需要对该特定系统的行为做出决定,在这种情况下,我认为"正确"的答案将是这里给出的几个答案之一.如果您希望请求停止并且行为就好像客户端在继续之前犯了一个需要修复的错误,那么使用409.如果冲突真的不那么重要并且想要保持请求继续,那么通过重定向来响应客户端到找到的实体.我认为正确的REST API应该在POST之后重定向(或者至少提供位置头)到该资源的GET端点,因此这种行为将提供一致的体验.
编辑:值得注意的是,您应该考虑PUT,因为您提供了ID.然后行为很简单:"我不在乎现在有什么,把这个东西放在那里." 意思是,如果没有任何东西,它就会被创造出来; 如果有东西它会被替换.我认为当服务器管理该ID时,POST更合适.分离这两个概念基本上告诉你如何处理它(即PUT是幂等的,所以只要有效载荷有效,它总是有效,POST总是创建,所以如果有ID冲突,那么409会描述这个冲突) .
我会使用422 Unprocessable Entity
,当请求无效但问题不在语法或身份验证中时使用.
作为反对其他答案的论据,使用任何非4xx
错误代码意味着它不是客户端错误,显然是.使用非4xx
错误代码来表示客户端错误根本没有任何意义.
这似乎409 Conflict
是这里最常见的答案,但是,根据规范,这意味着资源已经存在,并且您应用于它的新数据与其当前状态不兼容.如果您要发送POST
请求,例如,已经使用的用户名,则它实际上并不与目标资源冲突,因为目标资源尚未发布到.当存储的资源版本与请求的资源版本之间存在冲突时,这是一个专门用于版本控制的错误.它对于此目的非常有用,例如,当客户端缓存旧版本的资源并根据不再有条件有效的不正确版本发送请求时."在这种情况下,响应表示可能包含有助于根据修订历史合并差异的信息." 使用该用户名创建另一个用户的请求是无法处理的,与版本控制无关.
对于记录,422也是当您尝试使用已在使用的名称创建存储库时GitHub使用的状态代码.
为什么不接受202?这是一个OK请求(200s),本身没有客户端错误(400s).
来自10个状态码定义:
"202已接受.该请求已被接受处理,但处理尚未完成."
...因为它不需要完成,因为它已经存在.客户不知道它已经存在,他们没有做错任何事.
我倾向于投掷一个202,并返回类似的内容到GET /{resource}/{id}
会返回的内容.