查看: 1000|回复: 0

[DIV/CSS] 10个关于Node.js REST API 的最佳实践

发表于 2017-7-22 08:00:02

在这篇文章里,我们将介绍Node.js REST API的最佳实践,包括关于路由命名,身份认证,黑盒测试,使用恰当的网络缓存等内容。

一个最流行的Node.js RESTful API监听工具 Trace ,通过Trace,我们帮助我们的用户寻找程序中的问题。我们的经验告诉我们,开发者开发REST API时有很多问题。

我希望这些用在 RisingStack 上的这些最佳实践能够帮助大家。

1 使用HTTP方法和API路由

想象一下,你写的Node.js RESTful API可以用于新建,更新,检索或者删除用户。 对于这些业务操作,HTTP已经有了成套的工具箱: POST , PUT , GET , PATCH 或 DELETE 。

作为最佳实践,你的 API路由应该使用名词作为资源标识符 。说到关于用户的资源,你可以像下面这样构建:

POST /user 或 PUT /user:/id 创建新用户

GET /user 查找一些用户

GET /user/:id 查找某个用户

PATCH /user/:id 编辑修改一个存在用户的信息

DELETE /user/:id 删除用户

2 正确使用HTTP状态码

当请求出现错误的时候,你必须返回相应的状态码:

2xx , 一切正常

3xx , 资源被移除

4xx , 客户端错误 (比如请求一个不存在的资源)

5xx , API(服务端)错误 (比如出现异常)

如果你使用Express,设置状态码非常简单 res.status(500).send({error: 'Internal server error happened'}) ,Restify框架的写法也差不多 res.status(201) 。

3 使用HTTP头发送元数据(Metadata)

在HTTP header上添加关于有效载荷(payload)的元数据(metadata),适用于以下场景:

分页

限制访问频率

身份验证

如果你需要在你的HTTP header里设置一些定制的元数据,最佳实践是在定制内容前加 X 。 举个例子,如果你在使用CSRF token,通用的命名方法(不是规范)是命名为 X-Csrf-Token 。当然通过 RFC 6648 的方式被弃用。创建的API时,要尽最大的可能避免命名冲突。比如,OpenStack在命名HTTP header时,以 OpenStack 开头:

  1. OpenStack-Identity-Account-ID
  2. OpenStack-Networking-Host-Name
  3. OpenStack-Object-Storage-Policy
复制代码

however, Node.js (as of writing this article) imposes an 80KB size limit on the headers object for practical reasons.

注意,HTTP规范并没有对HTTP header大小进行限制;尽管如此,出于对应用场景的考虑,作者希望对HTTP header进行80KB的大小限制。

" 不要允许设置任意大小的 HTTP header (包括状态行) ,不要超过 HTTP_MAX_HEADER_SIZE 的限制。这种检验师为了保护程序,防止‘阻断服务攻击(denial-of-service attack)’,攻击者会填入一个无法加载完的请求头,让程序进入一个永远在加载中的状态。"

4 选择一个合适的框架实现 Node.js REST API

选择最适合项目应用场景的,才是最重要的。

Express, Koa 还是 Hapi

Express , Koa 和 Hapi 都可以支持浏览器调用,并且他们都支持模板和后端渲染,这里我们之罗列一小部分他们的特性。如果你的应用需要面向用户(界面),这些特性对他们是有意义的。

Restify

另一面, Restify 是一个专注于构建REST服务的库。它强制你使用严格模式构建你的API服务,以此保证整个系统的可维护性和可监控性。 Restify也带有自动化工具 DTrace 支持,动态监测你的程序。

Restify也被大量产品用在主程序里,比如 npm 和 Netflix 。

5 对 Node.js REST API 进行黑盒测试

检验你的REST API的最佳方式是进行黑盒测试。

黑盒测试是一种测试方法,就是排除任何主观因素和已知条件,检测每个功能是否都能正常使用。所以没有任何外部依赖是假数据或者桩代码(stub),整个程序是看一个整体。

一个可以帮助你进行Node.js REST API黑盒测试的工具 supertest 。

一个简单的用来检验用户返回数据的用例,如果使用 mocha 完成的话,如下:

  1. const request = require('supertest')
  2. describe('GET /user/:id', function() {
  3. it('returns a user', function() {
  4. // newer mocha versions accepts promises as well
  5. return request(app)
  6. .get('/user')
  7. .set('Accept', 'application/json')
  8. .expect(200, {
  9. id: '1',
  10. name: 'John Math'
  11. }, done)
  12. })
  13. })
复制代码

也许你会问: 数据是怎样通过REST API插入进数据库的?

通常,一个好的写测试用例的方法,就是尽可能减少假设的状态。并且,在某些场景中,当你需要知道系统状态的时候,黑盒测试可以发现系统设计的盲点,所以你可以通过断言,实现更高的测试覆盖率。

所以,根据你的需要,可以用下列方式之一,用测试数据填充数据库:

