净化

10个关于Node.js REST API 的最佳实践

净化 · 2017-03-14翻译 · 1261阅读 原文链接

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

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

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

1 使用HTTP方法和API路由

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

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

  • POST /userPUT /user:/id 创建新用户

  • GET /user 查找一些用户

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

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

  • DELETE /user/:id 删除用户

API路由必须是一个名词作为资源标识符

点击跳到Tweet

2 正确使用HTTP状态码

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

  • 2xx, 一切正常

  • 3xx, 资源被移除

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

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

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

如果想查看完整版 ,点击HTTP状态码清单

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

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

  • 分页

  • 限制访问频率

  • 身份验证

一系列的标准化HTTP header可参照此链接

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

OpenStack-Identity-Account-ID  
OpenStack-Networking-Host-Name  
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)’,攻击者会填入一个无法加载完的请求头,让程序进入一个永远在加载中的状态。"

选自 Node.js HTTP parser

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

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

Express, Koa 还是 Hapi

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

Restify

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

Restify也被大量产品用在主程序里,比如npmNetflix

"Restify 让你使用严格模式提供 API 服务,保持程序的可维护性和可监控性

点击跳到Tweet

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

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

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

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

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

const request = require('supertest')

describe('GET /user/:id', function() {
  it('returns a user', function() {
    // newer mocha versions accepts promises as well
    return request(app)
      .get('/user')
      .set('Accept', 'application/json')
      .expect(200, {
        id: '1',
        name: 'John Math'
      }, done)
  })
})

也许你会问: 数据是怎样通过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 非常简单:

const koa = require('koa')
const jwt = require('koa-jwt')

const app = koa()

app.use(jwt({
  secret: 'very-secret'
}))

// Protected middleware
app.use(function *(){
  // content of the token will be available on this.state.user
  this.body = {
    secret: '42'
  }
})

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

curl --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" my-website.com

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

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

推荐一篇有价值的文章,详解web安全认证方法

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作为先决条件:当条件符合,就会获得相应的返回。

对于条件请求,返回什么结果取决于HTTP header

点击跳到Tweet

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信息。之后服务端返回资源,并在响应头里写EtagLast-Modified

Node.js RESTful API with conditional request, without previous versions

引自:MDN条件请求文档

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

Node.js RESTfu API with conditional request, with previous versions

引自 MDN条件请求文档

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自动生成工具,推荐 Apiary

10 不要错过未来的API

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

想象以下RESTful资源请求:

/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个有价值的,现实中的例子:

我希望此刻你知道应该怎么构建Node.js API,如果我漏写了什么,请在评论区告知。

相关文章