对于一个有效请求但是没有数据的情况,适当的REST响应代码是什么?

547
例如,您发送了一个GET请求,请求的是users/9,但是没有ID为9的用户。 最合适的响应代码是哪个?
  • 200 OK
  • 202 Accepted
  • 204 No Content
  • 400 Bad Request
  • 404 Not Found

40
提示:你找到第九个用户了吗? - Chris Pfohl
69
那么,用户9 没有被找到 - Tomasz Nurkiewicz
32
@IMB是谁在说204?"No Content"表示你正在寻找的实体存在,但没有呈现。例如,如果id为15的博客没有评论,并且您不想返回空列表以获取博客15的评论:"/blog/15/comments"将返回NoContent。另一方面,如果博客15确实存在,则更适合使用'404 Not Found'。 - Chris Pfohl
17
@Crisfole,你的意思是_“另一方面,如果博客15不存在,则应使用‘404未找到’”_。 - gdoron
22
当然,我做到了 @gdoron! :) 谢谢。可悲的是,我晚了大约三年才能编辑和修复它。 - Chris Pfohl
显示剩余12条评论
29个回答

522
我强烈反对使用404,建议使用204或200状态码,并返回空数据。或者至少应该使用一个包含错误信息的响应实体作为404的替代方案。
请求已经被接收并且服务器已经正确处理了请求,可能客户端没有出错,因此整个客户端错误代码(4xx)可能不适合使用。
更重要的是,404会因为多种技术原因而发生。比如,服务器上的应用程序暂时停用或卸载,代理连接出现问题等等。
当然,5xx错误类别存在针对这些情况,但实际上受影响的中间件组件通常无法知道错误是在他们这一侧,然后假设错误是在客户端那一侧,然后用404代替500/503响应。
因此,仅根据状态码,客户端无法区分404表示“您要查找的内容不存在”和404表示“某些事情出了问题,请将此错误报告给运维团队”。 这可能是致命的:想象一下公司中列出所有有权获得年度奖金的员工的会计服务。 不幸的是,在第一次调用它时,它返回了404。 这意味着没有人有资格获得奖金,还是应用程序当前正在进行新部署而返回404的Tomcat实际上应安装到应用程序本身而不是Tomcat的情况下?这两种情况产生了相同的状态码,但在意义上有根本的不同。
因此,对于需要明确知道请求资源不存在的应用程序来说,404没有响应实体几乎是不可取的。 此外,许多客户端框架会通过抛出异常来响应404,这迫使客户端开发人员捕获该异常,进行评估,然后基于此决定是否将其记录为由监控组件拾取的错误或是否将其忽略。 我认为这并不美观。404状态码优于204状态码的原因在于它可以返回响应实体,其中包含关于未找到所请求资源原因的一些信息。但如果这确实很重要,那么也可以考虑使用200 OK响应,并以一种使得负载数据中允许错误响应的方式设计系统。或者,可以使用404响应的负载来向调用方返回结构化信息。如果他收到了一个HTML页面而不是他可以解析的XML或JSON,则这是一个好的指标,表明发生了技术故障,而不是一个可能从调用方的角度视为有效的“无结果”回复。另外,也可以使用HTTP响应头进行这样的处理。
但我仍然更喜欢使用空响应的204或200。这样可以将请求的技术执行状态与请求的逻辑结果分开。2xx表示“技术执行正常,这是结果,请处理”。
我认为在大多数情况下,应该由客户端决定是否接受空结果。通过尽管正确的技术执行而没有响应实体返回404,客户端可以决定将情况视为错误,而实际上并非如此。
另一个角度:从运营的角度来看,404可能会带来问题。因为它可能表明连接/中间件问题而不是有效的服务响应,我不想在我的指标/仪表板中有一个波动的数量的“有效”404,因为这可能掩盖了应该被调查和修复的真正技术问题(例如,在请求路由中某个地方配置不正确的代理)。一些API甚至使用404而不是401/403(例如gitlab做这样的事情),以隐藏请求URI有效但请求缺乏访问它的授权信息的信息。在这种情况下,404也应被视为技术错误,而不是有效的“未找到资源”的结果。

编辑:哇,这引起了很多争议。以下是反对404的另一个论点:严格从HTTP规范(RFC7231)的角度来看,404甚至并不意味着资源不存在。它仅表示服务器没有所请求的资源的当前表示,并且这可能只是暂时的。因此,从HTTP规范的角度来看,404在有关所请求的内容不存在方面本质上是不可靠的。 如果要明确表示所请求的内容不存在,请不要使用404。


