转载自   架构必备「RESTful API」设计技巧经验总结

【译者注】本文是作者在自己的工作经验中总结出来的RESTful API设计技巧,虽然部分技巧仍有争议,但总体来说还是有一定的参考价值的。以下是译文。

简单说一下代码重用

记得在Ken Rogers的Medium博客里曾经见过这么一句话(原文出自海明威):

我们都是手艺学徒,没有人会成为大师。

在我写这篇文章的时候,我不禁笑了起来,因为从这件事情的背后看到了一个伟大的类比,那就是从其他人那里引用了海明威的话。也就是说,我不需要为了得到类似的功能和结果而花费精力自己去创建一个与众不同的东西,上面提到的海明威的话正是代码重用在文学上的例子。

但是,我在这里不会写代码包的好处,而是更多地提一些我的感受,这些感受会在当前以及未来的项目中积极地得到实现。我还总结了一套API规则和原语,包括了功能和实现细节。

使用API版本控制

如果你要开发一个提供客户端服务的API,你需要为最后可能的修改而做好准备。最好的办法就是通过为RESTful API提供“版本命名空间”来实现。

我们只需将版本号作为前缀添加到所有的URL里即可。

然而,在我研究了其他的API实现之后发现,我喜欢上了这种较短的URL样式,它把api作为是子域名的一部分,并从路由中删除了/api,这样更短、更简洁。

跨域资源共享(CORS)

需要重点关注的是,如果你打算在www.myservice.com上托管你的前端站点,而将API放在另外一个不同的子域上,例如api.myservice.com,那么你需要在后端实现CORS,这样才能使得AJAX调用不会抛出这样的错误。

使用复数形式

当你从/posts请求多个帖子的时候,这样的URL看起来更明了:

更多有关混合类型的信息,请看下文:“使用根级别的‘me’端点(URL)”。

避免查询字符串

查询字符串的作用是对关系数据库返回的记录集做进一步地过滤。

更多信息请看下文:“避免对嵌套路由的操作”。

使用HTTP方法

我们可使用下面这些HTTP方法:

  • GET 用于获取数据。

  • POST 用于添加数据。

  • PUT 用于更新数据(整个对象)。

  • PATCH 用于更新数据(附带对象的部分信息)。

  • DELETE 用于删除数据。

补充一点,对于修改对象的部分内容的请求来说,我认为PATCH是减少请求包大小的一个好的方法,并且它也能很好的跟自动提交/自动保存字段配合起来用。

一个很好的例子是Tumblr的“仪表盘设置”屏幕,其中,“服务的用户体验”的一些非关键性选项可以单独地编辑和保存,而不需要点最下面的提交按钮。

对于POST,PUT或PATCH的成功响应消息,应该返回更新后的对象,而不是只返回一个null。点击这里有一篇http1.0和2.0的对比。

有关响应的其他内容,请阅读下文:“JSON格式的响应和请求”。

使用封包

“我不喜欢数据封包。它只是引入了另一个键来浏览数据树。元信息应该包含在包头中。”

最初,我坚持认为封包数据是不必要的,HTTP协议已经提供了足够的“封包”来传递响应消息。

然而,根据Reddit上的回复所述,如果不封包为JSON数组,则可能会出现各种漏洞和潜在的黑客攻击。

现在建议使用封包,你应该把数据封包后再应答!

同样要重点关注的是,不像其他语言那样,JavaScript之类的语言将会将空对象认为是true! 因此,在下面这种情况下,不要返回空的对象来作为响应的一部分:

JSON格式的响应和请求

所有东西都应该被序列化成JSON。如果你期待从服务器上获取JSON格式的数据,那么请客气一点,请发送JSON格式的内容给服务器。请两边保持一致!

某些情况下,如果动作执行成功(例如DELETE),那我并没有什么需要返回的。但是,在某些语言(如Python)中返回一个空对象可能被认为是false,并且在开发人员调试程序的时候,这种情况并不容易发现。因此,我喜欢返回“OK”,尽管这是一个字符串,但是在返回的时候会被包装成一个简单的响应对象。

