如果多个操作以不同的状态结束,该返回什么HTTP状态代码?


72

我正在构建一个API,用户可以在其中请求服务器在一个HTTP请求中执行多项操作。结果以JSON数组的形式返回,每个动作一个条目。

这些操作中的每一个都可能彼此独立失败或成功。例如,第一个操作可能会成功,第二个操作的输入格式可能不正确,无法验证,第三个操作可能会导致意外错误。

如果每个动作有一个请求,我将分别返回状态码200、422和500。但是,现在只有一个请求时,我应该返回什么状态码?

一些选项:

  • 始终返回200,并在正文中提供更多详细信息。
  • 也许仅在请求中有多个动作时才遵循上述规则?
  • 如果所有请求成功,也许返回200,否则返回500(或其他代码)?
  • 每个动作只使用一个请求,并接受额外的开销。
  • 完全不同吗?


7
稍微相关的还有:programmers.stackexchange.com/questions/305250/…(请参阅有关HTTP状态代码和应用程​​序代码之间的分隔的公认答案)

4
通过将这些请求分组在一起可以实现什么优势?是关于业务逻辑,例如通过多个资源进行的事务,还是关于性能?或者是其他东西?
卢克·弗兰肯

5
好的,在那种情况下,我强烈建议您在其他方面提高该性能。在将这种复杂性实现到您的业务层之前,请尝试诸如乐观ui,请求批处理,缓存等操作。您对失去最多时间的地方有清晰的见识吗?
卢克斯·弗兰肯

4
...不要太希望人们正确看待这些状态。大多数程序仅检查最常见的程序,如果得到意外的状态代码,则失败或行为异常。(我记得DefCon上也有一个演讲,内容涉及通过发送浏览器忽略的随机退出状态来保护您的网站不受爬虫的影响,而只是简单地显示了为什么爬虫有时会出错并因此停止爬取您网站的该部分)。
巴库里

Answers:


21

简短直接的答案

由于请求涉及执行任务列表(任务是我们在此处所说的资源),因此,如果任务组已移至执行(也就是说,无论执行结果如何),那将是明智的响应状态将为200 OK。否则,如果存在阻止任务组执行的问题(例如,任务对象的验证失败),或者例如某些必需的服务不可用,则响应状态应表示该错误。除此之外,当开始执行任务时,看到要执行的任务在请求正文中列出,那么我希望执行结果将在响应正文中列出。


漫长而哲学的答案

您正在经历这个难题,因为您偏离了HTTP的设计宗旨。您不是在与它进行交互来管理资源,而是在将它用作远程方法调用的方式(这不是很奇怪,但是如果没有先入为主的方案,效果会很差)。

鉴于以上所述,并且没有勇气将这个答案转变为冗长的指南,以下是符合资源管理方法的URI方案:

  • /tasks
    • GET 列出所有任务,分页
    • POST 添加一个任务
  • /tasks/task/[id]
    • GET 用单个任务的状态对象响应
    • DELETE 取消/删除任务
  • /tasks/groups
    • GET 列出所有任务组,分页
    • POST 添加一组任务
  • /tasks/groups/group/[id]
    • GET 响应任务组的状态
    • DELETE 取消/删除任务组

该结构只讨论资源,而不是处理资源。资源正在做的事情是另一项服务的关注。

需要注意的另一个重要点是,建议不要在HTTP请求处理程序中长时间阻塞。就像UI一样,HTTP接口应该具有响应能力-在时间尺度上要慢几个数量级(因为该层处理IO)。

转向设计严格管理资源的HTTP接口可能与单击按钮时将工作移出UI线程一样困难。它要求HTTP服务器与其他服务通信以执行任务,而不是在请求处理程序中执行任务。这不是一个简单的实现,而是方向的改变。


如何使用这种URI方案的一些示例

执行单个任务并跟踪进度:

  • POST /tasks 与执行任务
    • GET /tasks/task/[id]直到响应对象completed在显示当前状态/进度时具有正值

执行单个任务并等待其完成:

  • POST /tasks 与执行任务
    • GET /tasks/task/[id]?awaitCompletion=true直到completed具有正值(可能有超时,这就是为什么要循环)

执行任务组并跟踪进度:

  • POST /tasks/groups 与要执行的任务组
    • GET /tasks/groups/group/[groupId]直到响应对象completed属性具有值(显示单个任务状态)为止(例如,完成5个任务中的3个)

请求执行任务组并等待其完成:

  • POST /tasks/groups 与要执行的任务组
    • GET /tasks/groups/group/[groupId]?awaitCompletion=true 直到响应表示完成的结果(可能有超时,这就是为什么要循环)

我认为谈论从语义上讲有意义的是解决此问题的正确方法。谢谢!
安德斯

2
如果尚未出现,我将提出这个答案。这是不可能的,以使单个HTTP请求的多个请求。另一方面,完全可以发出单个 HTTP请求,该请求说“执行以下操作,并让我知道结果是什么”。这就是这里发生的事情。
Martin Kochanski

我将接受这个答案,即使它的票数最多。尽管其他答案也不错,但我认为这是唯一引起HTTP语义原因的答案。
Anders

