我正在开发一个JAX-RS API,其中包括一个简单的"Person"表,字段为"id"one_answers"name",其中"id"与mysql数据库中的一个autonumber绑定。一个典型的用例是为一个新人POST。
JSON消息{"name":"Bob"}
的POST可能会返回,例如{"id":101,"name":"Bob"}
。
如果调用者请求对包含标识符的对象进行POST,该怎么办?我的选择似乎是:
- 以无效为由拒绝请求
- 从请求中删除id并继续处理
- 将POST视为UPSERT(在更新失败时,删除ID并插入)
- 尝试使用提供的id创建新记录
从安全角度来看,最后一种选择似乎是不可靠的。如果我使用mysql,恶意用户可能会在一个请求中将我的autonumber提升到最大值。
在REST API中应该如何处理POST请求中包含的id?
您肯定应该拒绝所有到达/users/
端点的请求。首先,出于安全原因(在DB级别),其次,生成/建议ID不是客户端的工作。
因此,答案是将请求视为无效而拒绝,并附上适当的状态代码(400
)和解释拒绝原因的消息。
第二个选项是非直观的,即发送和ID(正如我所写的,这是一个坏主意)-不会期望收到它发布的不同ID。在正文中发送ID对于PUT
请求是有意义的,并且它假设对象已经创建/存在——这是一个更新。
第三个选项将不是RESTful——REST中没有upstart——POST
创建新的资源。第四个选项根本没有意义——这不是客户端提供ID的工作。
归根结底,您希望API支持什么。
允许创建,但也允许替换
向PUT /users/101
发送请求意味着您允许客户端替换或使用提供的有效负载创建ID为101的资源。对此的正常响应是,根据数据库的响应,如果它导致替换,则返回200;如果它导致插入,则返回201请注意,这是替换类型的更新,而不是部分更新。如果您只需要更新部分资源,那么您正在寻找PATCH,这在RDBMS中很难实现
如果你不想要这个行为,那么就不要允许这个动词
如果您不想支持这种行为,那么最好的做法就是根本不允许该路由使用PUT谓词。这是RESTful的做法。如果你已经实现了HAEOS(实际上是什么使某些东西RESTful),那么PUT谓词将不会出现在响应负载中的超媒体链接中,并且如果你无论如何都试图使用它,它将返回HTTP 405 Method not Allowed。
你真的是RESTful吗
如前所述,您可以不允许ID,而是要求它在主体中,但这不是必要的,而且实际上也不是很RESTful。我的意思是——透过HATEOAS的镜头来看它。很容易争辩说,如果客户端需要知道ID应该在PUT的主体中(甚至可能是ID的名称,例如,如果你将其称为"UserID"),那么这基本上忽略了超媒体的整个目标,即客户端不需要知道任何关于API的信息来导航它,基本上没有人实现HATEOAS/Hypermedia,但当试图对什么是RESTful和什么不是RESTful进行论证时,这就是你的指导思想。在真正的RESTful API中,客户端只需要知道资源路径和被操纵实体的形状。
客户端提供的资源ID的问题是,人们试图证明他们不可能知道整数标识中的下一个数字。对此,我说。。这就是你的问题。为了上帝的爱,停止使用PKs作为您的公共ID。实体ID应该是全局唯一的,所以让客户端生成一个GUID来使用,例如,或者在服务器端为POST生成GUID。用它作为你的";应用实体ID";。这应该是您在应用程序之间传递的ID。你可以整天争论性能,但a)在MongoDB这样的数据库中几乎没有明显的影响,在RDBMS中几乎没有任何明显的影响;b)你没有用应用程序ID将表链接在一起——这是一个where子句。您仍然在数据库端使用数据库ID/PK,只是不会将其泄露到野外。但无论如何。。。
当您想要创建一个新资源并且不允许客户端设置ID时
因此,如果您的意图是允许客户端创建资源,那么请使用POST。为此,不应在路由(例如POST /users/101
)中提供ID,因为这没有多大意义。如果您需要允许他们设置ID,那么按照上面的描述实现PUT。如果POST成功,则POST的正确响应是201 Created
,并包括Location
标头,供客户端执行GET以检索其新资源。事实上,有一种模式叫做";PRG";(Post Redirect Get),返回一个303 See Other
,它将为您解决问题。这在web/前端上下文中效果最好,但如果客户端将自动跟踪重定向,则也可以使用Client-API。或者,您可以简单地返回201,其中新资源在主体中,新ID在其中填充。
另一个选项是使用@JSonIgnore注释忽略它。在GET、PUT或DELETE上,您会有/user/{id},因此客户端应该已经知道id了。在POST中,客户端不应该发送ID,您应该返回ID,或者更确切地说,返回一个URL,您可以获取创建的对象,其中包括ID。