restify 中文手册

restify 是一款相对于express更专注于RESTful API 的nodejs框架

安装

1
npm install restify

服务端 API

最简单的一个服务端脚步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var restify = require('restify');
function respond(req, res, next) {
res.send('hello ' + req.params.name);
next();
}
var server = restify.createServer();
server.get('/hello/:name', respond);
server.head('/hello/:name', respond);
server.listen(8080, function() {
console.log('%s listening at %s', server.name, server.url);
});

一睹为快, 使用curl命令在shell中访问restify服务

1
2
3
4
5
6
7
8
9
curl -is http://localhost:3000/hello/mark -H 'accept: text/plain'
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 10
Date: Wed, 26 Oct 2016 13:51:08 GMT
Connection: keep-alive
hello mark

1
2
3
4
5
6
7
8
9
curl -is http://localhost:3000/hello/mark
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 12
Date: Wed, 26 Oct 2016 13:54:03 GMT
Connection: keep-alive
"hello mark"
1
2
3
4
5
6
7
curl -is http://localhost:3000/hello/mark -X HEAD -H 'connection: close'
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 12
Date: Wed, 26 Oct 2016 13:56:26 GMT
Connection: close

restify 能根据客户端请求返回对应格式信息, 默认返回json. 还能自由实现Connection类型.
因为REST APIs中经常使用curl, 所以restify中自带了一个插件可以检测客户端是否是curl

1
server.pre(restify.pre.userAgentConnection());

创建一个服务器

直接调用createServer方法就可以创建一个服务器; 可选的参数配置与node原生的http.Server.listen一致

1
2
3
4
5
6
7
8
9
10
var restify = require('restify'),
fs = require('fs');
var server = restify.createServer({
certificate: fs.readFileSync('path/to/server/certificate'),
key: fs.readFileSync('path/to/server/key'),
name: 'MyApp',
});
server.listen(8080);

参数说明

  • certificate: string, HTTPS服务器的证书
  • key: string, HTTPS服务器的证书key
  • formatters: object, 自定义的response的content-type
  • log: object, 服务器日志,可以配合bunyan一起使用
  • name: string, 服务器的response header
  • spdy: Object, 允许集成node-spdy服务器
  • version: string, 路由版本
  • responseTimeHeader: string, X-Response-Time
  • responseTimeFormatter: function, 格式化header的值

注册handler : server.use()

注册服务器控制组件,按照代码顺序执行,需要放在路由代码之前。

1
2
3
4
5
6
7
var count = 0;
server.use(function foo(req, res, next) {
count++;
console.log(count);
next();// 进入下一个组件 或 下一个相同路由方法
});

路由

restify的路由基本与express/sinatra相同.
经过URL-decoded解码的数据会存放在req.params中以供使用.
路由支持正则匹配.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function send(req, res, next) {
res.send('hello ' + req.params.name);
return next();
}
server.post('/hello', function create(req, res, next) {
res.send(201, Math.random().toString(36).substr(3, 8));
return next();
});
server.put('/hello', send);
server.get('/hello/:name', send);
server.head('/hello/:name', send);
server.del('hello/:name', function rm(req, res, next) {
res.send(204);
return next();
});
server.get(/^\/([a-zA-Z0-9_\.~-]+)\/(.*)/, function(req, res, next) {
console.log(req.params[0]);
console.log(req.params[1]);
res.send(200);
return next();
});

你可以在next()中传递一个字符串, 去匹配执行链中的下一个路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var restify = require('restify');
var server = restify.createServer();
const assert = require('assert');
var count = 0;
function send(req, res, next) {
res.send('hello ' + req.params.name);
}
server.use(function foo(req, res, next) {
count++;
next();
});
server.get('/foo/:id', function (req, res, next) {
next('foo2');
});
server.get({
name: 'foo2',
path: '/hello/:name'
}, function (req, res, next) {
assert.equal(count, 1);
res.send(200);
next();
});
server.get('/hello/:name',send);
server.listen(3000, function() {
console.log('%s listening at %s', server.name, server.url);
});

