实现自定义HTTP方法是否有问题?


34

我们的网址格式如下

/ instance / {instanceType} / {instanceId}

您可以使用标准的HTTP方法来调用它:POST,GET,DELETE和PUT。但是,我们还有其他一些操作,例如“另存为草稿”或“制作”

我们认为我们可以只使用自定义HTTP方法,例如:DRAFT,VALIDATE,CURATE

我认为这是可以接受的,因为标准说

“下面定义了HTTP / 1.1的一组常用方法。尽管可以扩展此组,但是不能假定其他方法可以为单独扩展的客户端和服务器共享相同的语义。”

像WebDav这样的工具会创建一些自己的扩展。

定制方法是否会遇到问题?我正在考虑代理服务器和防火墙,但是任何其他令人关注的方面都欢迎。我应该放心一点,只是拥有URL参数,例如action = validate | curate | draft?


6
正如我再次从RFC 1925中引用的那样-“在协议设计中,达到完美不是在没有什么可以添加的时候,而是在没有什么需要补充的时候。” -如果有效,则无需添加到http。

4
没错,只要您意识到自己现在使用的是自定义协议而不是HTTP。
user16764 2013年

10
@ user16764“下面定义了HTTP / 1.1的一组常用方法。尽管可以扩展此组,但是不能假定其他方法可以为单独扩展的客户端和服务器共享相同的语义。” w3.org/Protocols/rfc2616/rfc2616-sec9.html因此允许使用,并且仍然是HTTP
Juan Mendes

恕我直言,没有什么可以从HTTP添加/删除的,因为方法定义指出使用自定义方法在HTTP / 1.1范围内已经可以接受,但是不能期望使用相同的语义,因此我想@MichaelT和Juan Mendes的观点都可以有所平息
Prof83

Answers:


42

之一的HTTP的基本限制和所述 REST的中央设计特征是一个统一的接口所提供的(除其他外)一个小的,固定的一套普遍适用于所有资源的方法。统一的界面约束具有许多优点和缺点。我在这里自由引用菲尔丁的话

统一的界面:

  • 更简单。
  • 使实现与提供的服务脱钩。
  • 允许分层体系结构,包括HTTP负载平衡器(nginx)和缓存(清漆)之类的东西。

另一方面,一个统一的接口:

  • 因为信息是以标准化形式而不是特定于应用程序需求的形式传输的,所以降低了效率。

权衡是“针对Web的常见情况设计的”,并允许构建一个大型的生态系统,该生态系统为Web体系结构中的许多常见问题提供了解决方案。坚持统一的界面将使您的系统能够从该生态系统中受益,而破坏该生态系统将使其变得如此困难。您可能想要使用像nginx这样的负载均衡器,但是现在您只能使用了解DRAFT和CURATE的负载均衡器。您可能想要使用像Varnish这样的HTTP缓存层,但是现在您只能使用理解DRAFT和CURATE的HTTP缓存层。您可能想寻求某人的帮助来解决服务器故障,但没有人知道CURATE请求的语义。可能难以更改您首选的客户端或服务器库,以了解并正确实现新方法。等等。

正确的*表示方式是在资源(或相关资源)上进行状态转换。您无需草稿,您可以将其draft状态转换为,true也可以创建draft包含更改并链接到以前的草稿版本的资源。您无需创建帖子,而是将其curated状态转换为,true或创建curation将帖子与策划该帖子的用户链接的资源。

*正确,因为它最紧密地遵循REST体系结构原则。


感谢您对负载平衡的评论,我一定会考虑一下。您是否知道说明自定义方法是否可接受的资源?
Juan Mendes

2
除非自定义方法是WEBDAV之类的受广泛支持的扩展的一部分,否则我看不出任何优势。我只建议您将这些更改视为状态转换。使用我们现有的方法,网络可以正常工作。除非您认为统一界面的一部分有意义(例如PATCH),否则没有任何理由添加更多。
Rein Henrichs

5
在设计您希望HTTP服务自己工作的方式时,我看到了优势。但是,“不能假定其他方法共享相同的语义”-相当公平,但是它仍然是HTTP / 1.1范围的一部分,因此,如果防火墙,代理,负载均衡器等不支持,则应该允许这种可能性。那么他们是否没有正确实现HTTP / 1.1?
Prof83

您可能是正确的POV,但是,我看不出为什么自定义动词应该是什么问题。所有工具都应该像POST一样对待它们,即“资源可能会改变,这就是我们所知道的一切”。
maaartinus

7

我希望将它们设计为子资源,在其上执行POST请求。

假设您有的资源/instance/type/1,我将以该资源的表示形式传达一些指向可以在资源上执行的“操作”的链接,例如/instance/type/1/draft/instance/type/1/curate。在JSON中,这可能很简单:

{
    "some property":"the usual value",
    "state": "we can still inform the client about the current state",
    "draft": "http://server/instance/type/1/draft",
    "curate": "http://server/instance/type/1/curate"
}

这样,在对curate成员提供的链接的POST请求期间,客户端可以非常明确地了解需要发生的情况。此处发布的资源可能包含详细说明可能导致状态转换的事件的参数。

采用“天真”的方法来在资源上的可能状态之间移动的缺点是无法捕获导致这些转换的事件。

状态转换通常是响应特定事件而发生的,我宁愿捕获那些事件,也不愿让客户端决定某些事物现在处于特定的“状态”。这也使验证更加困难。另外,除非您也描述州本身,否则您将无法捕获任何“参数”。然后,当某些代码在不进行真实状态转换的情况下更改了代码而又需要进行验证时,一切就变得很棘手,整个过程很快就变得一团糟。


好答案。迄今为止,能够提供状态转换的参数并让服务器封装和管理这些参数是最好的方法。
Thomas W

我目前在(VMware)工作的公司就是这样做的。GET on /vms/some-id返回POST /vms/some-id/restart与之类的操作的链接,我们使用它来确定应启用还是禁用操作。我与HATEOAS有恋爱关系:)
Juan Mendes

如果要采取的动作是请求的动词而不是一些随机查询参数,资源路径段或主体属性,那将更加有意义。
马修·怀特

您无法链接到动词。
Dave Van den Eynde

6

我认为自定义HTTP方法是实现实体操作的最佳方法。将动作添加到实体主体(POST)似乎不正确,它不是您的实体的一部分(尽管结果可能保存在其中)。同样,使用自定义HTTP方法,代理可以确定其动作而无需解析实体主体。

就像CRUD,您总是想实现这些功能,但是也有自己的一套特定操作(针对每个实体)。我真的不知道扩展这些内容会有什么问题。

另外@Rein Henrichs“您不草稿,将其草稿状态转换为true或创建草稿资源”对我来说似乎是错误的。一个drafts属性将被用于持久保存的状态,而不是进行转换的。动作甚至不一定会导致“状态”,也不一定会保存在属性中。为每个状态/转换创建一个单独的实体似乎更加模糊。尝试保持对实体的相同引用(URI)。


1
这是一个公平的观点,尽管意见分歧很大,但我可以看到其背后的原因,但我不同意弃权票(尤其是选民未发表评论)。让我们以PHP异常处理为例,“最佳实践”似乎倾向于使用特定的Exception类型来建议异常类型,甚至忽略诸如RuntimeException与BadMethodCallException之类的实际消息。那么,如果使用自定义方法已经被认为是HTTP / 1.1范围的一部分,为什么反对使用DRAFT呢?负载平衡器和代理确实也应该接受这种可能性
Prof83
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.