> For the complete documentation index, see [llms.txt](https://microservice-nodejs.shujuwajue.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://microservice-nodejs.shujuwajue.com/jia-gou-mo-kuai/restify/kuai-su-ru-men.md).

# 快速入门

> 官方原文参考：[Quick Start](http://restify.com/docs/home/)

## 快速入门

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

```javascript
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建立的服务返回什么。

```javascript
$ 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。

```javascript
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 框架解读和构建接口](https://www.jianshu.com/p/0cd228984a3b)

```javascript
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 服务器的访问日志功能，以及添加计数器来记录访问相关的性能指标。

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

### 通用的操作链：`server.use()`

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

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

## 使用 next() <a href="#using-next" id="using-next"></a>

```javascript
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 抛出异常并终止进程，可以用来在出现无法恢复的错误时终止程序。

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

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

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

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

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

两者之间的区别在于，用一个错误对象调用next()可以让你利用服务器的事件发射器([event emitter](http://restify.com/components/server.md#errors))。这使您能够使用一个共同的处理程序处理出现的所有错误类型。有关详细信息，请参阅[错误处理部分](http://restify.com/docs/home/#error%20handling)。

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

```javascript
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解码。

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

你也可以传入一个[正则表达式](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/RegExp)对象,并且通过`req.params`数组访问（这种方式不好理解 ）：

```javascript
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]`得到`foo`和`req.params[1]`得到`my/cats/name/is/gandalf`。

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

```javascript
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编码。

```javascript
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})
});
```

返回：

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

### 多版本路由

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

```javascript
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);
```

尝试请求：

```javascript
$ 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：

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

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

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

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

```javascript
$ 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 匹配的版本的路由。
>
> ```javascript
> 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 服务器时，添加额外的响应内容类型的支持。

```javascript
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。

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

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

```javascript
$ 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，确保按您希望的方式进行排序：

```javascript
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 响应对象保留了一个服务相应节点的所有“原始”方法。

```javascript
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的示例：

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

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

```javascript
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`的例子，但这一次是用的一个自定义格式程序:

```javascript
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()方法来发送的。
>
> ```javascript
> 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));
> ```
