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

转载自   架构必备「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文件读取到的,如果读取错误或者文件不存在,则默认值为

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/325413.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

比较两个title是否相等(差点把我送走)

事情是这样的 把相同的分类找出来返回给前端我就比较了title 我用了两个比较字符串,结果vos里面输出的全是null 后来 想到字符串比较的是地址 结束 总结 忘记基础害人不浅

开篇有益-解析微软微服务架构eShopOnContainers(一)

为了推广.Net Core,微软为我们提供了一个开源Demo-eShopOnContainers,这是一个使用Net Core框架开发的,跨平台(几乎涵盖了所有平台,windows、mac、linux、android、ios)的,基于微服务架构的&…

React打包运行

项目打包运行 npm run build //生成打包文件 npm install -g serve //全局下载服务器包 serve build //通过服务器命令运行打包项目 访问: http://localhost:5000 //浏览器访问

端午将至……想和程序猿Coding个粽子行不行?

端午将至……都说程序猿无所不能……不造 Coding个粽子行不行? Build Tour 2017 世界巡回展已经开始倒计时……大家都报完名了么? 反正,M姐现在已经安排了我软的程序猿们不远万里的来到中国,他们将会在上海和北京两地,…

把本地文件上传到gitee

第一步 先克隆远程文件到本地 第二步 上传本地文件到远程

【活动】HoloLens 黑科技等你来探秘

微软全息眼镜(HoloLens)是微软最新推出的混合现实头显设备。酷炫的全息图像和包括语音控制和手势控制的全自然交互手段给用户带来前所未有的体验,目前已经在制造、医疗、建筑、娱乐等多个行业展现了非常好的应用前景和市场潜力。HoloLens既是…

ASP.NET Core MVC 模型绑定用法及原理

前言 查询了一下关于 MVC 中的模型绑定,大部分都是关于如何使用的,以及模型绑定过程中的一些用法和概念,很少有关于模型绑定的内部机制实现的文章,本文就来讲解一下在 ASP.NET Core MVC 中模型绑定是如何实现的,以及它…

IDEA无法加载log文件

如图所示,无论怎么生成log文件,idea文件列表始终不显式 解决方法 打开setting 打开File Types 选择文本文档Text,添加后缀*.log

使用sqlserver搭建高可用双机热备的Quartz集群部署【附源码】

一般拿Timer和Quartz相比较的,简直就是对Quartz的侮辱,两者的功能根本就不在一个层级上,如本篇介绍的Quartz强大的集群机制,可以采用基于 sqlserver,mysql的集群方案,当然还可以在第三方插件的基础上实现q…

一个正则表达式酿成的惨案

转载自 一个正则表达式酿成的惨案 导读:正则表达式是程序员经常使用的工具之一。本文作者通过一个正则表达式的陷阱,先深入剖析了出现问题的原因,后给出怎么处理这类问题的方法。最后还给出了一些检测常见正则表达式问题的工具&#xff0c…

详解C# Tuple VS ValueTuple(元组类 VS 值元组)

C# 7.0已经出来一段时间了,大家都知道新特性里面有个对元组的优化:ValueTuple。这里利用详尽的例子详解Tuple VS ValueTuple(元组类VS值元组),10分钟让你更了解ValueTuple的好处和用法。 如果您对Tuple足够了解&#…

Eclipse把默认为Gbk的编码变为UTF-8

菜单栏Windows–>Preferences,左侧导航栏展开General–>Workspace,修改左下角的Text file encoding,选中Other改为UTF-8即可

从LINQ开始之LINQ to Objects(上)

LINQ概述 LINQ,语言集成查询(Language Integrated Query),它允许使用C#或VB代码以查询数据库相同的方式来操作不同的数据源。 1.LINQ体系结构 从上图可以看出,LINQ总共包括五个部分:LINQ to Objects、LINQ to DataSets、LINQ to …

单点登录终极方案之 CAS 应用及原理

转载自 单点登录终极方案之 CAS 应用及原理 Cookie的单点登录的实现方式很简单,但是也问题颇多。例如:用户名密码不停传送,增加了被盗号的可能。另外,不能跨域! 1、基于Cookie的单点登录的回顾 基于Cookie的单点登录…

微软亚太区资料科学总监:R 语言是 VS 生态第一顺位

微软亚太区资料科学总监Graham Williams 微软在2015年并购R语言工具商Revolution Analytics之后,随即在2016年,也开始在自家主力开发工具Visual Studio上,支持R语言。微软将如何定位R语言在微软开发工具链的位置?微软亚太区资料科…

java中如何数组是如何赋值的?

由于数组是引用类型,故无法与变量赋值的方式一样,int a 10;int b a; 那么数组是如何赋值的呢? 是这样赋值的: public static void arrayFuZhi(){//八斤的身高和体重int [] ba {170,80};//九斤的身高和体重与八斤的一样int [] …

从LINQ开始之LINQ to Objects(下)

前言 上一篇《从LINQ开始之LINQ to Objects(上)》主要介绍了LINQ的体系结构、基本语法以及LINQ to Objects中标准查询操作符的使用方法。 本篇则主要讨论LINQ to Objects中的扩展方法以及延迟加载等方面的内容。 扩展方法 1.扩展方法简介 扩展方法能够向…