快速入门

官方原文参考:Quick Start

快速入门

设置一个服务器非常的快速和容易,下面是一个简单构建服务的例子

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命令来获取使用restify建立的服务返回什么。

$ curl -is http://localhost:8080/hello/mark -H 'accept: text/plain'
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 10
Date: Mon, 31 Dec 2012 01:32:44 GMT
Connection: keep-alive

hello mark


$ curl -is http://localhost:8080/hello/mark
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 12
Date: Mon, 31 Dec 2012 01:33:33 GMT
Connection: keep-alive

"hello mark"


$ curl -is http://localhost:8080/hello/mark -X HEAD -H 'connection: close'
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 12
Date: Mon, 31 Dec 2012 01:42:07 GMT
Connection: close

请注意,默认情况下,curl使用Connection: keep-alive,为了使HEAD头方法立即返回,您需要通过设置Connection: close

由于CURL经常与REST API一起使用,restify含有一个插件来处理以上这种问题。插件检查用户代理是否为curl方式。如果是,则将连接头设置为“close”,并删除“Content-Length“ header。

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

Sinatra 样式处理链

像许多其他基于Node.js的REST框架一样,restify利用Sinatra样式语法来定义路由和服务这些路由的函数处理程序:

备注:Sinatra是一款非常轻量的Web框架,基于Ruby语言开发,旨在以最小的精力为代价快速创建Web应用为目的的DSL(领域专属语言)。

Sinatra是Blake Mizerany在2007年9月开发的Ruby语言的Web框架,并在2009年1月18日发布0.9.0版本。

Sinatra最大的特点就是非常轻量、快速,整个源码也只有1000多行。

参考:项目主页: http://www.sinatrarb.com

资料:JWT 在 Ruby 中的使用总结和 Sinatra 框架解读和构建接口

server.get('/', function(req, res, next) {
  res.send('home')
  return next();
});

server.post('/foo',
  function(req, res, next) {
    req.someData = 'foo';
    return next();
  },
  function(req, res, next) {
    res.send(req.someData);
    return next();
  }
);

在restify服务器,有三个不同的处理程序链:

  • pre- 在确定路由之前执行的处理器链。

  • use- 在确定路由之后执行的处理器链。

  • {httpVerb}- 一个路由独有的处理器链。

所有三个处理程序链都接受单个函数、多个函数或函数数组。

通用的pre(前置)操作链:server.pre()

通过 restify 服务器的 pre()方法可以注册处理器到 pre 处理器链中。对所有接收的 HTTP 请求,都会预先调用该处理器链中的处理器。该处理器链的执行发生在确定路由之前,因此即便是没有路由与请求相对应,也会调用该处理器链。该处理器链适合执行日志记录、性能指标数据采集和 HTTP 请求清理等工作。典型的应用场景包括记录所有请求的信息,类似于 Apache httpd 服务器的访问日志功能,以及添加计数器来记录访问相关的性能指标。

// dedupe slashes in URL before routing
server.pre(restify.plugins.dedupeSlashes());

通用的操作链:server.use()

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

server.use(function(req, res, next) {
    console.warn('run for all routes!');
    return next();
});

使用 next()

server.use([
  function(req, res, next) {
    if (someCondition) {
      res.send('done!');
      return next(false);
    }
    return next();
  },
  function(req, res, next) {
    // if someCondition is true, this handler is never executed
  }
]);

在每个处理器的实现中,应该在合适的时机调用 next()方法来把处理流程转交给处理器链中的下一个处理器。具体的时机由每个处理器实现根据需要来决定。这给处理 HTTP 响应带来了极大的灵活性,也使得处理器可以被有效复用。每个处理器的实现逻辑也变得更加简单,只需要专注于完成所设计应有的功能就可以了。在处理完成之后,调用 next()方法即可。在某些情况下,可能不需要由处理器链中的后续处理器来继续进行处理,比如 HTTP 请求的格式是非法的。这个时候可以通过 next(false)来直接终止整个处理器链。在调用 next()方法的时候,也可以传入一个 Error 对象,使得 restify 直接把错误信息发送给客户端并终止处理链。在这种情况下,HTTP 响应的状态码由 Error 对象的属性 statusCode 来确定,默认使用 500。调用 next.ifError(err)并传入一个 Error 对象可以使得 restify 抛出异常并终止进程,可以用来在出现无法恢复的错误时终止程序。

