# 快速入门

> 官方原文参考：[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));
> ```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://microservice-nodejs.shujuwajue.com/jia-gou-mo-kuai/restify/kuai-su-ru-men.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