使用HTTP状态码和错误响应

因为我们使用了HTTP方法,所以我们应当使用HTTP状态码。 
我喜欢使用这些状态码:

对于数据错误

400:请求信息不完整或无法解析。 
422:请求信息完整,但无效。 
404:资源不存在。 
409:资源冲突。

对于鉴权错误

401:访问令牌没有提供,或者无效。 
403:访问令牌有效,但没有权限。

对于标准状态

200: 所有的都正确。 
500: 服务器内部抛出错误。

假设要创建一个新帐户,我们提供了email和password两个值。我们希望让客户端应用程序能够阻止任何无效的电子邮件或密码太短的请求,但外部人员可以像我们的客户端应用程序一样在需要的时候直接访问API。

如果email字段丢失,则返回400。 
如果password字段太短,则返回422。 
如果email字段不是有效的电子邮件,则返回422。 
如果email已经被使用,返回一个409。

从上面这些情况来看,有两个错误会返回422,不过他们的原因是不同的。这就是为什么我们需要一个错误码,甚至是一个错误描述。要区分代码和描述,我打算将error(代码)作为机器可识别的常量,将description作为可更改的用于人类识别的字符串。点击这里有一篇http1.0和2.0的对比。

字段校验错误

对于字段的错误,可以这样返回:

操作校验错误

对于返回操作校验错误:

这样,你的程序的错误提取逻辑要当心非200的错误了,你可以直接从响应中检查error字段,然后将其与客户端中相应的逻辑进行比较。

status这个字段似乎也很有用,如果你不想检查响应里的元数据,那你可以在需要的时候有条件地添加这个字段。

description可作为备用的用户可读的错误消息。

密码规则

