Web

Restify

Posted by Kerwen Blog on August 19, 2024

REST

REST是REpresentational State Transfer(表述性状态转移)的缩写。

Restify

restify 是一个 NodeJS 模块,可以让你创建正确的 REST Web Services。它借鉴了很多 express 的设计,restify比起express更专注于REST服务,去掉了express中的template, render等功能,同时强化了REST协议使用,并且提供了版本化支持,HTTP的异常处理等。此外 restify 还提供了 DTrace 功能,为程序调式带来新的便利!

安装 restify

安装restify,先创建目录,然后使用npm安装即可:

1
2
3
4
mkdir restify-restful
cd restify-restful
npm init -y 
npm install restify

Hello World

下面的代码就是restify的入门程序:

1
2
3
4
5
6
7
8
9
const restify = require('restify');
const server = restify.createServer()
server.get('/', (req, res, next)=>{
    res.send("hello world");
    return next();
})
server.listen(3001, '127.0.0.1', function () {
    console.log('%s listening at %s', server.name, server.url)
});

响应处理链

对于每个 HTTP 请求,restify 通过一个响应处理链来对请求进行处理。restify 中有三种不同的处理链。

  • pre:在确定路由之前执行的处理器链。
  • use:在确定路由之后执行的处理器链。
  • {httpVerb}:一个路由独有的处理器链。

img

通过 restify 服务器的 pre()方法可以注册处理器到 pre 处理器链中。那么对所有接收的 HTTP 请求,都会预先调用该处理器链中的处理器。处理器链的执行发生在确定路由之前,因此即便是没有路由与请求相对应,也会调用该处理器链。该处理器链适合执行日志记录、性能指标数据采集和 HTTP 请求清理等工作。

1
2
3
4
5
6
7
8
9
10
11
12
const restify = require('restify');
const server = restify.createServer()

server.pre(restify.pre.dedupeSlashes()) // 去除请求网址中的多个 /

server.get('/', (req, res, next) => {
    res.send("hello world");
    return next();
})
server.listen(3001, '127.0.0.1', function () {
    console.log('%s listening at %s', server.name, server.url)
});

通过 restify 服务器的 use()方法可以注册处理器到 use 处理器链中。该处理器链在选中了一个路由来处理请求之后被调用,但是发生在实际的路由处理逻辑之前。也就是说如果你没有定义的路由但是却被请求者请求了,那么pre会处理该请求,但是use处理链不会处理该请求。
该处理器链适合执行用户认证、应用相关的请求清理和响应格式化等工作。典型的应用场景包括检查用户是否完成认证,对请求和响应的 HTTP 头进行修改等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const restify = require('restify');
const server = restify.createServer()

server.use(function (req, res, next) {
    console.log('use', req.url);
return next()
})

server.get('/', (req, res, next) => {
    res.send("hello world");
return next();
})
server.listen(3001, '127.0.0.1', function () {
    console.log('%s listening at %s', server.name, server.url)
});

在每个处理器的实现中,应该在合适的时机调用 next()方法来把处理流程转交给处理器链中的下一个处理器。具体的时机由每个处理器实现根据需要来决定。这给处理 HTTP 响应带来了极大的灵活性,也使得处理器可以被有效复用。

路由

restify 的路由表示的是对 HTTP 请求的处理逻辑。一个路由有 3 个部分:分别是 HTTP 动词、匹配条件和处理方法。restify 服务器对象可以使用方法 get、put、post、del、head、patch 和 opts,分别与名称相同的 HTTP 请求动词相对应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const restify = require('restify');    
const server = restify.createServer();

const logHandler = (req, res, next) => {
  console.log('req: %s, params: %s', req.href(), JSON.stringify(req.params));
  res.send(req.params);
  return next();
};

server.get('/route/simple', logHandler);
server.get('/route/user/:id', logHandler);
server.get(/^\/route\/order\/(\d+)/, logHandler);
server.get({
  path: '/route/versioned',
  version: '1.0.0'
}, logHandler);

server.listen(8000, () => console.log('%s listening at %s', server.name, server.url));

多版本路由

REST API 通常有同时运行多个版本的要求,以支持 API 的演化。restify 内置提供了基于语义化版本号(semver)规范的多版本支持。在 HTTP 请求中可以使用 HTTP 请求头 Accept-Version 来指定版本号。每个路由可以按照下面的代码中的方式,在属性 version 中指定该路由的一个或多个版本号。如果请求中不包含 HTTP 头 Accept-Version,那么会匹配同一路由中版本最高的那一个。否则,就按照由 Accept-Version 指定的版本号来进行匹配,并调用匹配版本的路由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var restify = require('restify');
var server = restify.createServer();

function sendV1(req, res, next) {
    console.log('sendV1', req.params.name);
    res.send({sendV1: req.params.name});
    return next();
}

function sendV2(req, res, next) {
    console.log('sendV2', req.params.name);
    res.send({sendV2: req.params.name});
    return next();
}


server.get('/hello/:name', restify.plugins.conditionalHandler([
    {version: '1.1.3', handler: sendV1},
    {version: ['2.0.0', '2.1.0'], handler: sendV2}, // 默认返还最高版本
]));

server.listen(3001, '127.0.0.1', () => console.log('%s listening at %s', server.name, server.url));

错误处理

由于 HTTP 的状态码是标准的,restify 提供了一个专门的模块 restify-errors 来创建对应不同状态码的 Error 对象的处理,可以直接在 send() 方法中使用。restify 中产生的错误会作为事件来发送,可以使用 Node.js 标准的事件处理机制来进行处理。需要注意的是,只有使用 next()方法发送的错误会被作为事件来发送,使用响应对象的 send()方法发送的则不会。

插件

在 REST API 的开发中,某些任务是比较常见的。restify 提供了一系列插件来满足这些通用的需求。这些插件可以通过 restify.plugins 来访问,并使用 use()方法来注册。例如在下面的代码中,我们使用了 restify 中的若干个常用插件:

  • acceptParser 用来解析请求的 Accept 头,以确保是服务器端可以处理的类型。如果是服务器端不支持的类型,该插件会返回 406 错误。
  • authorizationParser 用来解析请求中的 Authorization 头,并把解析的结果保存在请求对象的属性 authorization 中。
  • queryParser 之前已经介绍过,用来解析请求中的查询字符串。
  • gzipResponse 用来发送 GZIP 压缩之后的响应。
  • bodyParser 用来解析请求的内容,并把结果保存在请求对象的属性 body 中。目前支持的请求内容类型包括 application/json、application/x-www-form-urlencoded 和 multipart/form-data。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    const restify = require('restify');      
    const server = restify.createServer();
    
    // 载入插件
    server.use(restify.plugins.acceptParser(server.acceptable));
    server.use(restify.plugins.authorizationParser());
    server.use(restify.plugins.queryParser());
    server.use(restify.plugins.gzipResponse());
    server.use(restify.plugins.bodyParser());
      
    server.post('/plugins', (req, res, next) => {
      console.log(req.body);
      res.send({a: 1});
      return next();
    });
      
    server.listen(8000, () => console.log('%s listening at %s', server.name, server.url));
    

Reference

official document
使用 Restify 开发 REST API !