上例中的foo2只会被执行一次, 这里需要注意的是:

  • 如果next()中传递的name在下游找不到, restify将会返回一个500
  • 不能循环使用这种传递
  • 最后, 使用next(‘foo2’)唤起的路由不能重复注册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server.get('/foo/:id', function (req, res, next) {
next('foo2');
});
server.get({
name: 'foo2',
path: '/hello/:name'
}, function (req, res, next) {
assert.equal(count, 1);
res.send(200);
next();
});
server.get('/hello/:name',send);//这个路由将无法匹配上

链式组件: restify的路由机制可以接受多个组件调用

1
2
3
4
5
6
7
8
9
10
11
server.get(
'/foo/:id',
function(req, res, next) {
console.log('Authenticate');
return next();
},
function(req, res, next) {
res.send(200);
return next();
}
);

如果用字符串定义了一个参数化的路由,你可以在程序的其他地方将其解析出来,这将非常有助于定位程序内的路由资源;而不需要你手动拼接准确的地址,还需要去进行适当的URL编码.

1
2
3
4
5
6
7
8
9
10
server.get({name: 'city', path: '/cities/:slug'}, function(){});
// in another route
server.get('/test',function(req, res, next){
res.send({
country: 'Australia',
// render a URL by specifying the route name and parameters
capital: server.router.render('city', {slug: 'canberra'}, {details: true})
});
});

得到的结果如下

1
2
3
4
{
country: "Australia",
capital: "/cities/canberra?details=true"
}

带有版本控制的路由: 大多数的REST APIs都应带有版本控制,restify返回的header Accept-Version中就恰好附带着类似semver的版本信息, 使用了与NPM一样的方式实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var restify = require('restify');
var server = restify.createServer();
function sendV1(req, res, next) {
res.send('hello: ' + req.params.name);
return next();
}
function sendV2(req, res, next) {
res.send({hello: req.params.name});
return next();
}
var PATH = '/hello/:name';
server.get({path: PATH, version: '1.1.3'}, sendV1);
server.get({path: PATH, version: '2.0.0'}, sendV2);
server.listen(8080);

使用curl访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ curl -s localhost:8080/hello/mark
"hello: mark"
$ curl -s -H 'accept-version: ~1' localhost:8080/hello/mark
"hello: mark"
$ curl -s -H 'accept-version: ~2' localhost:8080/hello/mark
{"hello":"mark"}
$ curl -s -H 'accept-version: ~3' localhost:8080/hello/mark | json
{
"code": "InvalidVersion",
"message": "GET /hello/mark supports versions: 1.1.3, 2.0.0"
}

当然你也可以同一个接口指定多个版本信息

1
server.get({path: PATH, version: ['2.0.0', '2.1.0', '2.2.0']}, sendV2);

错误处理

restify中有一些处理错误的方法. 首先,你可以仅仅简单调用res.send(err). 当然你也可以像这样将错误信息暂存进路由中

1
2
3
4
5
6
7
8
server.get('/hello/:name', function(req, res, next) {
return database.get(req.params.name, function(err, user) {
if (err) return next(err);
res.send(user);
return next();
});
});

如果你调用res.send()时遇到了错误,一般会带有一个statusCode信息,再要么直接报500(除非此时你显式的使用 res.send(4xx, new Error(‘blah’)); ).

另外的,restify2.1提供了next.ifError API

1
2
3
4
5
6
7
8
server.get('/hello/:name', function(req, res, next) {
return database.get(req.params.name, function(err, user) {
next.ifError(err);
res.send(user);
next();
});
});

有时,你想使用一种通用的错误处理机制对付所有的请求异常;就可以像这样

1
2
3
4
5
6
7
8
9
10
server.get('/hello/:name', function(req, res, next) {
// some internal unrecoverable error
var err = new restify.errors.InternalServerError('oh noes!');
return next(err);
});
server.on('InternalServer', function (req, res, err, cb) {
err.body = 'something is wrong!';
return cb();
});

自定义的错误处理

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();
var util = require('util');
function MyError(message) {
restify.RestError.call(this, {
restCode: 'MyError',
statusCode: 418,
message: message,
constructorOpt: MyError
});
this.name = 'MyError';
};
util.inherits(MyError, restify.RestError);
server.get('/hello/:name', function(req, res, next) {
return next(new MyError('here occur an error'));
});
server.listen(3000, function() {
console.log('%s listening at %s', server.name, server.url);
});