在一个已知的数据库的子集,运行黑盒测试脚本

新建一个填充了数据的数据库,运行黑盒测试脚本

当然,黑盒测试不意味这你不需要单元测试,你也需要给你的API写单元测试脚本 unit tests 。

使用RisingStack的程序监听和Debug专家

通过Trace提高REST API质量

6 使用JWT-Based,无状态身份认证

你的REST API必须是无状态的,身份认证也一样。JWT (JSON Web Token) 是个经典的解决方案。

JWT 由三部分构成:

Header,包含token的类型和哈希算法

Payload,包括断言

Signature(JWT 不加密 payload,只签名)

在你的程序中加入 JWT-based 非常简单:

  1. const koa = require('koa')
  2. const jwt = require('koa-jwt')
  3. const app = koa()
  4. app.use(jwt({
  5. secret: 'very-secret'
  6. }))
  7. // Protected middleware
  8. app.use(function *(){
  9. // content of the token will be available on this.state.user
  10. this.body = {
  11. secret: '42'
  12. }
  13. })
复制代码

之后,在客户端调的API会受到JWT保护。访问受保护的端,你必须在请求头 Authorization 字段提供token。

  1. curl --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" my-website.com`
复制代码

你可能注意到了,JWT不依赖任何数据层。因为JWT可以通过tokens自我验证,请求也可以包含请求有效时间。

当然,你也可以通过HTTPS来保护你的API安全。

7 使用条件请求

You can think of these headers as preconditions: if they are met, the requests will be executed in a different way.

条件请求根据HTTP header不同,返回不同。你可以把这些HTTP header作为先决条件:当条件符合,就会获得相应的返回。

These headers try to check whether a version of a resource stored on the server matches a given version of the same resource. Because of this reason, these headers can be:

这些header希望检测,这一版本的资源是否存在服务端,并返回对应版本的资源。所以,header可能包括如下信息:

最后一次修改的时间戳

一个标识不同版本entity tag

对应API:

Last-Modified (标识当前资源最后修改时间)

Etag (实体标签,标识版本)

If-Modified-Since (和 Last-Modified 一起使用)

If-None-Match (和 Etag 一起使用)

一个例子

The client below did not have any previous versions of the doc resource, so neither the If-Modified-Since , nor the If-None-Match header was applied when the resource was sent. Then, the server responds with the Etag and Last-Modified headers properly set.

下面,客户端先前没有任何关于 doc 资源的版本,所以在发送请求时没有 If-Modified-Since , 和 If-None-Match 信息。之后服务端返回资源,并在响应头里写 Etag 和 Last-Modified 。

如果在请求的时候,客户端设置了 If-Modified-Since 和 If-None-Match ,一旦请求这个资源, 这个验证这个资源的版本。如果版本相同,服务器只是回应 304 - Not Modified 状态,不返回其他信息。

8 请求频率限制

请求频率限制用于控制API可以被消费多少次。

可以通过设置HTTP header告诉客户端还有多少请求可以被消费:

X-Rate-Limit-Limit 同一个时间段所允许的请求的最大数目

X-Rate-Limit-Remaining 在当前时间段内剩余的请求的数量

X-Rate-Limit-Reset 为了得到最大请求数所等待的秒数

大部分HTTP框架支持这种写法(或通过插件支持)。举个例子,如果你用Koa,可以使用 koa-ratelimit 这个包。

注意,请求时间间隔可以根据API不同,设置不同。比如 GitHub时间间隔是1小时,Twitter是15分钟。

9 创建一个合适的API文档

你写的API是给别人用的,要对别人有价值。提供一个API文档是十分重要的。

以下开源项目可以帮助你创建API文档:

API Blueprint

Swagger

10 不要错过未来的API

在过去的几年中,出现了两个API查询语言,Facebook的GraphQL和Netflix的Falcor。但我们为什么需要它们?

想象以下RESTful资源请求:

  1. /org/1/space/2/docs/1/collaborators?include=email&page=1&limit=10`
复制代码

这很容易失控——如果你希望按照相同的数据结构返回数据。这是GraphQL和Falcor解决的问题。

关于GraphQL

GraphQL是一门API查询语言,运行时为完成这些查询现有数据。GraphQL提供了一套完整的,易于理解的,可以描述数据的API。让客户端有了完整描述自己需要的数据的能力,随着时间的推移,这种方式可能演变成API,成为更强大的开发工具。

关于Falcor

Falcor一个创新的数据抓取哭,支撑着Netflix UI。Falcor允许你通过一个Node服务上的虚拟JSON操作任何后台数据。在客户端,使用远程JSON对象,通过get,set, call查找数据,数据就是API 。

令人惊讶的REST API灵感

如果你正想要开发Node.js REST API或者更新一个版本,我们收集了4个有价值的,现实中的例子:

GitHub API

Twilio API

Stripe API

DigitalOcean API

来自:http://www.w3ctech.com/topic/1968



回复

使用道具 举报