server.use(function(req, res, next) {
  return next(new Error('boom!'));
});

如果要发送一个404的响应,使用NotFoundError 并传入构造函数值 ,返回一个statusCode为404.

server.use(function(req, res, next) {
  return next(new NotFoundError('not here!'));
});

使用Error对象调用res.send()会产生类似的结果,这个片段发送带有序列化错误的http 500 :

server.use(function(req, res, next) {
  res.send(new Error('boom!'));
  return next();
});

两者之间的区别在于,用一个错误对象调用next()可以让你利用服务器的事件发射器(event emitter)。这使您能够使用一个共同的处理程序处理出现的所有错误类型。有关详细信息,请参阅错误处理部分

在以下示例 中,通过 pre()方法注册的处理器会记录请求的完整路径。第一个 use()方法注册了 restify 的插件 queryParser,其作用是把请求的查询字符串解析成 JavaScript 对象。第二个 use()方法注册的处理器把请求的 HTTP 头 Accept 设置为 application/json,也就是 API 只接受 JSON 格式的请求。最后通过 get()方法注册了两个对于 GET 请求的处理器,第一个设置了响应的额外 HTTP 头 X-Test,第二个设置响应的内容。当请求包含了查询参数 boom 时,服务器会直接返回 500 错误。

const restify = require('restify');

const server = restify.createServer();

server.pre((req, res, next) => {
 console.log('req: %s', req.href());
 return next();
});

server.use(restify.plugins.queryParser());

server.use((req, res, next) => {
 req.headers.accept = 'application/json';
 return next();
});

server.get('/handler_chain', [(req, res, next) => {
 res.header('X-Test', 'test');
 return next();
}, (req, res, next) => {
 if (req.query.boom) {
   return next(new Error('boom!'));
 }
 res.send({
   msg: 'handled!'
 });
 return next();
}]);

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

注解:优先级 pre > use > get

路由

在“basic”模式下,restify路由与express/sinatra几乎相同,因为HTTP谓词与参数化资源一起使用,以确定要运行哪些处理程序链。与命名占位符相关的值在req.params中可用。在传递给您之前,这些值将被URL解码。

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();
});

你也可以传入一个正则表达式对象,并且通过req.params数组访问(这种方式不好理解 ):

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();
});

请求类似于:

$ curl localhost:8080/foo/my/cats/name/is/gandalf

将得到的结果:req.params[0]得到fooreq.params[1]得到my/cats/name/is/gandalf

路由可以由以下任何HTTP动词指定:del,get,head,opts,post,put, 和patch。

server.get(
    '/foo/:id',
    function(req, res, next) {
        console.log('Authenticate');
        return next();
    },
    function(req, res, next) {
        res.send(200);
        return next();
    }
);

多媒体

如果用字符串(不是正则表达式)定义参数化路由,则可以从服务器中的其他地方呈现它。这有助于将HTTP响应链接到其他资源,而不必在整个代码库中硬编码URL。路径和查询字符串参数都可以适当地进行URL编码。

server.get({name: 'city', path: '/cities/:slug'}, /* ... */);

// in another route
res.send({
  country: 'Australia',
  // render a URL by specifying the route name and parameters
  capital: server.router.render('city', {slug: 'canberra'}, {details: true})
});

返回:

{
  "country": "Australia",
  "capital": "/cities/canberra?details=true"
}

多版本路由

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

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();
}

server.get('/hello/:name', restify.plugins.conditionalHandler([
  { version: '1.1.3', handler: sendV1 },
  { version: '2.0.0', handler: sendV2 }
]));

server.listen(8080);

尝试请求:

$ 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": "~3 is not supported by GET /hello/mark"
}

在第一种情况下,我们没有指定一个Accept-Version的头,所以相当于发送一个*。REST将选择匹配最高版本的路由。在第二种情况下,我们显式地请求V1,它让我们响应来自版本1处理程序函数的响应,随后我们请求V2版本并返回JSON。最后,我们要求一个不存在的版本并返回了一个错误。

