作者Hsins (迅雷不及掩耳盗铃)
看板Soft_Job
标题Re: [讨论] API没资料,回200还是404比较好
时间Wed Jun 22 21:51:24 2022
: 推 rollr: 回404的可以 fired 了 06/22 19:54
: → moom50302: 回404来乱的吗? 06/22 20:45
嗯,我想两位的建议可以寄信向 GitHub 和
Atlassian 这两间公司说明一下,或许可以
帮他们团队缩减人力。
当查询资源不存在时返回 HTTP Code 404:
https://i.imgur.com/67gl9w8.png
https://i.imgur.com/Mysgpr4.png
[REF]
https://docs.github.com/en/rest
[REF]
https://docs.atlassian.com/ConfluenceServer/rest/7.18.1/#-getNodeById
---
在往下展开之前,我必须说下面的内容是以
RESTful API 设计风格的角度去看待,实现
上还有其他的方式可以采用。
REST = Representational State Transfer
‧网路上的资源,透过 URI 与之对应
‧资源可以有不同的表现形式,透过请求时
携带的 Content-Type/Accept 指定
‧透过 HTTP 协议方法转化资源状态
在 RESTful 架构中,每一个 URI 对应一种
资源,所以设计上会避免路径出现动词(动
词用以转化资源状态,以 HTTP 协议的方法
来对应),而以名词呈现;加上资料库中存
放资料通常为资料的集合,因此多采用复数
形式(多个用户/多篇文章/多个仓库…)
---
以 GitHub REST API (v3) 的设计来回应推
文中提到的几个问题,举例来说:
[GET] /users
获取使用者清单
[GET] /users/<USERNAME>
获取指定使用者资讯
‧如果 USERNAME 不存在,返回 404
[GET] /users/<USERNAME>/followers
获取指定使用者的追踪者
‧如果 USERNAME 下没有追踪者
返回 200 与空阵列
---
差异在於以 REST 设计时,倘若今天你要拿
到 USERNAME 去进行请求,会假定你已经知
道要拿这个使用者下面的资料了,如果是使
用者不存在,代表是 Client 的问题,企图
存取没有的资源,当然是 404。
而访问指定 USERNAME 下面的追踪者时,是
要获取资源清单,所以这时的空阵列就是他
的清单,只是空无一人,并不是没有清单,
自然是 200 表示请求成功。
至於有人问说返回 404 怎麽跟 API 不存在
作区别,其实从上述的范例看的出来,今天
的 <USERNAME> 相当於是查询用的参数,访
问这个资源的确不存在,查询资源的 API的
确不存在,两件事情都成立呀!
因为此时 REST 设计要告诉你的就是「路径
不存在,路径又对应资源,资源不存在」
---
另外提一下推文有的一些谬误:
‧HTTP Status Code 中的 4xx 代表客户端
错误,而 5xx 才是伺服器错误,所以系
统错误更不该返回 404…
‧在 REST 中用 GET 拿资料,而 204 其实
代表的是「请求成功,但没有 BODY」,
这个比较常用在 DELETE 操作…
---
顺带提一下,规范跟设计上是需要做取舍的
,比如 GitHub 其中有一段关於授权认证的
叙述:
「Requests that require authentication
will return 404 Not Found, instead of
403 Forbidden, in some places. This is
to prevent the accidental leakage of
private repositories to unauthorized
users.」
当没有授权的使用者去获取仓库清单时,即
使是没有权限,也必须先考虑到隐私性所以
返回 404 而不是 403 避免私有仓库被没有
权限的人知道:
> 没有权限,但访问 A 仓库拿到 403
> 可以猜出有个私有仓库 A
> 但使用者其实不想让人知道他有这个仓库
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 111.82.214.46 (台湾)
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/Soft_Job/M.1655905888.A.728.html
1F:推 Soarwind: 推~ 很清楚, RESTful 是这样 06/22 22:25
2F:推 CMJ0121: 推 06/22 22:35
3F:推 cip604: 推 06/22 22:59
4F:推 yehzu: 推!认同此篇概念 06/22 23:03
5F:推 lovdkkkk: 推 不过最後的 403 404 好像反了 06/22 23:05
6F:推 devilkool: 推 06/22 23:22
7F:推 justaID: 楼上,404 403 的例子应该没有讲反?先验权限回403,缺 06/22 23:24
8F:→ justaID: 乏权限的连资源是否存在都不该让 hacker 直到很合理 06/22 23:24
抱歉,这里的确是我刚刚数字写反了…我修
改了一下并把你想要说的补充上去
9F:→ justaID: *楼楼上06/22 23:24
10F:→ justaID: *知道 (自动选字QQ06/22 23:25
11F:→ justaID: 推这篇对 REST 描述得满清楚06/22 23:26
12F:推 viper9709: 推06/22 23:27
13F:推 justaID: 我刚刚没有注意 GitHub 原文那段,原来 GitHub in some06/22 23:47
14F:→ justaID: places 没权限时回404,我个人本来以为应该优先回 40306/22 23:47
15F:→ justaID: 比较合理 (无论背後资源是否存在,都回 403 并不会让 u06/22 23:47
16F:→ justaID: nauthorized user 知道资源到底在不在),推测也许考量06/22 23:47
17F:→ justaID: 观点是:看到 403,hacker 会觉得背後资源有可能存在,06/22 23:47
18F:→ justaID: 而继续找其他方法突破 auth;但看到 404 很大机率是资源06/22 23:47
19F:→ justaID: 真的不存在,effort 取舍下会放弃 hack 这个资源,避免06/22 23:47
20F:→ justaID: hack 半天一场空06/22 23:47
无论「资源是否存在都回 403」
这件事情卡到的是有公开仓库:
存在的公开仓库有权限 200
存在的公开仓库没权限 200
存在的私有仓库有权限 200
存在的私有仓库没权限 403 -> 被猜出
不存在的公开仓库 404 -> 合理
不存在的公开仓库 403 -> 不合理
21F:推 Y78: 推06/22 23:57
22F:推 justaID: 谢谢原po的列举解说,以 Github 的 case 来说确实卡到了 06/23 00:44
23F:→ justaID: 公开仓库 和私有仓库 是同样的 path pattern,无法在判 06/23 00:44
24F:→ justaID: 断仓库是否存在前就先以 403 阻挡 06/23 00:44
25F:推 genius945: 说得很清楚,推 06/23 01:10
26F:推 Romulus: GitHub不要说REST了,就连网页介面照样丢404 XD 06/23 01:23
27F:推 YYYero: 推 06/23 01:43
28F:推 LeoSW: 推这篇,写得很清楚 06/23 08:15
29F:嘘 DrTech: 原文搞错了。重点不在查询资源存在不存在。而是你认为Clie 06/23 08:34
30F:→ DrTech: nt的Request行为,是正常还是预期外的error。4xx开头是 er 06/23 08:34
31F:→ DrTech: ror 。2xx开头是 Info。 06/23 08:34
32F:→ DrTech: 请参考RFC2616。 06/23 08:34
好了啦,你要扯国际规范我拿给你呀:
https://www.rfc-editor.org/rfc/rfc7231#section-6.3.1
https://www.rfc-editor.org/rfc/rfc7231#section-6.3.5
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.
符合上述我说的 GitHub 设计时的两种状况
:
1. 资源不存在
2. 存在但不想被泄漏
拜托你要酸要嘘之前,更新一下自己的认知
和当前规范有没有差异好吗?自己动手翻一
下你说的 RFC2616 也写了:
https://i.imgur.com/ngpNzDp.png
Obsoleted by: 7230, 7231, 7232, 7233,
7234, 7235
拿明朝的剑斩清朝的官?但其实不论是你说
的 RFC2616 还是 RFC7231/RFC9110 都也没
说错,与微软还有 GitHub 设计的状况都不
谋而合。是你误解了规范的说法,并忽略我
前面说的「在 REST 设计风格角度」下去看
待事情。
---
你说的「重点不在查询资源存在不存在。而
是你认为 Client 的请求行为,是正常还是
预期外的错误」
是你没搞清楚吧?
对於今天 Client 请求一
个不存在的 /users/<USERNAME>来说,当这
个 <USERNAME> 不存在时,就是你所说的预
期外的错误。
因为在 REST 设计的视角上,这是访问一个
不存在的资源,返回 404一点悬念都没有,
他正是你说的预期外的错误,在这个设计风
格下面,要以「资源」和「操作」来考量。
(拜托再往上翻一下国际规范,就我有上
色那一部分)
---
在应用场景下,他预期的是你先透过:
/users 获取 <USERNAME>
/users/<USERNAME>/followers 获取 <USERNAME>
/posts/<POST_ID>/authors 获取 <USERNAME>
...
先拿到 <USERNAME> 才去打 /users/<USERNAME>
---
这是 REST 设计风格的不友善之处,因为拆
分路径和设计返回的内容,切分粒度太细跟
切分粒度太粗,要嘛是我一下就拿到一大包
资料,有很多不必要的资讯,要嘛是我要层
层打好几个请求才能拿到需要的资讯:
‧前者塞太多东西,费流
‧後者跨多个请求,占线
可以改用 GraphQL 设计 API。
也可以单纯用 RPC 方式处理。
33F:推 zxc8787: 推 06/23 08:55
34F:推 BBSealion: 赞赞 06/23 08:56
35F:推 suibo: 推 06/23 09:37
36F:推 zxc6414189: 推 06/23 10:16
37F:推 longlyeagle: nice nice 06/23 10:19
38F:推 TheWhack: 原文那2位版友是在指API回空资料应该要用200而非404吧? 06/23 11:20
39F:→ TheWhack: 就是对应到原po的"空无一人,并不是没有清单"这项 06/23 11:20
40F:→ alan3100: /users/<USERNAME> 已经指定user却找不到是种错误回404 06/23 11:57
41F:→ alan3100: /users/<USERNAME>/followers 没有是一种正常情况回200 06/23 11:57
42F:推 hegemon: 这个是万年议题了...怎麽没有人把204一起拉来参战? 国外 06/23 12:42
43F:→ hegemon: 都是200, 204, 404大乱斗的 06/23 12:42
44F:推 Romulus: 可是明明就有啊.204 06/23 13:13
45F:推 lturtsamuel: 原文在问的不是你这个找不到user的状况吧 比较像是有 06/23 13:37
46F:→ lturtsamuel: 这个user但他没有repo 06/23 13:37
47F:→ Hsins: 原 po 只有说没资料吧? /users/<USERNAME> 当 USERNAME 06/23 13:45
48F:→ Hsins: 不在资料库中,也是没资料... 06/23 13:46
49F:推 TheWhack: 可能要厘清最原po的"没资料",是指null,还是{}或[] ? 06/23 14:09
50F:→ Hsins: 所以应该引导他去思考这件事情,而不是直接就说 404 或 200 06/23 14:48
51F:→ Hsins: 的直接方案,这个从那篇的推文跟我这篇回文,我有将情境和 06/23 14:49
52F:→ Hsins: 前提叙述出来的 06/23 14:49
53F:→ ssccg: 404和403的部分其实两个都可以,只要统一即可 06/23 15:07
54F:→ ssccg: 让存在但没权限和不存在两个状态无法分辨即可 06/23 15:08
55F:→ ssccg: RFC 403的理由不一定要跟权限有关,而404也可以是故意隐藏 06/23 15:11
56F:→ ssccg: 当然如果网站有公开的部分,统一成404比较合理 06/23 15:13
57F:推 davidsky: 下面写得不错但是上面酸他们没必要 他们只用一行字要怎 06/23 17:00
58F:→ davidsky: 怎麽表达404不该用在[] 06/23 17:00
59F:→ davidsky: 而且我看原标题也会觉得再问array为空 单一资源没有的话 06/23 17:01
60F:→ davidsky: 不太可能会想用200 06/23 17:01
61F:→ Hsins: 这麽说吧,我看标题也会这麽认为,而且也同意这样的状况是 06/23 17:15
62F:→ Hsins: 200 并回传空值,但是看到文章内容,会提到 404 通常是与 R 06/23 17:15
63F:→ Hsins: EST 风格的这种资源不存在混淆(不论是发文者或是他看到这 06/23 17:15
64F:→ Hsins: 样实作的那个人)。 06/23 17:15
65F:→ Hsins: 推文的可以选择回文,也可以选择推多行文字。直接用一行字 06/23 17:18
66F:→ Hsins: 不负责任地表达又说可以 fire 人,还真不知道是我比较酸还 06/23 17:18
67F:→ Hsins: 是他们比较酸? 06/23 17:18
68F:推 janbarry168: 推 06/23 19:17
69F:推 zegas: 推 06/23 19:55
70F:推 wwfwwf: 推 06/23 20:57
71F:嘘 DrTech: 原文拿Restful惯例(非国际标准),来战国际标准。搞错优先 06/23 21:49
72F:→ DrTech: 权啦。 06/23 21:49
你是不是搞错什麽了?你所说的国际标准考
虑到 REST 的使用,在 2014 年增修了内容
,然後你一直说他增添的内容不符合国际标
准,大叔你是在哈罗?
https://i.imgur.com/r3WxHVV.png
不要只是嘘文啊,拿你的国际标准写篇文反
驳一下呀。我先跟你说在 RFC 7231 的第二
章就引用 [REST] 添加了资源(resource)
和表现层(Representations)的叙述。
拿出点证据来嘛!当大家没看过文件?「说
话科学点,是身为工程师最基本的吧?」我
记得这好像是你说的?
73F:推 iamOsaka: 推 06/23 22:09
74F:推 lovdkkkk: 倒是讲到个点了,之前就想回标准是方便大家对齐用的,不 06/23 23:02
75F:→ lovdkkkk: 是权威铁律,如果自己用标准感觉不方便可以不用符合,如 06/23 23:03
76F:→ lovdkkkk: 果大家都照标准走都觉得不方便也可以修 06/23 23:03
77F:→ lovdkkkk: 看到修文讲到回一下 (飘走) 06/23 23:05
78F:→ Hsins: 像是 Meta 家的就没有很依照这篇提的 REST 风格(即使他们 06/23 23:07
79F:→ Hsins: 宣称是 REST API) 06/23 23:09
80F:推 SMMIT: 推 详细 06/23 23:24
81F:→ mTwTm: 我觉得其实拿 RFC 2616 的叙述来佐证 API 设计很奇怪,他 06/24 03:08
82F:→ mTwTm: 当下主要目的就是设计给 hypermedia 资源的存取,只是後来 06/24 03:08
83F:→ mTwTm: 有些人决定也沿用这个来当 API 的协定当然现在就渐渐变成 06/24 03:08
84F:→ mTwTm: 常见的做法。就是因为後来才借用当然就需要一个 adapter 06/24 03:08
85F:→ mTwTm: 虽然不一定要是 rest 但不考虑中间这层直接去引用 http 的 06/24 03:08
86F:→ mTwTm: 叙述我是觉得一定会因为文件资源跟资讯的目的不同而对不上 06/24 03:08
上面并不是拿 RFC 2616 来佐证设计,而是
拿修正後的 RFC 723X 来说明 REST 风格的
合理性。
在 RFC 2616 还可以看到 URL 和 URN 的描
述,在 RFC 723X 就几乎回归到更为抽象的
URI 上了,更加强调「资源」的抽象概念。
你所谓中间那层的 adapater 其实就是文件
中对於 representation 的描述,而这也是
REST 风格的一部分:
「一个 URI 对应一种资源」
不论今天这个资源是一张图片、一段文字、
一个页面,都对应成一个 URI来表示。你要
提供资料库中的使用者资讯、文章资讯,当
然也是可以,转化成一个资源并对应,而在
目前多会以方便解析的结构化 JSON/XML 格
式表示这个资源。
另外必须补充一下,现代的客户端并不是只
有网页浏览器,如果都以浏览器的角度去看
的话,就很容易会因为你说的这个差异混淆
…
87F:→ ssccg: RESTful的精神就是回归hypermedia,而不是用cgi的角度在看 06/24 03:57
88F:→ ssccg: 不管背後是个File system还是Web framework,URI呈现出来的 06/24 03:59
89F:→ ssccg: 就是资源、就跟一个网址对应一个网页都一样 06/24 04:00
90F:→ ssccg: 反过来说不把URI当资源而是API端点的话,根本不用分path 06/24 04:03
91F:→ ssccg: 像一般RPC把要做什麽也都放在参数不是更单纯不会有404? 06/24 04:07
92F:→ ssccg: 就是在Web API多数分path有分GET、POST,有的时候用4xx有时 06/24 04:12
93F:→ ssccg: 候又用body内自定义code,没一个原则,才会有人提RESTful 06/24 04:14
94F:→ ssccg: 用本来就存在、实作也大致符合标准的HTTP来当这原则 06/24 04:18
※ 编辑: Hsins (111.82.214.46 台湾), 06/24/2022 09:22:36
95F:→ mTwTm: 我的意思就是怎麽样都应该拿後期的 RFC 而不是 2616 06/24 12:30
96F:→ mTwTm: 也不是说一定要拿 RFC 但不能直接套用 2616 (回应科技博 06/24 12:31
97F:→ mTwTm: 正是因为是不是从浏览器出发的这个差异所以看旧的 RFC 的 06/24 12:32
98F:→ mTwTm: 时候要意识到当初他的设计不能用现代的方式自行解读 06/24 12:32
99F:推 fadeawaygod: 这篇才是正解,回200与204都是积非成是 06/24 15:07
100F:推 Romulus: 我觉得MT是最被看破手脚的 大概可以想成他其他话题 06/24 16:26
101F:→ Romulus: 很呛可能也是用类似的一知半解就不知道在呛什麽…… 06/24 16:26
102F:推 mirror0227: 推 06/26 03:27