87

我的投票是将这些任务分解为单独的请求。但是,如果要考虑太多往返行程,我确实遇到了HTTP响应代码207-多状态

从此链接复制/粘贴:

在可能需要多个状态代码的情况下,多状态响应传达有关多个资源的信息。默认的多状态响应主体是带有'multistatus'根元素的text / xml或application / xml HTTP实体。其他元素包含在方法调用期间生成的200、300、400和500系列状态代码。100系列状态代码不应记录在“响应” XML元素中。

尽管将“ 207”用作整体响应状态代码,但是接收者需要咨询多状态响应主体的内容,以获取有关方法执行成功或失败的更多信息。该响应可以用于成功,部分成功以及失败情况。


22
207OP确实确实希望这样做,但是我真的想强调一下,采用这种多请求合一的方法可能不是一个好主意。如果关注的是性能,那么您应该为云风格的水平可伸缩系统(这是基于HTTP的系统
擅长的

44
@DavidGrinberg我完全同意。如果单个动作很便宜,那么处理请求的开销可能比动作本身要高得多。您的建议可能会导致方案中的数据库中多行更新使用每行单独的事务来完成,因为每一行都是作为单独的请求发送的。这不仅效率极低,而且还意味着如果需要的话,将无法原子地更新多行。水平缩放很重要,但不能代替有效的设计。
卡巴斯德,2016年

4
说得好,并指出了由不了解业务需求现实(例如性能和/或原子性)的人们完成的REST API实现的典型问题。例如,这就是为什么OData REST规范在一个调用中具有用于多个操作的批处理格式的原因-确实需要它。
TomTom

8
@ TomTom,OP 不需要原子性。由于原子操作只有一个状态,因此设计起来会容易得多。同样,HTTP规范确实允许通过HTTP / 2复用进行批处理操作以提高性能(自然,HTTP / 2支持是另一回事,但规范允许这样做)。
Paul Draper

2
@David过去曾处理过一些HPC问题,以我的经验,发送单个字节的成本与发送一千个字节几乎相同(当然,不同的传输介质具有不同的开销,但很少比这更好)。因此,如果要考虑性能,我看不到发送多个请求不会有太大的开销。现在,如果您可以在同一个连接上多路复用多个请求,则此问题将消失,但是据我了解,这只是HTTP / 2的一个选项,并且对它的支持非常有限。还是我错过了什么?
Voo

24

尽管可以使用多状态,但如果所有请求成功,我将返回200(一切正常),否则返回错误(500或207)。

标准大小写通常应为200-一切正常。客户只需要检查一下。而且只有在发生错误情况时,您才能返回500(或207)。我认为207在发生至少一个错误的情况下是一个有效的选择,但是如果您将整个包裹视为一次交易,则您也可以发送500。-客户端将希望以任何一种方式解释错误消息。

为什么不总是发送207? -因为标准案例应该简单而标准。虽然例外情况可能例外。如果例外情况允许,客户只应阅读响应主体并做进一步的复杂决定。


6
我不太同意。如果子请求1和3成功,则您将获得组合资源,并且无论如何都必须检查组合响应。您只需再考虑一种情况。如果响应= 200或子响应1 = 200,则请求1成功。如果响应= 200或子响应2 = 200,则请求2成功,以此类推,而不仅仅是测试子响应。
gnasher729

1
@ gnasher729它实际上取决于应用程序。我想像一个用户驱动的操作,当所有请求成功后,它将(一切正常)简单地进入下一步。-如果发生任何错误(全局状态<= 200),则您必须显示详细的错误并更改工作流程,并且只需要对每个子请求进行一次检查,因为您使用的是“ handleMixedState”函数,而不是“ handleAllOk”函数。
法尔科

它确实取决于它的含义。例如,我有一个控制交易策略的端点。您可以一次运行“启动”标识符列表。返回200表示操作(对其进行处理)成功-但并非所有操作都可能成功启动。顺便说一句,即使在立即结果(即将开始)中也看不到,因为启动可能需要几秒钟。多操作调用中的语义取决于场景。
TomTom

如果出现一般性问题(例如数据库关闭),我也很可能发送500,这样服务器甚至不会尝试单独的请求,而只会返回一般性错误。-因为有3个不同的结果给用户1.一切正常,2.常规问题,不起作用,3.一些请求失败。->通常会导致完全不同的程序流程。
法尔科

1
好的,所以一种方法是:207 =每个请求的个人状态。其他:返回的状态适用于每个请求。对于
200、401

13

一种选择是始终返回状态码200,然后在JSON文档主体中返回特定的错误。这正是某些API的设计方式(它们总是返回状态码200并在正文中分派错误)。有关不同方法的更多详细信息,请参见http://archive.oreilly.com/pub/post/restful_error_handling.html


2
在这种情况下,我喜欢这样的想法:使用200来表示一切都很好,接收到请求并且该请求是有效的,然后使用JSON提供有关该请求中发生的事情(即事务处理的结果)的详细信息。
rickcnagy

4

我认为neilsimp1是正确的,但是我建议对发送的数据进行重新设计,以使您可以206 - Accepted稍后发送和处理数据。也许带有回调。

尝试在单个请求中发送多个动作的问题恰恰是每个动作都应具有自己的“状态”这一事实

查看导入CSV(我不知道OP到底是什么,但它是一个简单的版本)。POST CSV并返回206。然后可以导入CSV,并且可以使用针对每个行显示错误的URL的GET(200)获取导入状态。

POST /imports/ -> 206
GET  /imports/1 -> 200
GET  /imports/1/errors -> 200 -> Has a list of errors

相同的模式可以应用于许多批处理

POST /operations/ -> 206
GET  /operations/1 -> 200
GET  /operations/1/errors -> 200 - > Has a list of errors.

处理POST的代码仅需要验证操作数据的格式有效。然后,在稍后的时间,可以执行操作。例如,在后台工作人员中,您可以更轻松地扩展规模。然后,您可以随时查看操作的状态。您可以使用轮询或回调,流或其他方法来满足需要了解一组操作何时完成的需要。


2

这里已经有很多好的答案,但是缺少一个方面:

您的客户期望的合同是什么?

HTTP返回码至少应指示成功/失败区别,从而起到“穷人例外”的作用。然后200表示“合同已完全履行”,而4xx或5xx表示未履行。

天真的,我希望您的多项操作请求的合同是“执行我的所有任务”,并且如果其中一项失败,则该请求不会(完全)成功。通常,作为客户,我会把200理解为“一切都很好”,而400和500家族的代码迫使我考虑(部分)失败的后果。因此,对于部分失败,将200用于“所有任务已完成”,并使用500加上描述性响应。

一个不同的假设合同可能是“尝试执行所有操作”。如果某些行动失败,则完全符合合同规定。因此,您总是返回200加上结果文档,在其中您可以找到各个任务的成功/失败信息。

那么,您要遵循什么合同?两者都是有效的,但是第一个(对所有情况而言,只有200个)对我来说更直观,并且与典型的软件模式更好。对于(希望)服务完成所有任务的大多数情况,客户端可以很容易地检测到这种情况。

最后一个重要方面:您如何将合同决定传达给客户?例如在Java中,我将使用“ doAll()”或“ tryToDoAll()”之类的方法名称。在HTTP中,您可以相应地命名端点URL,希望您的客户端开发人员可以看到,阅读和理解命名(我不会打赌)。选择合同最少的另一个原因。


0

回答:

每个动作只使用一个请求,并接受额外的开销。

状态码描述了一项操作的状态。因此,有意义的是每个请求执行一次操作。

多个独立的操作破坏了请求-响应模型和状态码所基于的原理。你在打自然。

HTTP / 1.1和HTTP / 2降低了HTTP请求的开销。我估计在极少数情况下,建议独立批处理请求。


那就是

(1)您可以使用PATCH请求(RFC 5789)进行多次修改。但是,这要求更改不独立;它们是原子应用的(全部或全部)。

(2)其他人指出了207多状态代码。但是,这仅针对WebDAV(RFC 4918)(HTTP的扩展名)定义。

207(多状态)状态代码提供了多个独立操作的状态(更多信息,请参见第13节)。

...

在可能需要多个状态代码的情况下,多状态响应传达有关多个资源的信息。“ multistatus”根[XML]元素以任何顺序保存零个或多个“ response”元素,每个元素都具有有关单个资源的信息。

在非WebDAV API中,207 WebDAV XML响应就像鸭子一样奇怪。不要这样


1
您实际上是在声明@Anders存在XY问题。您可能是正确的,但不幸的是,这意味着您实际上尚未回答他提出的问题(用于多操作请求的状态码)。
Azuaron

2
@Azuaron,哪种皮带最适合殴打孩子?我认为“不适用”是可以接受的答案。此外,安德列斯(Andres)在他的想法列表中包含了多个请求。我全心全意地支持这一选择。
Paul Draper

我莫名其妙地错过了他列出的内容。在这种情况下,我认为这是一个愚蠢的问题,尊敬的阁下!
Azuaron

1
@Azuaron我绝对认为这是一个有效的答案。如果我做错了所有事情,我希望有人这样说,而不是给我有关如何最好地驶离悬崖的指示。
安德斯

1
只要正确设置了Content-Type标头并匹配客户端要求的内容(Accept标头),就不会禁止在207响应中发送JSON。
支石墓

0

如果您确实需要在一个请求中包含多个操作,为什么不将所有操作包装在后端的事务中?这样,它们要么全部成功,要么全部失败。

作为使用API​​的客户端,我可以处理API调用中的全部成功或失败。部分成功很难解决,因为我将不得不处理所有可能的结果状态。


2
我假设如果请求应该是原子的,他就不会发布此问题。
安迪

@Andy也许吧,但是你不能认为他已经考虑了这种设计的所有含义。
院长

该请求不应是原子请求-例如,如果#2失败,则#1所做的更改仍应保留。因此,将所有内容包装在一个事务中不是一种选择。
安德斯
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.