60
“未找到”出现的“技术性”原因也被称为服务器错误。这些应该在500范围内。具体来说,“无法找到服务”是 503 服务不可用 - Chris Pfohl
74
问者也在询问一个具体的资源:单个用户(不是/users路由,而是/users/9,即“被称为#9的用户”),所以你的“空结果集”比较没有意义。404表示该对象不存在。 - Chris Pfohl
66
404仅表示所请求的资源(在这种情况下是用户编号9)未被找到。它与应用程序代码是否触发无关,也与后端应用程序是否响应无关。该问题涉及的是Web服务器,问题中没有提到反向代理。 - Chris Pfohl
64
这个答案的推理非常错误。 - Kurt Spindler
38
404:客户端与给定服务器通信成功,但是服务器无法找到所请求的内容。字面意思是:请求被接收,它是良好且正确格式化的(错误请求400),它已经被认证或公开(未经授权401/禁止403),无需付款(需要付款402),请求方法正确(不允许使用的方法405),请求接受可以满足(406不可接受)。当用户获得资源时应使用200 OK。空不是资源。400区间用于客户端错误 500区间用于服务器错误简而言之:您的推理有误。 - Derk-Jan
显示剩余29条评论

411

TL;DR: 使用 404

详见这篇博客。它对此有很好的解释。

博客评论对204的总结:

  1. 204 No Content 对于浏览器而言并不是非常有用(尽管按照HTTP规范,浏览器确实需要将其视为“不要更改视图”的响应代码)。
  2. 204 No Content在ajax Web服务中非常有用,因为可以在不必返回任何内容的情况下指示成功(特别是在不需要反馈的DELETEPOST等情况下)。

因此,针对您的问题,您应该使用404。在响应GET请求时,204是一种专门的响应代码,您不应经常向浏览器返回此代码。

其他响应代码甚至比204404还不合适:

  1. 200应该与您成功获取的任何内容一起返回。当您想获取的实体不存在时,这是不合适的。
  2. 202用于服务器已经开始处理对象但对象尚未完全准备好的情况。这里显然不是这种情况,您不会在响应GET请求时开始或将要开始构建用户9。这样做会违反各种规则。
  • 400用于响应格式错误的HTTP请求(例如,格式不正确的HTTP头、段错误顺序等)。这通常会由您使用的框架处理。除非您正在从头开始编写自己的服务器,否则您不必处理此问题。编辑:新版RFC现在允许将400用于语义无效的请求。
  • 维基百科的HTTP状态代码描述特别有帮助。 您还可以在HTTP/1.1 RFC2616文档中查看相关定义,该文档可在 www.w3.org 获取。


    13
    注意:以2开头的响应代码表示成功。以4开头的响应代码表示失败。总结、第一点和第二点是关于 204 响应代码(无内容)的内容。 - Chris Pfohl
    354
    作为一个开发者,我非常反对在调用成功但没有记录返回时使用404作为响应。在处理非网络应用的API时,我曾经浪费了数小时联系API的开发人员,以追踪我正在调用的API端点为什么不存在,而实际上它是存在的,只是没有数据报告。至于204对于浏览器来说并不特别有用,这有点本末倒置,因为我们智能设备的API端点的大多数用途都不是基于浏览器的,而那些可能使用AJAX。虽然如此,我还是要扣分。 - Matt Cashatt
    59
    @MatthewPatrickCashatt,您可以自由地按照您的意愿进行点踩。现在我终于明白为什么人们要点踩我了,但是这个理由仍然是错误的。当收到404错误时,它并不意味着路由没有意义,而是表示该位置上没有资源。无论您请求的是/badurl还是/user/9(当不存在这样的用户时),都是如此。开发人员可以通过添加比“未找到”更好的原因短语来帮助解释问题,但这并非必需。 - Chris Pfohl
    32
    基于W3的404定义,特别是服务器未找到与请求URI匹配的任何内容,我倾向于不同意(虽然不会投反对票,请继续阅读)。事实上,Web或应用程序服务器已找到与请求URI匹配的操作,但数据库服务器没有。然而,它也说“当[...]没有其他响应适用时,通常使用此状态代码”,因此我认为这在某种程度上证实了它的使用方式(即使我不同意或者不喜欢它)。 - Daevin
    22
    我认为你没有针对我所说的要点进行回应:404错误会带来很大的困惑和危险。依赖调用者检查原因短语或响应体意味着你不信任状态码,这种情况下它就没有用处了。 - Adrian Baker
    显示剩余29条评论

    132

    起初,我认为返回状态码204是有道理的,但经过讨论后,我相信404才是唯一正确的响应。考虑以下数据:

    用户:John,Peter

    METHOD  URL                      STATUS  RESPONSE
    GET     /users                   200     [John, Peter]
    GET     /users/john              200     John
    GET     /unknown-url-egaer       404     Not Found
    GET     /users/kyle              404     User Not found
    GET     /users?name=kyle`        200     []
    DELETE  /users/john              204     No Content
    

    一些背景:

    1. 搜索返回一个数组,只是没有匹配项,但它有内容:一个空数组

    2. 404 当然最为人所知的是 URL 不被请求的服务器支持,但缺少资源实际上是相同的。
      即使 /users/:nameusers/kyle 匹配,用户 Kyle 也不是可用资源,因此仍然适用 404。它不是一个搜索查询,而是一个动态URL的直接引用,所以它是404。

    3. 在评论中建议后,自定义404消息是帮助API使用者更好地区分完全未知路由和缺失实体的另一种方式。

    无论如何,这是我的建议 :)


    18
    支持这种 REST API 的风格。除非客户端通过调用 /users 或 /users?name=kyle 确认该资源存在,否则不应请求 /users/kyle。 - Gary Barker
    4
    这是我最赞同的答案。完美的展示了我希望API工作的方式。 @GaryBarker 的评论也很完美。如果您按ID查找某个内容,但该内容不存在,那么这是一个错误。在调用之前,您应该知道该ID是否存在。主要是因为获得一些“成功”的空响应只会将错误推迟到后面,可能会在执行 user.name 或其他操作时出现“无法读取未定义属性名称”的错误提示。 - Jamie Twells
    3
    我认为这将导致错误代码404,因为这是一个无效的请求:Kyle不存在,所以搜索他的朋友也不可能。 - Justus Romijn
    3
    这不是一个搜索查询,而是一个动态URL的直接引用,所以会返回404。 - Piotr Dobrogost
    2
    @JustusRomijn 我(当然)同意你的观点。我想再补充一句,我认为这是争论的关键所在:GET /usrs/john 404 Not found。开发人员无法区分输错路径和缺失个人信息之间的区别。这就是为什么我提倡使用 GET /users/kyle 404 No such userGET /usrs/john 404 Not found - Chris Pfohl
    显示剩余6条评论

    32

    如果期望资源存在,但可能为空,我认为更容易的方式是获得一个表示为空的表述信息,而不是没有任何内容的204状态。

    因此,我宁愿用 {"Items": []} 返回200 OK 的/things,而不是204状态,因为这样0项的集合就可以像有一项或多项的集合一样处理。

    我建议保留204 No Content状态用于PUT和DELETE请求,因为对于这些请求来说,确实没有有用的表述信息。

    如果/thing/9不存在,应返回404状态。


    1
    听起来你更喜欢使用一种抽象的形式称为RPC来针对API进行编程。与紧密遵循动词和基于资源的URL(例如customers/1/addressbook)访问资源不同,RPC的方式是调用类似于GetCustomerAddressBook的端点,然后接收数据或者实质上是空值,不必过多担心HTTP的复杂性。两种方法都有优缺点。 - The Muffin Man
    4
    @松饼店老板,我不确定你如何假装知道我更喜欢REST还是RPC,也不清楚这与返回HTTP GET请求的状态码有什么关系。请为我翻译此内容。 - j0057
    当我使用204时,我遇到了一些可怕的缓存问题。Chrome会奇怪地处理请求,并且在网络中不显示任何内容并缓存先前的结果。即使使用了所有的no-cache头文件。我同意这个答案,200似乎是最好的选择,将一个空数组/对象传递给用户。 - Jack
    对于集合,应返回HTTP 200并带有items: [],因为该集合确实存在,但是它是空的。在这里使用204会非常令人困惑,因为它用于不应返回任何内容的API。当请求单个资源时,适当的HTTP状态码是404。 - nod

    30

    在之前的项目中,我使用了404。如果没有用户9,则表示未找到该对象。因此404 Not Found是适当的。

    对于对象存在但没有数据的情况,204 No Content 是适当的。我认为在您的情况下,对象不存在


    20
    有两个问题。一个在标题中,一个在示例中。我认为这部分导致了关于哪个响应是适当的争议。
    标题问题询问空数据。空数据仍然是数据,但不同于无数据。因此,建议请求结果集,即列表,可能来自/users。如果列表为空,则仍然是列表,因此204(无内容)最合适。你刚刚要求了一个用户列表,并获得了一个,只是没有内容而已。
    相反,提供的示例询问特定对象,即用户,/users/9。如果找不到用户#9,则不返回用户对象。您请求了特定资源(用户对象),因为未找到它,因此404是适当的。
    我认为解决这个问题的方法是,如果您可以在不添加任何条件语句的情况下使用响应,则使用204,否则使用404。
    在我的示例中,我可以迭代一个空列表,而无需检查其是否有内容,但我无法在空对象上显示用户对象数据,而不破坏任何内容或添加检查以查看其是否为空。
    当然,如果符合您的需求,您可以使用空对象模式返回对象,但这是另一个主题的讨论。

    17

    简而言之,

    2xx:可选数据:格式正确的URI:标准不是URI的一部分:如果条件是可选的,可以在@RequestBody和@RequestParam中指定,应该返回2xx。例如:按名称/状态筛选

    4xx:期望的数据:格式不正确的URI:条件是URI的一部分:如果条件是强制性的,只能在@PathVariable中指定,则应导致4xx。例如:按唯一ID查找。

    因此,对于所问的情况:

    "users/9" 应该是 4xx(可能是404)

    但是对于 "users?name=superman" 应该是 2xx(可能是204)


    17
  • 对于路径参数,参数是资源路径的一部分。例如在/users/9的情况下,响应应该是404,因为没有找到该资源。在这种情况下,/users/9是资源,结果是单一的或者是错误的,它不存在。这不是一个monad。
  • 对于查询参数,参数不是资源路径的一部分。例如在/users?id=9的情况下,响应应该是204,因为已找到资源/users,但它无法返回任何数据。资源/users存在,结果是n元的,即使为空也存在。如果id唯一,则这是一个monad。
  • 使用路径参数或查询参数取决于具体情况。我更喜欢将路径参数用于必填、规范或标识参数,而将查询参数用于可选、非规范或属性参数(如分页、排序规则等)。在REST API中,我会使用/users/9而不是/users?id=9,特别是由于可能需要嵌套获取"子记录",例如/users/9/ssh-keys/0获取第一个公共ssh密钥,或者/users/9/address/2获取第三个邮政地址。

    我更喜欢使用404。原因如下:

    • 对于一元方法(1个结果)和n元方法(n个结果),在没有充分理由的情况下,调用方式不应该有所不同。如果可能的话,我希望使用相同的响应代码。当然,期望结果的数量是有区别的,比如说,你期望主体是一个对象(一元)或者是一个对象数组(n元)。
    • 对于n元方法,我会返回一个数组,并且如果没有结果,我不会返回“无集合”(no set,即没有文档),而是返回一个空集合(empty set,即空文档,就像JSON中的空数组或XML中的空元素)。也就是说,仍然是200,但是结果数为零。除了在主体中,没有必要将这些信息放在报文中。
    • 204 就像void方法。我不会在GET中使用它,只会在POSTPUTDELETE中使用。我会在GET的情况下做一个例外,其中标识符是查询参数而不是路径参数。
    • 未找到记录就像NoSuchElementExceptionArrayIndexOutOfBoundsException或类似的错误,是由客户端使用不存在的id造成的,因此是客户端错误。
    • 从代码角度来看,收到 204 意味着代码中多了一个可以避免的分支。它会使客户端代码变得复杂,并且在某些情况下也会让服务器代码变得复杂(这取决于您是否使用实体/模型单子或纯实体/模型;我强烈建议远离实体/模型单子,否则可能会导致因为单子而认为操作成功并返回 200 或 204,而实际上应该返回其他东西的恶劣错误)。
    • 如果 2xx 表示服务器按客户端请求的内容执行了操作,4xx 表示服务器未按客户端请求的内容执行操作,那么客户端代码编写和理解起来就更容易了。如果未向客户端提供客户端按 id 请求的记录,则是客户端的责任,因为客户端请求了一个不存在的 id。
    • 最后但并非最不重要的:一致性

      • GET /users/9
      • PUT /users/9DELETE /users/9

      PUT /users/9DELETE /users/9 在成功更新或删除时必须返回204。那么如果用户 9 不存在,它们应该返回什么?在 HTTP 方法不同的情况下,用不同的状态代码表示相同的情况是毫无意义的。

      此外,不是规范原因而是文化原因:如果在 GET /users/9 中使用 204,项目中下一步会发生的事情是有人认为返回 204 对于 n 元方法来说是好的。这会使客户端代码变得复杂,因为客户端现在不仅需要检查 2xx 然后对主体进行解码,还必须具体检查是否为 204 并在这种情况下跳过主体解码。但客户端该怎么做呢?创建一个空数组吗?为什么不把它放在网络上传输呢?如果客户端创建空数组,则 204 是一种愚蠢的压缩方式。如果客户端使用 null,则会引起完全不同的问题。

    14
    根据w3的文章,
    200 OK
    请求成功。响应返回的信息取决于请求中使用的方法。
    202 Accepted
    请求已被接受进行处理,但处理尚未完成。
    204 No Content
    服务器已满足请求,但不需要返回实体正文,并可能希望返回更新的元信息。
    400 Bad Request
    由于格式不正确,服务器无法理解请求。客户端不应在未经修改的情况下重复请求。
    401 Unauthorized
    请求需要用户身份验证。响应必须包括WWW-Authenticate头字段。
    404 Not Found
    服务器没有找到与请求URI匹配的任何内容。没有指示条件是暂时的还是永久的。

    5
    这篇W3的文章讨论了HTTP状态码。HTTP并不等同于REST,REST通常但不一定使用HTTP作为传输协议。404是一个HTTP传输错误代码。如果REST和HTTP相同,难道它不应该被称为HTTP吗? - anneb
    1
    @anneb 正如你所说,REST使用HTTP,因此这个答案完全是有道理的。REST不是HTTP,REST无论如何都不是协议。即使REST与HTTP不完全相同,这个答案也不会失去意义。 - Koray Tugay
    @Koray Tugay:Google 搜索也使用 HTTP,因此根据这个答案,如果搜索没有匹配项,Google 搜索应该响应 HTTP 状态码 404? - anneb
    @anneb,你提到的Google搜索是一个RESTful应用程序吗?我不这么认为。因此,答案将是否定的。回到原始问题和这个答案,而不是陷入兔子洞中。如果Google搜索有一天创建了一个RESTful API(或者它可能已经有了,我不知道),当它找不到结果时,它可能会返回“204 No Content”,而不是“404”。它有一个结果给你,但它没有内容。 - Koray Tugay

    13

    很遗憾,在这个讨论串中,一个如此简单和明确定义的东西却变成了“基于观点”的问题。

    HTTP服务器只知道“实体”,这是任何内容的抽象,无论是静态网页、搜索结果列表、其他实体列表、某个事物的JSON描述、媒体文件等等。

    每个这样的实体都应该可以通过唯一的URL进行识别,例如:

    • /user/9 -- 一个实体:用户ID=9
    • /users -- 一个实体:所有用户的列表
    • /media/x.mp3 -- 一个实体:名为x.mp3的媒体文件
    • /search -- 一个实体:基于查询参数的动态内容

    如果服务器能够通过给定的URL找到资源,那么它的内容是什么并不重要——2G的数据、null、{}、[]——只要它存在,就会返回200。但如果服务器不知道这样的实体,则期望返回404“未找到”。

    一个困惑似乎来自开发人员认为,如果应用程序具有特定路径形状的处理程序,那么它就不应该是一个错误。在HTTP协议的眼中,服务器内部发生了什么(即默认路由器是否响应或特定路径形状的处理程序是否响应)并不重要,只要在服务器上没有与请求的URL匹配的实体(请求的MP3文件、网页、用户对象等),它必须是404(或410等)。

    另一个令人困惑的问题似乎是关于“无数据”和“无实体”的区别。前者涉及实体的内容,而后者涉及其存在性。
    示例1:
    - 无数据:/users 返回200 OK,正文为[],因为没有人注册 - 无实体:/users 返回404,因为没有路径/users
    示例2:
    - 无数据:/user/9 返回200 OK,正文为{},因为用户ID = 9从未输入个人数据 - 无实体:/user/9 返回404,因为不存在用户ID = 9
    示例3:
    - 无数据:/search?name=Joe 返回200 OK [],因为数据库中没有Joe - 无实体:/search?name=Joe 返回404,因为没有路径/search

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接