node 服务端 API

事件:restify服务器除了会发送所有的node原生http.Server中事件,还会发送一些你可能需要用到的其他事件。

res() : 允许你在路由前添加组件;如果你愿意的话,可以像只钩子(hook)一样改变请求头信息。

1
2
3
4
server.pre(function(req, res, next) {
req.headers.accept = 'application/json'; // screw you client!
return next();
});

run() : 无论路由如何,都可以添加组件。

已经绑定的插件

  • 解析Accept头部: 注册restify服务时传递参数进行配置即可
  • 解析授权Authorization头部: 目前只支持基础的http授权和认证
  • 跨域处理
  • 解析日期: 可以调整时钟偏移
  • 解析请求字符串
  • 支持JSONP
  • 解析body: JSON/URL-encoded/multipart form
  • 请求日志: 使用了轻量的bunyan日志框架(出于性能考虑,默认只记录所有请求id信息)
  • Gzip响应: 只有客户端请求accept-encoding: gzip时才会奏效
  • 静态资源: 支持http缓存机制
  • 节流
  • Request Expiry
  • 审计日志(详细的记录输入和输出): 这个蛮特殊的,不是使用use()调用,而是在after事件中触发.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
var server = restify.createServer();
server.use(restify.acceptParser(server.acceptable));
server.use(restify.authorizationParser());
server.use(restify.CORS({
origins: ['https://foo.com', 'http://bar.com', 'http://baz.com:8081'], // defaults to ['*']
credentials: true, // defaults to false
headers: ['x-foo'] // sets expose-headers
}));
server.use(restify.dateParser(60));
server.use(restify.queryParser());
server.use(restify.jsonp());
server.use(restify.gzipResponse());
server.use(restify.bodyParser({
maxBodySize: 0,
mapParams: true,
mapFiles: false,
overrideParams: false,
multipartHandler: function(part) {
part.on('data', function(data) {
/* do something with the multipart data */
});
},
multipartFileHandler: function(part) {
part.on('data', function(data) {
/* do something with the multipart file data */
});
},
keepExtensions: false,
uploadDir: os.tmpdir(),
multiples: true
hash: 'sha1'
}));
server.use(restify.requestLogger({
properties: {
foo: 'bar'
},
serializers: {...}
}));
server.use(restify.requestExpiry({
header: 'x-request-expiry-time'
});
server.get(/\/docs\/current\/?.*/, restify.serveStatic({
directory: './documentation/v1',
default: 'index.html'
}));
server.use(restify.throttle({
burst: 100,
rate: 50,
ip: true,
overrides: {
'192.168.1.1': {
rate: 0,// unlimited
burst: 0
}
}
}));
server.use(function setETag(req, res, next) {
res.header('ETag', 'myETag');
res.header('Last-Modified', new Date());
});
server.use(restify.conditionalRequest());
server.get('/hello/:name', function(req, res, next) {
res.send('hello ' + req.params.name);
});
server.on('after', restify.auditLogger({
log: bunyan.createLogger({
name: 'audit',
stream: process.stdout
})
}));

请求 API

打包了所有node http.IncomingMessage APIs,事件和属性;还包括下面的

  • header(key, [defaultValue])
  • accepts(type)
  • is(type)
  • isSecure()
  • isChunked()
  • isKeepAlive()
  • log
  • getQuery()
  • time()
  • startHandlerTimer(handlerName)
  • endHandlerTimer(handlerName)

响应 API

打包了所有node ServerResponse APIs,事件和属性;还包括下面的

- header(key, value)
- charSet(type)
- cache([type], [options])
- status(code)
- send([status], body)
- redirect(status, url, next)
- redirect([url | options], next)

  • json([status], body)

DTrace

restify 最酷的一个特性是:当你添加一个新的路由或组件时,将自动为其创建一个 DTrace探针。

客户端 API

  • JsonClient: sends and expects application/json
  • StringClient: sends url-encoded request and expects text/plain
  • HttpClient: thin wrapper over node’s http/https libraries

参考链接
restify 官方文档