在做了很多密码规则的研究之后,我比较赞同《密码规则是废话》(https://blog.codinghorror.com/password-rules-are-bullshit/)和《NIST禁止做的事情》(https://nakedsecurity.sophos.com/2016/08/18/nists-new-password-rules-what-you-need-to-know/)这两篇帖子的观点。

整理了一些处理密码的规则:

1. 执行unicode密码的最小长度策略(最小8-10位)。

2. 检查常见的密码(例如“password12345”)

3. 检查密码熵(不允许使用“aaaaaaaaaaaaa”)。

4. 不要使用密码编写规则(至少包含其中一个字符“!@#$%&”)。

5. 不要使用密码提示(“assword”这样的)。

6. 不要使用基于知识的认证。

7. 不要超期不修改密码。

8. 不要使用短信进行双认证。

9. 使用32位以上的密码盐(salt)。

在某种程度上,所有这些规则能使密码验证更容易!

使用访问和刷新令牌

现代的无状态、RESTful API一般会使用令牌来实现身份认证。这消除了在无状态服务器上处理会话和Cookie的需要,并且可以很容易地使用Authorization头(或access_token查询参数)来调试网络请求。点击这里有一篇JWT生成token实战。

访问令牌用于认证所有未来的API请求,生命期短,不会被取消。

刷新令牌在初始登录的响应中返回,然后跟过期时间戳和与使用者的关系一起进行散列计算后存储到数据库中。这个长生命期的像密码一样的密钥,可以被用来请求新的短生命期的JWT访问令牌。刷新令牌也可以用于续订并延长其使用寿命,这意味着如果用户持续使用该服务,则无需再次登录。

但是,如果API希望签订一个不同的“密钥”,JWT就会被取消,但是这将使所有当前发出的令牌全部无效,但因为这些令牌是短生命期的,所以这并没有关系。

登录

在我的程序实现中,正常的登录过程如下所示:

1. 通过/login接收邮件和密码。

2. 检查数据库的电子邮件和密码哈希。

3. 创建一个新的刷新令牌和JWT访问令牌。

4. 返回以上两个数据。

续订令牌

正常的续订验证流程如下所示:

1. 尝试从客户端创建请求时,JWT已经过期。

2. 将刷新令牌提交到/renew。

3. 通过将刷新令牌进行哈希与数据库中保存的进行匹配。

4. 成功后,创建新的JWT访问令牌并延长到期时间。

5. 返回访问令牌。

验证令牌

通过检查到期日期和签名哈希可以校验JWT访问令牌的有效性。如果校验失败,则认为是一个无效的令牌。

如果验证通过,则JWT的有效载荷中包含了一个uid,它用于在API响应的上下文中传递一个对应的user对象来检查权限/角色,并相应地创建/读取/更新/删除数据。

终止会话

由于刷新令牌存储在数据库中,因此可以将其删除来“终止会话”。这为用户提供了一个控制方法,即他们可以通过主动的刷新令牌“会话”来保护自己的帐户,并且通过这种方法来进行多次重复认证(通过调整超时时间戳来实现)。

让JWT保持小巧

在把信息序列化到JWT访问令牌中时,请尽可能地让这个信息小巧,身份验证令牌的生命期不需要很长,因此没必要。如果可以的话,只序列化用户的uid(id)就可以了,其余的可以通过“GET /me”来传递。点击这里有一篇JWT生成token实战。

还值得注意的是,存储在JWT有效载荷中的任何敏感信息并不安全,因为它只是一个经过base64编码的字符串。

使用根级别的“Me”端点(URL)

一般人会使用/profile这个URL来提供自身的基本属性。但是,我也看到过比较混论的实现,例如对于/users/:id这种接受整数的URL,它竟然允许传入字符串me来指向自身的属性。

通过/me访问自身信息的更深层次的URL,例如/me的/settings或者/billing信息,而通过users/:id/billing访问其他用户的信息。

避免对嵌套路由的操作

有一个采用了以上一些设计理念的重构的项目,最后却设计出了一个难用的URL系统:

如果要POST上传一个附件,这个URL可能看起来还行,但是如果在开发客户端应用程序时想要实现像对附件标星号这么一个简单操作的功能的话,那你就需要重写相关的代码。相关代码如下:

attachments.js

助手函数的代码如下:

MyComponent.js

如果你把获取附件属性这个功能委派给服务器来实现,并且只使用根级别的URL,这样不是更好吗?

attachments.js

MyComponent.js

总的来说,我认为这两种方法各有各的优势,而我倾向于用一个长的路径来创建/提取资源,用一个短的路径来更新/删除资源。

提供分页功能

分页很重要,因为你不会想让一个简单的请求就获得数千行的记录。这个问题似乎很明显,但是还是会有许多人忽略这个功能。

有多种方法来实现分页:

“From”参数

可以说这是最容易实现的,API接受一个from查询字符串参数,然后从这个偏移量开始返回有限数量的结果(通常返回20个结果)。

另外最好提供一个limit参数来限制最大记录数,例如Twitter,最大限制为1000,而默认限制为200。

“下一页”令牌

如果每页20个结果之外还有其他的结果,谷歌的Places API就会在响应中返回next_page_token。然后,服务器在新的请求中接收到这个令牌后,就会返回更多的结果,并附带新的next_page_token,直到所有的结果全部都返回给客户端。

Twitter使用参数next_cursor实现了类似的功能。

实现“健康检查”URL

很有必要提供一种方法来输出一个简单的响应,以此来表明API实例是活着的,不需要重新启动。这个功能也很有用,通过它可以很方便地检查某个时间点的某台服务器上的API是什么版本,而这无需通过认证。

我提供了status和version这两个值。另外值得一提的是,这个值是从version.txt文件读取到的,如果读取错误或者文件不存在,则默认值为

架构必备「RESTful API」设计技巧经验总结相关推荐

  1. 微服务架构之「 服务注册 」

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 微服务架构是一个庞大复杂的工程,为什么说它庞大复杂呢?因为想要做好微服务,就必须先要建设好微服务所 ...

  2. 微服务架构之「 容器技术 」

    点击上方"方志朋",选择"设为星标" 做积极的人,而不是积极废人 现在一聊到容器技术,大家就默认是指 Docker 了.但事实上,在 Docker 出现之前,P ...

  3. 微服务架构之「 容错隔离 」

    点击上方"方志朋",选择"置顶公众号" 技术文章第一时间送达! 我们知道,在单体应用的架构下一旦程序发生了故障,那么整个应用可能就没法使用了,所以我们要把单体应 ...

  4. 微服务架构之「 配置中心 」

    点击上方"方志朋",选择"置顶公众号" 技术文章第一时间送达! 在微服务架构的系列文章中,前面已经通过文章<微服务架构之「服务网关 」>介绍过了在微 ...

  5. spring.profiles.active配置了没生效_微服务架构之「 配置中心 」

    在微服务架构的系列文章中,前面已经通过文章<微服务架构之「服务网关 」>介绍过了在微服务中服务网关的原理和应用,今天这篇文章我们继续来聊一聊微服务中另外一个重要模块:「 配置中心 」.后面 ...

  6. 微服务架构之「 访问安全 」

    戳蓝字"CSDN云计算"关注我们哦! 作者 | 奎哥 来源 | 不止思考 应用程序的访问安全又是我们每一个研发团队都必须关注的重点问题.尤其是在我们采用了微服务架构之后,项目的复杂 ...

  7. Eggjs笔记:RESTful API的设计

    关于RESTful API 网络应用程序,分为前端和后端两个部分.当前的发展趋势,就是前端设备层出不穷(手机.平板.桌面电脑.其他专用设备-). 因此,必须有一种统一的机制,方便不同的前端设备与后端进 ...

  8. RESTful API的设计原则

    说在前面,这篇文章是无意中发现的,因为感觉写的很好,所以翻译了一下.由于英文水平有限,难免有出错的地方,请看官理解一下.翻译和校正文章花了我大约2周的业余时间,如有人愿意转载请注明出处,谢谢^_^ P ...

  9. RESTful API设计技巧经验总结

    原文:RESTful API Design Tips from Experience 作者:Peter Boyer 翻译:雁惊寒 译者注:本文是作者在自己的工作经验中总结出来的RESTful API设 ...

最新文章

  1. log4.properties文件【笔记自用】
  2. sonarqube扫描安卓代码
  3. JS_14 执行上下文与执行上下文栈
  4. 关于JTAG,你知道的和不知道的都在这里
  5. linux启动脚本卡住,linux 服务脚本启动问题
  6. java 共享session_java session共享
  7. 思科下一代模拟器EVE-NG做一个不同VLAN互通实验
  8. cad道路里程桩号标注_甲级设计院CAD制图技巧与画法讲解! 超实用,值得你收藏~...
  9. 微信小程序商城毕业设计毕设作品(6)任务书
  10. 天涯怎么引流到qq?天涯社区如何靠发帖子引流?
  11. maya mentray_新手快速掌握Maya Mental ray
  12. 通过Godaddy抢注域名
  13. 子母钟运用在什么领域,设备如何调试,天穹告诉你子母钟系统授时的意义
  14. P1571 眼红的Medusa 题解
  15. 关于《学编程,还有必要上大学吗? 》
  16. Adobe系列軟件PC + MAC 2021
  17. 常用颜色的RGB值及中英文名称对照表
  18. 操作系统---计算机系统概述
  19. 16进制转8进制(一般数据)
  20. Ubuntu10.04编译太阳神三国杀

热门文章

  1. 每天一小时python官方文档学习(四)————数据结构之列表
  2. [Nginx]负载均衡和动静分离
  3. [JavaWeb-MySQL]多表查询概述
  4. [蓝桥杯][历届试题]九宫重排-双向bfs和map标记
  5. hash table(开放寻址法-线性探查实现的哈希表)
  6. bim建筑绘图计算机要求,BIM考试报名条件:想要成为一级BIM建模师需要达到哪些要求?...
  7. ubtunu打开firefox_ubuntu中火狐浏览器问题(也许是)
  8. XGBoost-工程实现与优缺点(中)
  9. 【水】几个网络流图论模型的记录
  10. AcWing 320. 能量项链