网站建设html,饿了么网站开发费用,做网站与运营一般多少钱,南昌做网站后台投票转载自 架构必备「RESTful API」设计技巧经验总结
【译者注】本文是作者在自己的工作经验中总结出来的RESTful API设计技巧#xff0c;虽然部分技巧仍有争议#xff0c;但总体来说还是有一定的参考价值的。以下是译文。
简单说一下代码重用
记得在Ken Rogers的Medium博客…转载自 架构必备「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的“仪表盘设置”屏幕其中“服务的用户体验”的一些非关键性选项可以单独地编辑和保存而不需要点最下面的提交按钮。
对于POSTPUT或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访问令牌中时请尽可能地让这个信息小巧身份验证令牌的生命期不需要很长因此没必要。如果可以的话只序列化用户的uidid就可以了其余的可以通过“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文件读取到的如果读取错误或者文件不存在则默认值为。