你可以在创建服务器时通过一个version字段定义路由上的多版本。最后,您可以通过使用数组来支持多个版本的API:

server.get('/hello/:name' restify.plugins.conditionalHandler([
  { version: ['2.0.0', '2.1.0', '2.2.0'], handler: sendV2 }
]));

在这种情况下,您可能需要知道更多信息,例如原始请求的版本字符串是什么,以及路由支持的版本数组中的匹配版本是什么。这些信可以使用以下两种方法获取:

server.get('/version/test', restify.plugins.conditionalHandler([
  {
    version: ['2.0.0', '2.1.0', '2.2.0'],
    handler: function (req, res, next) {
      res.send(200, {
        requestedVersion: req.version(),
        matchedVersion: req.matchedVersion()
      });
      return next();
    }
  }
]));

以下是匹配到路由返回的结果:

$ curl -s -H 'accept-version: <2.2.0' localhost:8080/version/test | json
{
  "requestedVersion": "<2.2.0",
  "matchedVersion": "2.1.0"
}

补充示例:

在下面的示例中,同一个路由/versioned 有多个版本。版本 1.0.0 的处理方法返回字符串 V1。第二个路由同时支持 2.0.0、2.1.0 和 2.2.0 等 3 个版本,返回的是请求的版本和实际匹配的版本。如果直接访问/versioned,返回的结果是{"requestedVersion":"*","matchedVersion":"2.2.0"},因为默认匹配最高版本。如果运行"curl -s -H 'accept-version: ~1' [[[[http://localhost:8000/versioned",由于请求中](http://localhost:8000/versioned",由于请求中](http://localhost:8000/versioned",由于请求中](http://localhost:8000/versioned",由于请求中)](http://localhost:8000/versioned",由于请求中](http://localhost:8000/versioned",由于请求中](http://localhost:8000/versioned",由于请求中](http://localhost:8000/versioned",由于请求中))](http://localhost:8000/versioned",由于请求中](http://localhost:8000/versioned",由于请求中](http://localhost:8000/versioned",由于请求中](http://localhost:8000/versioned",由于请求中)](http://localhost:8000/versioned",由于请求中](http://localhost:8000/versioned",由于请求中](http://localhost:8000/versioned",由于请求中](http://localhost:8000/versioned",由于请求中)))\) Accept-Version 头的值为~1,会匹配到 1.0.0 版本的路由,返回结果为 V1。如果请求中 Accept-Version 头的值为~3, 则返回结果为{"code":"InvalidVersion","message":"~3 is not supported by GET /versioned"},因为并没有与~3 匹配的版本的路由。

const restify = require('restify');
 
const server = restify.createServer();
 
server.get({
 path: '/versioned',
 version: '1.0.0',
}, (req, res, next) => {
 res.send('V1');
 return next();
});
 
server.get({
 path: '/versioned',
 version: ['2.0.0', '2.1.0', '2.2.0'],
}, (req, res, next) => {
 res.send({
   requestedVersion: req.version(),
   matchedVersion: req.matchedVersion(),
 });
 return next();
});
 
server.listen(8000, () => console.log('%s listening at %s', server.name, server.url));

内容协商

在之前的示例中,我们都是使用 send()方法来直接发送响应内容。如果传入的是 JavaScript 对象,restify 会自动转换成 JSON 格式。这是由于 restify 内置提供了对于不同响应内容类型的格式化实现。内置支持的响应内容类型包括 application/json、text/plain 和 application/octet-stream。restify 会根据请求的 Accept 头来确定响应的内容类型。可以在创建 restify 服务器时,添加额外的响应内容类型的支持。

var server = restify.createServer({
  formatters: {
    'application/foo': function formatFoo(req, res, body) {
      if (body instanceof Error)
        return body.stack;

      if (Buffer.isBuffer(body))
        return body.toString('base64');

      return util.inspect(body);
    }
  }
});

如果无法确定,则默认使用 application/octet-stream。

server.get('/foo', function(req, res, next) {
  res.setHeader('content-type', 'text/css');
  res.send('hi');
  return next();
});

将要返回一个content-type的是application/octet-stream的结果:

$ curl -i localhost:3000/
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 2
Date: Thu, 02 Jun 2016 06:50:54 GMT
Connection: keep-alive

如前所述,对JSON、文本和二进制格式的内置格式化程序进行重整。当覆盖或附加新的格式化器时,“优先级”可能会改变;为了确保设置为所需的优先级,您应该在格式化器定义上设置q-value,确保按您希望的方式进行排序:

restify.createServer({
  formatters: {
    'application/foo; q=0.9': function formatFoo(req, res, body) {
      if (body instanceof Error)
        return body.stack;

      if (Buffer.isBuffer(body))
        return body.toString('base64');

      return util.inspect(body);
    }
  }
});

restify 响应对象保留了一个服务相应节点的所有“原始”方法。

var body = 'hello world';
res.writeHead(200, {
  'Content-Length': Buffer.byteLength(body),
  'Content-Type': 'text/plain'
});
res.write(body);
res.end();

错误处理

希望以相同的方式处理错误的情况是很常见的。作为一个例子,您可能希望在所有内部服务器错误上提供500页面的服务。在这种情况下,您可以为InternalServer错误事件添加侦听器,当遇到此错误时,restify将此错误事件作为next(error)语句的一部分,将始终触发该错误事件。这为您提供了一种在服务器上处理同一类错误的方法。还可以使用泛型ReStimeError事件,该事件将捕获所有类型的错误。

发送404的示例:

server.get('/hello/:foo', function(req, res, next) {
  // resource not found error
  var err = new restify.errors.NotFoundError('oh noes!');
  return next(err);
});

server.on('NotFound', function (req, res, err, cb) {
  // do not call res.send! you are now in an error context and are outside
  // of the normal next chain. you can log or do metrics here, and invoke
  // the callback when you're done. restify will automtically render the
  // NotFoundError depending on the content-type header you have set in your
  // response.
  return cb();
});

自定义错误被送回客户端:

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) {
  // by default, restify will usually render the Error object as plaintext or
  // JSON depending on content negotiation. the default text formatter and JSON
  // formatter are pretty simple, they just call toString() and toJSON() on the
  // object being passed to res.send, which in this case, is the error object.
  // so to customize what it sent back to the client when this error occurs,
  // you would implement as follows:

  // for any response that is text/plain
  err.toString = function toString() {
    return 'an internal server error occurred!';
  };
  // for any response that is application/json
  err.toJSON = function toJSON() {
    return {
      message: 'an internal server error occurred!',
      code: 'boom!'
    }
  };

  return cb();
});

