找不到資料要回 404 還是 200?

TLDR

當找不到目標資源或是不想讓別人知道資源存在的時候,可以回 404。其他看情境回應,有可能是 200,也有可能是其他的狀態碼,例如:曾經存在,可以回 410。

RESTful 亦同。

前情提要

近年來,RESTful 愛好者都會提出使用有意義的 HTTP 狀態碼(status code)來表達回傳內容當下的狀態。在找不到資料的情境時,過去的習慣「可能」是回傳 200,並帶有額外的錯誤碼:

{
"code": "C0001"
}

RESTful 愛好者「可能」會說回傳 200 是上一世紀的做法了,然後提出應該要使用 404 來表達「找不到資源」的意義,因此就開始吵架啦!

了解定義

這裡就不討論 RESTful 與 REST 的差異了。因最原始的論文是 REST,因此這邊開始用 REST 這個名詞。

平常在討論 REST 的時候,通常都會跟 HTTP 一併討論,這是因為 REST 所定義的資源定位與取得方法等(這部分待閱讀),跟 HTTP 的定義不謀而合,這也能解釋 HTTP 最初想達成的目的跟 REST 是相同的。

因此最一開始先不要提 REST 這個名詞,因為它也是建立在 HTTP 的協定上,因此應該是先關注 HTTP 的協定說了什麼。

首先看 404 狀態碼的定義

The 404 (Not Found) status code indicates that the origin server did not find a current representation for the target resource or is not willing to disclose that one exists.

404 想回應的狀態是,當目標資源(target resource,可以理解成 URI,參依 RFC 3986)找不到 current representation,或是不想讓別人知道資源存在。

後者很好理解,在 403 狀態碼的定義能找到說明:

An origin server that wishes to “hide” the current existence of a forbidden target resource MAY instead respond with a status code of 404 (Not Found).

當想「隱藏」目標資源已存在的狀態時,可以用 404 狀態碼來取代 403 狀態碼。

前者則有點複雜,因為它講了另一個概念 Representation

A representation is information that is intended to reflect a past, current, or desired state of a given resource, in a format that can be readily communicated via the protocol.

Representation 反應給定資源的過去、現在或是期望的狀態的資訊。

  • 「現在的狀態」比方說取得資源時,請求 GET /users/123 如果存在,這就代表著現在的狀態(current representation)。
  • 「期望的狀態」比方說更新資源時,請求 PUT /users/123 可以傳入預期的狀態,成功後,資源「現在的狀態」會被更新,而預期的狀態就會變成「現在的狀態」。
  • 「過去的狀態」比方說刪除資源時,請求 DELETE /users/123 使用軟刪除(soft delete)後,設計可能是:GET 請求「現在的狀態」會找不到資源(404),但是後台的另一個 API 則可以取得資源「過去的狀態」。

然後裡面又提到了 representation 可以有多個表示方法:

A target resource might be provided with, or be capable of generating, multiple representations that are each intended to reflect the resource’s current state.

所以 REST 有提到同一個 API 可以回傳 JSON 格式或 XML 格式,而每個回傳代表的都是資源當前狀態,這跟 HTTP 定義相符。

REST 又是怎麼說的?

REST 的論文的第六章是實際應用,其中 6.2 是說明如何把 REST 應用在 URI 上。URI 的定義,這一小節 REST 再一次參考了 RFC(RFC 2369,已被 RFC 9110 提到的 RFC 3986 取代)

接著論文 6.2.1 有提到它重新定義了 URI:

The definition of resource in REST is based on a simple premise: identifiers should change as infrequently as possible.

這個意思是指,同個 URI 指向的資源必須穩定,才能符合 REST 對 URI 的定義。正向的例子如:

GET /book/123
GET /shop/456

只要指向的資源對到同一個實體,那它就符合 REST 的定義。

相反的例子是:

GET /ticket/latest
GET /comment/latest

它們指向的實體不是穩定的,因此不符合 REST 的定義。

那 URI 又是什麼?

URI 的全名為 Uniform Resource Identifier,指的是統一資源標識方法。統一的意思代表是,在某個範圍內的統一,例如 https://google.com 對大部分的使用者來說,能夠取得 Google 首頁,但對某些封閉的國家來說,就無法取得。

參考 RFC 3986 第三章 的 ASCII 圖說明:

  foo://example.com:8042/over/there?name=ferret#nose
\_/ \______________/\_________/ \_________/ \__/
| | | | |
scheme authority path query fragment
| _____________________|__
/ \ / \
urn:example:animal:ferret:nose

以我的經驗來說,path 的部分可以表達定位資源主要的方法,因此寫程式的時候,路由通常會這樣設計:

GET /book/{id}

由不同的 id 來定位到不同的資源。而 query 則是額外輔助定位資源的方法。

上面講的是目前的經驗與 HTTP 的定義,而 REST 的定義尚未確認。

查詢單筆資料

接著,再回頭看一下最初的 404 定義:尋找不到 current representation 代表什麼意思呢?簡單來說有下面這兩種:

  1. 目標資源不存在,原因一是 current representation 沒找到,二是沒有定義目標資源。
  2. 目標資源存在,且 media type 合法(不合法會回 415),但找不到對應的 media type 的 representation。

這可以解釋為什麼在請求 GET /users/123 找不到資料的時候,要回的是 404--要嘛目標資源不存在,要嘛沒有合適的 representation。

查詢多筆資料

有個情境是,請求 GET /users 找所有或多筆使用者,如果找不到使用者時,應該要回 404 還是 200?

這時要看目標資源的 representation 是如何定義的。若是在找不到資料的時候,能夠對應出 representation,那就應該要回 200。以要查多筆使用者來說,資料通常會是以列表(list)型態輸出,空列表是合理的 representation,也能表達出資源現在的狀態,因此可以回應 200。

相對地,當單筆資料找不到時,若要回傳 200 與一個空的實體(entity),意思是指「資源現在的狀態存在,而實體是空的」。這件事是不合理的,因為資料不存在的話,是無法建立實體的。

結論

以 HTTP 協定或 REST 概念來設計的話:

  • 查詢單筆找不到回 404
  • 查詢多筆找不到回 200 + 空列表

若團隊有自己規範的話,那就依照團隊規範來執行。

順帶一提:

找不到資料回 404 狀態碼,不是什麼新方法,而是上古時代(1992 年 HTTP/0.9)的做法。

The server has not found anything matching the URI given

參考資料

  • RFC 3986
  • RFC 9110
  • REST - Fielding, R.T., “Architectural Styles and the Design of Network-based Software Architectures”, Doctoral Dissertation, University of California, Irvine, September 2000,