server.on('restifyError', function (req, res, err, cb) {
  // this listener will fire after both events above!
  // `err` here is the same as the error that was passed to the above
  // error handlers.
  return cb();
});

下面是另一个InternalServerError的例子,但这一次是用的一个自定义格式程序:

const errs = require('restify-errors');

const server = restify.createServer({
  formatters: {
    'text/html': function(req, res, body) {
      if (body instanceof Error) {
        // body here is an instance of InternalServerError
        return '<html><body>' + body.message + '</body></html>';
      }
    }
  }
});

server.get('/', function(req, res, next) {
  res.header('content-type', 'text/html');
  return next(new errs.InternalServerError('oh noes!'));
});

补充:

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

在下面的代码中,路由/error/500 使用 send()发送了一个 InternalServerError 错误对象,而/error/400 和/error/404 使用 next()分别发送了 BadRequestError 和 NotFoundError 错误对象。可以使用 server.on()来添加对于 NotFound 错误的处理逻辑,但是对于 InternalServer 错误的处理逻辑不会被触发,因为该错误是通过 send()方法来发送的。

const restify = require('restify');
const errors = require('restify-errors');
 
const server = restify.createServer();
 
server.get('/error/500', (req, res, next) => {
 res.send(new errors.InternalServerError('boom!'));
 return next();
});
server.get('/error/400', (req, res, next) => next(new errors.BadRequestError('bad request')));
server.get('/error/404', (req, res, next) => next(new errors.NotFoundError('not found')));
 
server.on('NotFound', (req, res, err, cb) => {
 console.error('404 %s', req.href());
 return cb();
});
 
server.on('InternalServer', (req, res, err, cb) => {
 console.error('should not appear');
 return cb();
});
 
server.listen(8000, () => console.log('%s listening at %s', server.name, server.url));

Last updated

Was this helpful?