diff --git a/static/lib/tutorials/zh_CN/auth.md b/static/lib/tutorials/zh_CN/auth.md
index f082879..8054550 100644
--- a/static/lib/tutorials/zh_CN/auth.md
+++ b/static/lib/tutorials/zh_CN/auth.md
@@ -1,154 +1,95 @@
-## 身份认证
+# 鉴别(Authentication)
-_该教程适用于 hapi v17版本_
+_本教程适用于 hapi v17 及以上_
-hapi 的身份认证基于两个概念:`schemes` 和 `strategies`。
+## 总览
-可以将 scheme 视为一般类型的认证, 例如 "basic" 或 "digest"。而 strategy 是一个预先配置好并且命名好的 scheme 实例。
+现在网页应用,大多都会用到鉴别(Authentication)。hapi 的鉴别有两个概念:`计划(schemes)`、`策略(strategies)`。`计划`乃 hapi 中的一种鉴别方式。例如,`@hapi/basic`、`@hapi/cookie`。`策略`是`计划`的实例,预先配置并且命名。
-首先我们来看一下如何使用 [hapi-auth-basic](https://github.com/hapijs/hapi-auth-basic):
+## 计划(Schemes)
-```javascript
-'use strict';
+`计划` 是个方法,其签名为 `function (server, options)`。参数 `server` 服务器对象的引用,参数 `options` 是该计划的配置项。
-const Bcrypt = require('bcrypt');
-const Hapi = require('@hapi/hapi');
+此方法返回对象,必须带有属性方法 `authenticate`。剩余属性可选,如 `payload` 和 `response`。
-const users = {
- john: {
- username: 'john',
- password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm', // '密码: secret'
- name: 'John Doe',
- id: '2133d32a'
- }
-};
+`计划`可自定义,或者使用 hapi 提供的插件。如 `@hapi/basic`、`@hapi/cookie`。
-const validate = async (request, username, password) => {
+### 属性方法 `authenticate`
- const user = users[username];
- if (!user) {
- return { credentials: null, isValid: false };
- }
+方法 `authenticate` 签名为 `function (request, h)`, 必选。
- const isValid = await Bcrypt.compare(password, user.password);
- const credentials = { id: user.id, name: user.name };
+其中,参数 `request` 为服务器创建的 `request` 对象。这个对象与路由处理函数中的相同,详情请参阅 [API](/api#request-object)。
- return { isValid, credentials };
-};
+参数 `h` 是 hapi 的[响应工具包](https://hapijs.com/api#response-toolkit).
-const start = async () => {
+当鉴别成功时, 该方法必须返回 `h.authenticated({ credentials, artifacts })`。两属性皆是对象。其中,属性 `credentials` 用以存用户鉴别数据,或用户凭证(credentials)。属性 `artifacts` 用以存其他鉴别信息。
- const server = Hapi.server({ port: 4000 });
+返回的两属性 `credentials` 和 `artifacts`,存于对象 `request.auth`,可随时访问(如路由处理函数)。
- await server.register(require('hapi-auth-basic'));
+若鉴别失败, 可抛出错误或返回 `h.unauthenticated(error, [data])`。此处,参数 `error` 是鉴别错误,参数 `data` 是个可选对象,其包含 `credentials`、`artifacts`。如果自定义抛出错误且没提供 `data` 对象,这与直接调用 `return h.unauthenticated(error)` 一样。所传错误会影响后续行为。详情参见 [`server.auth.scheme(name, scheme)`](/api#server.auth.scheme())。此外,处理错误,推荐使用 [boom](/module/boom)。
- server.auth.strategy('simple', 'basic', { validate });
+### 属性方法 payload
- server.route({
- method: 'GET',
- path: '/',
- options: {
- auth: 'simple'
- },
- handler: function (request, h) {
+方法 `payload` 签名为 `function (request, h)`。
- return 'welcome';
- }
- });
+同样,方法提供 hapi 响应工具包。处理错误,再次建议使用 [boom](/module/boom)。
- await server.start();
+鉴别成功,请返回 `h.continue`。
- console.log('server running at: ' + server.info.uri);
-};
+### 属性方法 response
-start();
-```
-
-首先定义我们的 `users` 数据库, 在这个示例中只是一个简单的对象。之后我们定一个验证函数, 这个函数是 [hapi-auth-basic](https://github.com/hapijs/hapi-auth-basic) 的一个特性,可以允许我们验证用户是否提供了有效的凭证,
-之后我们注册这个插件。随后我们创建一个名为 `basic`的 scheme ,这个可以通过[server.auth.scheme()](/api#serverauthschemename-scheme)来完成。
-
-当这个插件被注册后, 我们可以使用 [server.auth.strategy()](/api#serverauthstrategyname-scheme-mode-options) 去创建一个名为 `simple` 的 strategy。 这个代表的就是我们名为 `basic` 的 scheme。 同时也传递了一个选项对象,这样我们可以配置 scheme 的行为。
-
-最后我们告诉路由使用名为 `simple` 的 strategy 来做身份验证。
-
-## Schemes
-
-`scheme` 是一个拥有签名 `function (server, options)` 的方法。 `server` 参数是需要添加这个 scheme 的服务器对象的引用,`options` 参数是使用它注册 strategy 时的配置。
-
-这个方法返回的对象 *至少* 包含了 `authenticate` 方法。而其他可用的非必须返回的方法是 `payload` 和 `response`。
-
-### `authenticate`
-
-`authenticate` 方法的签名为 `function (request, h)`, 也是 scheme 中唯一 *必须* 有的方法。
-
-在这里 `request` 为服务器创建的 `request` 对象。 这个对象与路由 handler 中的对象为同一个, 完整的文档可以参阅 [API reference](/api#request-object)。
-
-`h` 是 hapi 中的 [response toolkit](https://hapijs.com/api#response-toolkit).
+方法 `response` 签名为 `function (request, h)`,也提供 hapi 响应工具包。
-当身份认证成功时, 你必须调用并且返回 `h.authenticated({ credentials, artifacts })`。 `credentials` 是一个代表身份验证用户 (或者用户将要进行身份认证的凭证) 的对象。 另外 `artifacts` 包含了除用户凭证之外的其余验证信息。
+响应发送之前,此方法可装饰响应对象 (`request.response`),为其添加头信息。
-`credentials` 和 `artifacts` 作为 `request.auth`对象的一部分,也可以在之后的处理过程中访问 (例如:路由 handler)。
+包装毕,必须返回 `h.continue`, 以发送响应。
-如果身份验证不成功, 你可以抛出一个错误或者调用并返回 `h.unauthenticated(error, [data])`。 这里的 `error` 是一个身份验证错误,`data` 是一个可选对象,它包含了 `credentials` 和 `artifacts`。 如果你抛出的错误没有 `data` 对象,那么将和你调用 `return h.unauthenticated(error)` 没有什么区别。 这些错误传递的细节将会对行为有一定的影响,更多细节可以 API 文档中找到 [`server.auth.scheme(name, scheme)`](https://hapijs.com/api#-serverauthschemename-scheme)。我们推荐你使用 [boom](https://github.com/hapijs/boom) 来处理错误。
+处理错误,仍建议用 [boom](/module/boom)。
-### `payload`
+## 策略(Strategies)
-`payload` 方法拥有签名 `function (request, h)`。
+`计划`注册后,如何使用。这就要靠`策略(Strategies)`了。
-同样,这里也可以获得到 hapi 的响应对象。 我们同样建议你使用 [boom](https://github.com/hapijs/boom) 进行错误处理。
+如上所述,`策略(Strategies)`其实`计划(scheme)`实例的拷贝。
-要发出身份验证成功的信号请返回 `h.continue`。
+欲注册`策略(Strategies)`,需先注册`计划(scheme)`。而后,调用 `server.auth.strategy(name, scheme, [options])` 来注册`策略(Strategies)`。
-### `response`
+参数 `name` 为字符串,必填,用以标识`策略(Strategies)`。参数 `scheme` 也是字符串,必填,是`计划(scheme)`名。参数 `options` 为对象,可选,是`计划(scheme)`配置项。
-`response` 方法拥有签名 `function (request, h)` 也拥有 hapi标准的 response toolkit。
-
-在响应发送会给用户之前,此方法旨在装饰响应对象 (`request.response`),可以为其添加额外的头部信息。
-
-当包装完毕后,你必须返回 `h.continue`, 这样响应才会被发送出去。
-
-当错误发生时,我们依然建议你使用 [boom](https://github.com/hapijs/boom) 进行错误处理。
-
-### 注册
-
-注册一个 scheme 使用 `server.auth.scheme(name, scheme)`。其中 `name` 是一个字符串,用于标明特定的 scheme, 而 `scheme` 就是上文提到的方法。
-
-## Strategies
-
-当你注册了你的 scheme 后,你需要一种方式去使用它。这就是为什么引入了 strategies 。
-
-正如之前提到的,strategy 其实是一个预先配置好的 scheme 实例拷贝。
-
-使用 strategy 时, 我们必须保证有一个 scheme 已经被注册。准本好之后,你就可以使用 `server.auth.strategy(name, scheme, [options])` 去注册你的 strategy。
-
-`name` 参数必须是一个字符串, 用于标明一个特定的 strategy。 `scheme` 其实也是一个字符串, 是实例化这个 strategy 的 scheme 的名称。
+```js
+server.auth.strategy('session', 'cookie', {
+ name: 'sid-example',
+ password: '!wsYhFA*C2U6nz=Bu^%A@^F#SF3&kSR6',
+ isSecure: false
+});
+```
-### 选项
+上述例子,注册了一个`策略(Strategies)`,名为 `session` ,其`计划(scheme)`用 `cookie`。并配置`策略(Strategies)`,给其 `name`、`password`、`isSecure`。
-最后的可选参数是 `options`, 这个参数将直接传递给已经命名的 scheme 。
+### 默认策略(Default Strategies)
-### 设置一个默认的 strategy
+用 `server.auth.default()` 来设置默认`策略(Strategies)`。
-你可以通过使用 `server.auth.default()` 设置一个默认的 strategy。
+此方法接受一个参数, 可传`策略(Strategies)`名,可传对象,其格式与[路由鉴别选项](#route-configuration) 同。
-这个方法接受一个参数, 这个参数可以是一个默认 strategy 的名字,或者与路由handler [auth options](#route-configuration) 同样格式的对象。
+需注意,所有路由都会使用默认`策略(Strategies)`,即使路由添加在 `server.auth.strategy()` 前。
-## 路由配置
+## 路由配置(Route Configuration)
-身份认证也可以在一个路由上通过 `options.auth` 参数进行配置。如果设置为 `false` 将会在路由上关闭身份验证。
+鉴别(Authentication)亦可配置于路由,在 `options.auth`。设置为 `false` 即在该路由上停用鉴别。
-它可以通过 strategy 将要使用的名字去配置,或者一个拥有 `mode`, `strategies`, 和`payload` 参数的对象去配置。
+其值可为`策略(Strategies)`名,或对象,改对象属性如右:`mode`、`strategies`、`payload`。
-`mode` 参数可以被设置为 `'required'`, `'optional'`, 或者`'try'`。 这三种参数在注册 strategy 的时候方式相同。
+参数 `mode` 可为 `'required'`、`'optional'`、`'try'`。此三者与注册`策略(Strategies)`时同。
-如果 `mode` 设置为 `'required'` ,在访问路由的时候必须对用户进行身份进行验证,并且验证必须有效,否则他们将收到错误。
+如果 `mode` 为 `'required'`,访问路由时,必须鉴别用户,且鉴别必须通过,否则报错。
-如果 `mode` 设置为 `'optional'` , 路由也将会使用这个 strategy 。但是用户 *不必* 进行身份验证。一旦如果验证信息提供了,那就必须是有效的。
+如果 `mode` 为 `'optional'`, 路由仍会使用这个策略。不过**无需**鉴别用户。但若提供了鉴别信息,则必须是有效的。
-最后 `mode` 可以被设置为 `'try'`。 `'try'` 与 `'optional'` 的区别在于:如果用 `'try'` ,不合法的身份验证是被接受的, 用户依然可以访问路由 handler。
+`mode` 也可为 `'try'`。`'try'` 与 `'optional'` 的区别在于,使用 `try` 鉴别失败,依然会进入路由处理程序。
-当只需一个 strategy 时,你可以设置 `strategy` 属性为 strategy 的名字。如果指定多个 strategy 时, 参数名称必须为 `strategies`, 属性为 strategy 名称的数组。 这些 strategies 将会按照顺序执行一直到有第一个成功的, 否则的话将全部失败。
+当只需一个策略,可以设置属性 `strategy` 为策略名。如果需要多个, 必须使用属性 `strategies`,其值为策略名数组。这些策略会被依次执行,直到有第一个成功, 或者全部失败。
-最后 `payload` 参数可以被设置为 `false` 表示内容信息不需要被认证。如果设置为 `'required'` 或者 `true` 则意味着内容 *必须* 被验证。 如果设置为 `'optional'` 意味着如果客户端一旦包含了内容认证的信息,这些信息就必须是有效的。
+最后,参数 `payload` 设为 `false`,表示不鉴别荷载。如果设为 `'required'` 或 `true`,则**必须**鉴别。如果设为 `'optional'`,则若客户端带有鉴别信息,这些信息必须有效。
-`payload` 只有在支持`payload` 方法的 strategy 中才会生效。
+策略方法支持 `payload` 时,`payload`生效。
diff --git a/static/lib/tutorials/zh_CN/caching.md b/static/lib/tutorials/zh_CN/caching.md
index 5289fae..cb392fe 100644
--- a/static/lib/tutorials/zh_CN/caching.md
+++ b/static/lib/tutorials/zh_CN/caching.md
@@ -1,18 +1,22 @@
-## 缓存
+# 缓存(Caching)
-_该教程适用于 hapi v17版本_
+_本教程适用于 hapi v17 及以上_
-### 客户端缓存
+## 总览
-HTTP 协议定义了许多 HTTP 头部(headers)信息,方便如浏览器等客户端使用缓存资源。想要更多的了解这些信息,并决定哪些适用与你的用例,可以访问这里 [Google 指南](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching)。
+在服务器上配置缓存,可让网站性能更佳。hapi 中,配置客户端和服务端缓存,十分容易。
-这份教程的第一部分展示了 hapi 如何简单的为客户端配置头部信息。
+## 客户端缓存(Client-side caching)
-#### Cache-Control
+HTTP 协议定义了诸多 HTTP 请求头(headers),用以指示如浏览器登客户端,如何使用缓存资源。欲知详情,可以查阅 [Google 的指南](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching)。
-`Cache-Control` header 控制缓存的条件以及可以缓存多久。 例如, `Cache-Control:max-age=30, must-revalidate, private` 意味这浏览器可以缓存相关的资源 30 秒,`private` 意味这资源只能被浏览器缓存而不是任何中间缓存,`must-revalidate` 意思是一旦过期客户端就必须向服务器重新请求资源。
+本教程的第一部分,演示 hapi 为客户端配置头信息,十分简单。
-让我们看一下 hapi 如何设置这个头部信息:
+### 请求头 Cache-Control
+
+请求头 `Cache-Control` 用以配置是否缓存及缓存时间。例如, `Cache-Control:max-age=30, must-revalidate, private`,表示浏览器可以缓存相关的资源 30 秒,`private` 表示只能被单个用户缓存,`must-revalidate` 表示一旦过期,客户端就必须向服务器重新请求资源。
+
+例:
```javascript
server.route({
@@ -37,84 +41,92 @@ server.route({
});
```
-以上的例子显示了如何为路由设置 `cache` 选项。 这里我们设置 `expiresIn` 为 30 秒,以及 `privacy` 为私有。
-该示例还说明了 `expiresIn` 值可以使用 [response 对象](/api#response-object) 接口提供的 `ttl(毫秒)`值改写。
+上述例子演示了为路由设置 `cache` 选项。此处设置 `expiresIn` 为 30 秒,`privacy` 为私有。
+该示例还表明,`expiresIn` 可以使用 [响应对象](/api#response-object) 接口提供的 `ttl(毫秒)` 值改写。
-如果我们向 `/hapi` 发出请求,我们将收到响应头信息 `cache-control: max-age=30, must-revalidate, private`。 如果我们发送请求 `/hapi/5000` 我们则收到响应头信息 `cache-control: max-age=5, must-revalidate, private`。
+此时向 `/hapi` 发出请求,会收到响应头 `cache-control: max-age=30, must-revalidate, private`。如果向 `/hapi/5000` 发送请求,则收到响应头 `cache-control: max-age=5, must-revalidate, private`。
-参考 [route-options](/api#route-options) 可以获得更多关于 `cache` 配置的信息。
+欲了解更多 `cache` 配置信息,请参考 [route-options](/api#route-options) 。
-#### Last-Modified
+### 请求头 Last-Modified
-在一些情况中,服务器可以提供关于一个资源最后被修改的信息。当使用 [inert](https://github.com/hapijs/inert) 插件来处理静态内容时, 一个 `Last-Modified` header 将在每个响应中自动添加。
+有时,服务器可以提供某资源最后被修改时间的信息。当用 [inert](/modules/inert) 插件来处理静态内容时,`Last-Modified` 请求头会加载所有响应中。
-当响应中设置了 `Last-Modified` header 时, hapi 通过与客户端发来的 `If-Modified-Since` header 比较之后才来决定响应的返回码是否该为 `304 Not Modified`。 这个做法通常也被称之为条件 GET 请求,它的好处就是告知浏览器当收到一个 `304` 响应时,不需要重新去下载资源。
+当响应中设置了 `Last-Modified` 请求头,客户端发来 `If-Modified-Since` 请求头,hapi 会将其与之对比,以决定响应状态码是否该为 `304 Not Modified`。通常称之为条件 GET 请求,其好处是告知浏览器无需为 `304` 响应再次下载资源。
-假设 `lastModified` 是一个 `Date` 对象,你可以通过 [response 对象](/api#response-object) 设置这个头部信息。
+假设 `lastModified` 是 `Date` 对象,可以通过 [响应对象](/api#response-object) 设置这个头信息:
```javascript
return h.response(result)
.header('Last-Modified', lastModified.toUTCString());
```
-这个示例展示了使用 `Last-Modified` 在本教程的 [最后一节](#客户端和服务器端缓存) 中。
+`Last-Modified`,见本文 [最后一节](#clientandserver)。
-#### ETag
+### 请求头 ETag
-ETag header 是除 `Last-Modified` 之外的另一种选择。这里服务器将会提供一个令牌(token) (通常是资源的校验和) 来代替上次改动的时间戳。浏览器将会在下个请求中使用这个令牌去设置 `If-None-Match` header 信息。服务器将会拿这个头部的值与新 `ETag` 的校验和进行比较来决定要不要返回 `304` 响应。
+请求头 ETag `Last-Modified` 的替代品。服务器提供一个令牌(token) (通常是资源的校验和) 来代替最后修改时间戳。下个请求,浏览器会用这个令牌去设置请求头 `If-None-Match`。服务器拿该值与新 `ETag` 比较,来决定要不要返回 `304` 响应。
-在你的 handler 中你只需通过 `etag(tag, options)` 函数来设置 `ETag`:
+用 `etag(tag, options)` 函数来设置 `ETag`:
```javascript
return h.response(result).etag('xxxxxxxxx');
```
-关于 `etag` 的更多细节如参数和选项等信息,可以在[response 对象](/api#response-object) 中找到。
+欲知详情,请参阅[响应对象](/api#response-object)。
+
+## 服务端缓存(Server-side Caching)
+
+hapi 服务端缓存十分强大方便,由 [catbox](/module/catbox) 提供,接下来介绍 catbox。
-### 服务端缓存
+### catbox
-hapi 通过 [catbox](https://www.github.com/hapijs/catbox) 提供强健易用的服务端缓存技术,这份教程接下来的部分将会告诉你如何使用 catbox。
+[catbox](/module/catbox) 是一个多策略的键值对象存储。其提供的扩展,可支持内存缓存,如 [Redis](https://redis.io/)、[Memcached](https://memcached.org/)。
-Catbox有两个接口: client 和 policy。
+Catbox 接口有二: client、policy。
-#### Client
+### Client
-[Client](https://github.com/hapijs/catbox#client) 是一个低级别的接口,允许你设置和获取键值对。它通过以下可用的适配器来初始化: ([Memory](https://github.com/hapijs/catbox-memory), [Redis](https://github.com/hapijs/catbox-redis), [mongoDB](https://github.com/hapijs/catbox-mongodb), [Memcached](https://github.com/hapijs/catbox-memcached), or [Riak](https://github.com/DanielBarnes/catbox-riak))。
+[Client](https://github.com/hapijs/catbox#client) 是个低级接口,允许设置和获取键值对。可由以下适配器来初始化: ([Memory](/module/catbox-memory)、[Redis](/module/catbox-redis)、[Memcached](/module/catbox-memcached)。
-hapi 通过 [catbox memory](https://github.com/hapijs/catbox-memory) 适配器来初始化一个默认的 [client](https://github.com/hapijs/catbox#client)。让我们看一下如何定义更多的 client。
+hapi 里,默认 [client](/module/catbox#client) 用 [catbox memory](/module/catbox-memory) 适配器来初始化。下面例子演示 [Redis](http://redis.io/) 策略来定义 client。
```javascript
'use strict';
const Hapi = require('@hapi/hapi');
+const CatboxRedis = require('@hapi/catbox-redis');
const server = Hapi.server({
port: 8000,
cache: [
{
- name: 'mongoCache',
- engine: require('catbox-mongodb'),
- host: '127.0.0.1',
- partition: 'cache'
- },
- {
- name: 'redisCache',
- engine: require('catbox-redis'),
- host: '127.0.0.1',
- partition: 'cache'
+ name: 'my_cache',
+ provider: {
+ constructor: CatboxRedis,
+ options: {
+ partition : 'my_cached_data',
+ host: 'redis-cluster.domain.com',
+ port: 6379,
+ database: 0,
+ tls: {}
+ }
+ }
}
]
});
```
-在上面的例子中我们定义了两个 catbox client: mongoCache 与 redisCache。包括 hapi 默认创建的 memory catbox 在内,一共有三个可用的缓存 client。注册新的缓存 client时,可以通过省略 `name` 属性来替换默认的 client 。 `partition` 告诉适配器如何命名缓存( 默认为 'catbox')。在 [mongoDB](http://www.mongodb.org/) 这个值将变成数据库的名称,而在 [redis](http://redis.io/) 中它将会变成键的前缀。
+上面例子,定义了 catbox 客户端:`my_cache`。加上 hapi 默认创建的 catbox,现有两个可用的缓存客户端。注册新缓存客户端时,省略属性 `name` 以替换默认客户端。 `partition` 告诉适配器如何命名缓存(默认为 'catbox')。其在 [redis](http://redis.io/) 中为键前缀。
-#### Policy
+### Policy
-[Policy](https://github.com/hapijs/catbox#policy) 是比 Client 更高一级别的接口。接下来展示了如何缓存两个数之和的例子,这个示例的原理也可以应用于其余的缓存情况。如函数的回调结果,async或者是别的内容。[server.cache(options)](/api#-servercacheoptions) 创建一个新的[policy](https://github.com/hapijs/catbox#policy), 这个可以在路由 handler 中使用。
+[Policy](/module/catbox/api#policy) 接口比 Client 更高一级。下面例子,演示缓存两数之和。其原理可举一反三,推广应用。[server.cache(options)](/api#server.cache()) 创建一个新的[policy](/module/catbox/api#policy), 而后可以在路由处理函数中使用。
```javascript
const start = async () => {
+ const server = Hapi.server();
+
const add = async (a, b) => {
await Hoek.wait(1000); // 模拟一些慢的 I/O 操作
@@ -123,7 +135,7 @@ const start = async () => {
};
const sumCache = server.cache({
- cache: 'mongoCache',
+ cache: 'my_cache',
expiresIn: 10 * 1000,
segment: 'customSegment',
generateFunc: async (id) => {
@@ -147,33 +159,41 @@ const start = async () => {
await server.start();
- console.log('Server running at:', server.info.uri);
+ console.log('服务器运行于:', server.info.uri);
};
start();
```
-如果发送一个请求到 http://localhost:8000/add/1/5, 你将在一秒后得到一个响应 `6`。 如果你再次请求的时候,因为被缓存了,将会立刻得到结果。如果你等待10秒后再次请求, 你会发现它需要等待一段时间,因为缓存的值现在已从缓存中移除了。
+此时发请求到 `http://localhost:8000/add/1/5`,一秒后,收到响应 `6`。再次请求,会从缓存中立刻返回结果。如果过 10 秒再次请求,因其缓存值已过期,仍一秒后返回。
-`cache` 选项告诉 hapi 哪个 [client](https://github.com/hapijs/catbox#client) 该被使用。
+`server.cache(options)` 用以配置缓存策略。上述例子,缓存策略为 `'my_cache'`。
-`sumCache.get()` 函数的第一个参数是一个 id, 这可以为一个字符串或者一个拥有 `id` 属性的对象,这个属性是用来在缓存中标识对象。
+属性 `expiresIn` 为缓存过期时间,单位毫秒。例子中,设置为 10 秒。
-除了 **分区(partitions)** 之外, 还有 **分段(segments)** ,它允许你在一个 [client](https://github.com/hapijs/catbox#client) 分区中进一步隔离缓存。如果需要缓存来自两种不同方法的结果,你通常不希望结果混在一起。在 [mongoDB](http://www.mongodb.org/) 适配器中, `segment` 代表一个集合,而在 [redis](http://redis.io/) 中它则是一个额外的拥有 `partition` 选项的前缀。
+属性 `segment` 为缓存分段。例子中,设置为 `'customSegment'`。分段可用于隔离缓存,如不同方法的缓存。[Redis](http://redis.io/) 中,`segment` 是与 `partition` 一起的额外前缀。当在插件内部调用 [server.cache()](/api#-servercacheoptions) 时,`segment` 的默认值为 `'!pluginName'`。当创建[服务器方法](/tutorials/servermethods) 时, `segment` 值是 `'#methodName'`。如果需要多策略功用一个分段,可以使用 [shared](/api#-servercacheoptions) 选项。
-当在插件内部调用 [server.cache()](/api#-servercacheoptions) 时,`segment` 的默认值为 `'!pluginName'`。当创建[server methods](/tutorials/servermethods) 时, `segment` 值将会是 `'#methodName'`。 如果你需要一个用于共享一个分段或者多个分段的用例,可以使用 [shared](/api#-servercacheoptions) 选项。
+方法 `generateFunc` 表示,调用 `get()` 没有命中,则新建缓存。例子中,返回两数之和。若缓存过期,也会调用此方法。还可配置选项 `staleIn` 来设置缓存中的项目过期时间,单位毫秒,其值需小于 `expiredIn`。当缓存过期,但仍在 `staleIn` 时间内,调用 `get()` 仍可命中,且返回缓存值。若过 `staleIn` 时间,调用 `get()` 则不命中,并调用 `generateFunc` 生成新值。
-#### 服务器方法
+属性 `generateTimeout` 用以设置 `generateFunc` 执行超时时间。超时候返回错误信息, 单位毫秒。
-除此之外我们可以做的更好!在 95% 的情况下,可以通过使用 [server methods](/tutorials/servermethods) 进行缓存, 可以将样本代码减少到最小。让我们使用服务器方法重写前面的示例:
+方法 `get(id)` 是用来从缓存中获取项目。如果没有找到该项目,且存在 `generateFunc`,则生成新值,缓存并返回。
+
+方法 `sumCache.get()` 接收参数 id,可为字符串,或一个有 `id` 属性的对象,这个属性标识缓存对象。
+
+查看 [catbox 选项](/module/catbox/api#policy),了解更多。充分利用 `staleIn`、`staleTimeout`、`generateTimeout` 等选项,可实现更多功能。
+
+### 服务器方法(Server methods)
+
+绝大多数情况,可以用服务器方法来缓存,此句可极大减少样板代码。例:
```javascript
const start = async () => {
- // ...
+ const server = Hapi.server();
server.method('sum', add, {
cache: {
- cache: 'mongoCache',
+ cache: 'my_cache',
expiresIn: 10 * 1000,
generateTimeout: 2000
}
@@ -191,32 +211,26 @@ const start = async () => {
await server.start();
- // ...
};
start();
```
-[server.method()](/api#-servermethodname-method-options) 创建一个新的包含 `segment: '#sum'` 的 [policy](https://github.com/hapijs/catbox#policy),以及通过参数自动生成的唯一的 `id` (缓存的键)。 默认来说它只处理 `string`, `number` 以及 `boolean` 参数。对于更复杂的参数,需要提供 `generateKey` 函数去创建一个基于参数的唯一id。 - 请参阅教程的这部分内容 [服务器方法](/tutorials/servermethods) 。
-
-#### 接下来呢?
-
-* 更深入的了解 catbox policy [options](https://github.com/hapijs/catbox#policy) ,了解更多关于 `staleIn`, `staleTimeout`, `generateTimeout`的信息, 以充分利用 catbox 缓存的潜力。
-* 阅读服务器方法的教程 [服务器方法](http://hapijs.com/tutorials/servermethods) 了解更多示例。
+[server.method()](/api#server.method()) 会自动创建一个新 [policy](/module/catbox/api#policy),带有 `segment: '#sum'`。同时,`id`(缓存键) 也自动生成。 默认,可处理 `string`、`number`、`boolean` 参数。对于更复杂的参数,需要提供 `generateKey` 函数,并根据参数唯一id。——详情请参阅服务器方法相关内容。
-### 客户端和服务器端缓存
+## 客户端和服务器缓存(Client and Server caching)
-通常作为可选项,[Catbox Policy](https://github.com/hapijs/catbox#policy) 可以提供从缓存中检索的值的更多信息。若要开启此选项,需要在创建 policy 时将 `getDecoratedValue` 的值设置为 true 。这样,从服务器方法返回的任何值都将是一个对象 `{ value, cached, report }`。 `value` 只是缓存中的项目, `cached` 和 `report` 提供了有关项目缓存状态的一些额外细节。
+[Catbox Policy](/module/catbox/api#policy) 还有可选项,提供从缓存值的更多信息。若要启用,需要在创建 policy 时将 `getDecoratedValue` 设置为 true。这样,从服务器方法返回值都均为对象 `{ value, cached, report }`。 `value` 是缓存值,`cached` 是布尔值,表示是否命中缓存,`report` 是一个对象,包含缓存相关信息。
-服务器端和客户端缓存协同工作的一个例子是使用 `cached.stored` 时间戳设置 `last-modified` 头信息来实现的:
+下面例子,演示服务器和客户端缓存协同工作,使用 `cached.stored` 时间戳来设置 `last-modified` 头。
```javascript
const start = async () => {
- //...
+ const server = Hapi.server();
server.method('sum', add, {
cache: {
- cache: 'mongoCache',
+ cache: 'my_cache',
expiresIn: 10 * 1000,
generateTimeout: 2000,
getDecoratedValue: true
@@ -239,8 +253,7 @@ const start = async () => {
await server.start();
- // ...
};
```
-关于`cached` 和 `report` 的更多信息可以在 [Catbox Policy API docs](https://github.com/hapijs/catbox#api-1) 的文档中找到。
+欲了解关于`cached`和`report`的更多细节,前往[Catbox Policy API](/module/catbox/api#api-1)。
diff --git a/static/lib/tutorials/zh_CN/community.md b/static/lib/tutorials/zh_CN/community.md
index a651208..2f6bf62 100644
--- a/static/lib/tutorials/zh_CN/community.md
+++ b/static/lib/tutorials/zh_CN/community.md
@@ -1,5 +1,47 @@
-# Community Tutorials
+# 社区教程
-## No tutorials yet
+* [hapi 鉴别和授权](https://medium.com/@poeticninja/authentication-and-authorization-with-hapi-5529b5ecc8ec)
-There are no tutorials for this language. If you would like to add one, please submit a pull request.
+ 文章主要讲如何让应用更安全。
+
+* [用 hapi.js、Socket.io、Redis 构建聊天应用程序](https://github.com/dwyl/hapi-socketio-redis-chat-example)
+
+ 构建实时聊天应用,使用 hapi.js、Socket.io、Redis Pub/Sub,含端到端测试。
+
+* [使用 TypeScript、Hapi、PostgreSQL、Prisma 构建现代后端](https://www.prisma.io/blog/series/modern-backend-bdes2ps5kibb)
+
+ 一系列关于使用 TypeScript、Hapi、PostgreSQL、Prisma构 建后台的直播和文章。
+ 该系列涵盖了数据建模、CRUD、聚合、REST、Joi 确认、测试、鉴别、授权、集成其他 API、持续集成以及用 GitHub Actions 和 Heroku 来部署。
+ 这个系列实现了一个具体问题:在线课程评分系统。探索和演示现代后台的不同模式、问题、架构。这歌例子很好,因足够复杂,可为典型。
+
+* [处理插件的依赖性](https://hapipal.com/best-practices/handling-plugin-dependencies)
+
+ 为搞清楚个中 hapi 插件的边界,提供了一个具体的方法来理顺插件间的依赖关系。
+
+* [hapi 系列教程(30多个教程)](https://futurestud.io/tutorials/hapi-get-your-server-up-and-running)
+
+ 这个深度教程系列涵盖了路由设置、鉴别、插件、请求和响应处理等等。
+
+* [hapi.js 全方位教程](https://content.nanobox.io/tag/hapi.js/)
+
+ 涵盖开发、部署、优化、生产管理(扩展、监控等)等等,并不断更新。
+
+* [用 hapi 创建REST API](http://blog.webkid.io/how-to-create-a-rest-api-with-hapi/)
+
+ 使用插件 Dogwater(Waterline ORM)和 Bedwetter,创建 RESTful API。
+
+* [服务器/插件分离的乐趣](https://hapipal.com/best-practices/server-plugin-separation)
+
+ 将插件与部署解耦,让 hapi 应用更易移植,更易测试。
+
+* [rate-limiter-flexible 插件示例](https://github.com/animir/node-rate-limiter-flexible/wiki/Hapi-plugin)
+
+ 使用 rate-limiter-flexible 软件包来限制速率和DDoS保护
+
+* [在谷歌云平台上运行 hapi.js](https://cloud.google.com/nodejs/resources/frameworks/hapi)
+
+ 该示例展示了如何在 Google App Engine上 创建并部署hapi.js应用程序。
+
+* [Catbox-Redis 和 Cookie Auth 实例](https://github.com/johnmanko/catbox-redis-example)
+
+ 演示如何使用 @hapi/catbox-redis 和 @hapi/cookie。
diff --git a/static/lib/tutorials/zh_CN/cookies.md b/static/lib/tutorials/zh_CN/cookies.md
index 9d03022..4cedab1 100644
--- a/static/lib/tutorials/zh_CN/cookies.md
+++ b/static/lib/tutorials/zh_CN/cookies.md
@@ -1,14 +1,21 @@
-## Cookies
+# Cookies
-_该教程适用于 hapi v17版本_
+_本教程适用于 hapi v17 及以上_
-当创建一个 web 应用时, cookies 通常用于保存一个用户在每个请求之间的状态。在 hapi 中, cookies 可以被灵活、安全以及便捷的使用。
+## 总览
-## 配置服务器
+网页应用中, cookies 通常用于维持两次请求间的用户状态。hapi 中,使用 cookies 更灵活、更安全、更便捷。
-hapi 有许多配置选项用于处理 cookies。默认的配置已经可以适用于大多数场景了,当然也可以在需要时进行替换。
+## 配置
+
+处理 cookie,hapi 提供了诸多配置选项。默认配置已足够适用,当然也可以自定义。
+
+### server.state()
+
+使用 cookie,要先命名并配置。可调用 `server.state(name, [options])`。其中,`name` 是 cookie 名,`options` 是配置对象。
+
+通常,使用默认配置即可。此处为了演示,自定义配置:
-使用 cookie, 首先你需要调用 [`server.state(name, [options])`](/api#-serverstatename-options)来配置。这里`name` 是 cookie 的名字, `options` 是用来配置 cookie 的对象。
```javascript
server.state('data', {
@@ -16,14 +23,28 @@ server.state('data', {
isSecure: true,
isHttpOnly: true,
encoding: 'base64json',
- clearInvalid: false, // remove invalid cookies
- strictHeader: true // don't allow violations of RFC 6265
+ clearInvalid: true,
+ strictHeader: true
});
```
-这份配置将 cookie 命名为 `data` ,它拥有一个 session 生命周期 (当浏览器关闭时将被删除),它标记为安全以及只支持 HTTP (参见 [RFC 6265](http://tools.ietf.org/html/rfc6265),的章节 [4.1.2.5](http://tools.ietf.org/html/rfc6265#section-4.1.2.5) 以及 [4.1.2.6](http://tools.ietf.org/html/rfc6265#section-4.1.2.6) 以了解这个标记的更多信息), 之后告诉 hapi 内容是 base64 编码的JSON字符串。关于 `server.state()` 的完整文档可以在 [the API reference](/api#serverstatename-options) 中找到。
+此例,命名 cookie 为 `data`,配置选项如下:
+
+`ttl`,cookie 的生命周期,单位为毫秒。默认为 `null`,即浏览器关闭时删除。
+
+`isSecure` 和 `isHttpOnly`,参见 [RFC 6265](http://tools.ietf.org/html/rfc6265),特别是 [4.1.2.5](http://tools.ietf.org/html/rfc6265#section-4.1.2.5) 和 [4.1.2.6](http://tools.ietf.org/html/rfc6265#section-4.1.2.6)。
+
+`encoding`,cookie 的值是 base64 编码的 JSON 字符串。
+
+`clearInvalid`,当 cookie 无效时,是否清理。默认为 `false`。
+
+`strictHeader`,是否严格检查 cookie 的值。默认为 `true`。参见 [RFC 6265](https://tools.ietf.org/html/rfc6265)。
+
+### route.options.state()
+
+除此之外,每条路有还可单独配置。有两个属性,位于路由的 `options.state` 对象。
-除此之外,你也可以通过路由级别的两个属性更以进一步的配置 cookie 的行为,这两个属性位于路由的 `options.state` 对象中:
+需注意,路由 cookie 是 `server.state` 的补充。
```json5
{
@@ -36,50 +57,67 @@ server.state('data', {
}
```
-## 设置一个 cookie
+`parse`,是否解析 cookie。默认为 `false`。
-cookie 的设置可以通过 [response toolkit](/api#response-toolkit)。这个设置可以在请求的 handler, 预处理或者请求生命周期的扩展的位置中使用,示例如下:
+`failAction`,解析失败时的行为。默认为 `'error'`,即返回错误。可选 `'ignore'` 或 `'log'`。
+
+## 设置 cookie
+
+cookie 设置点有三:[响应工具包](/api#response-toolkit)、前置条件(pre-requisite)、请求生命周期。
+
+### h.state()
+
+调用[`h.state(name, value, [options]`](/api#h.state()),可设置 cookie。例:
```javascript
-h.state('data', { firstVisit: false });
-return h.response('Hello');
+server.route({
+ method: 'GET',
+ path: '/',
+ handler: function (request, h) {
+
+ h.state('data', { firstVisit: false });
+ return h.response('你好');
+ }
+});
```
-这个例子中, hapi 将会返回字符串 `Hello` 同时也设置一个 cookie 名为 `data` 内容为 `{ firstVisit: false }` 的 base64 编码的 JSON 字符串。
+本例,用到上节配置的 cookie。返回 `你好`,以及一个名为 `data` 的 cookie,内容为 `{ firstVisit: false }`,base64 编码的 JSON 字符串。
-`state()` 方法也可以在 [response 对象](/api#response-object) 中使用,这样可以方便链式调用,使用的例子如下:
+`state()` 方法也可用于[响应对象](/api#response-object),方便链式调用。上面例子可以这样写:
```javascript
-return h.response('Hello').state('data', { firstVisit: false });
+return h.response('你好').state('data', { firstVisit: false });
```
-### 重载选项
+## 选项重载
-当设置 cookie 的时候,您也可以将 `server.state()` 可用的选项作为第三个参数传递,例如
+`server.state()` 可接收第三个可选参数,来配置 cookie。例如:
```javascript
-return h.response('Hello').state('data', 'test', { encoding: 'none' });
+return h.response('你好').state('data', 'test', { encoding: 'none' });
```
-这个例子中 cookie 将会被简单的设置为字符串 `"test"` 而不使用任何编码。
+这个例子,仅设置 cookie 为字符串 `"test"`,不编码。
+
+## cookie 获取
-## 获取一个 cookie 的值
+用 `request.state` 来获取 cookie。有三处位置:路由处理函数、预处理、请求生命周期。
-在路由 handler,预处理或请求生命周期扩展的位置中通过 `request.state` 可以访问 cookie 的值。
+`request.state` 对象包含解析的 HTTP 状态。键为 cookie 名,值为内容。
-`request.state` 对象包含了解析后的 HTTP 状态。每一个键代表了 cookie 的名字,而值代表了定义的内容。
+示例代码用到了上面的 `data` cookie,其中的值是 `{ firstVisit: false }`:
```javascript
const value = request.state.data;
-// console.log(value) 将会得到 { firstVisit : false }
+// console.log(value) 会收到 { firstVisit : false }
```
-示例代码使用了 `data` cookie 键,而相关的值被设定为了 `{ firstVisit: false }`.
-
-## 清理一个 cookie
-cookie 可以通过调用 `unstate()` 来清理,这个方法在[response toolkit](/api#response-toolkit) 或者[response object](/api#response-object) 中:
+## cookie 清除
+欲清除 cookie,可调用 `unstate()`,此方法见[响应工具包](/api#response-toolkit) 或 [响应对象](/api#response-object):
```javascript
-return h.response('Bye').unstate('data');
+return h.response('再会').unstate('data');
```
+此处,将 cookie 名传给 `unstate()`,就可以清除 cookie。
+
diff --git a/static/lib/tutorials/zh_CN/expresstohapi.md b/static/lib/tutorials/zh_CN/expresstohapi.md
index 94bc180..4c6a258 100644
--- a/static/lib/tutorials/zh_CN/expresstohapi.md
+++ b/static/lib/tutorials/zh_CN/expresstohapi.md
@@ -1,11 +1,11 @@
# Express 迁移
-_当前文献适用于 hapi v17 或者更新的版本_
+_本教程适用于 hapi v17 及以上_
-## 总揽
+## 总览
-这个Express 到 hapi的指南将向您展示如何使用Express中的知识,以及如何在hapi中做到这一点。 Express的大部分功能都严重依赖中间件,而hapi则更多地内置于核心中。 正文解析 (Body Parsing),cookie的处理,输入/输出验证以及HTTP友好错误处理对象已内置在hapi的框架中。 为了获得更多功能,hapi在其核心生态系统中提供了丰富的插件选择。 hapi也是唯一一个不依赖外部依赖项的框架。每个依赖项均由核心hapi团队进行管理,这使安全性和可靠性成为了hapi的最大优势。
+本教程演示如何将 Express 迁移到 hapi。Express 的大部分功能都严重依赖中间件,而 hapi 则更多内置其于核心。正文解析 (Body Parsing),cookie 处理,输入/输出确认以及 HTTP 友好错误处理对象,均已内置于 hapi 框架。hapi 在其核心生态系统中提供了丰富的插件选择,以扩展额外的功能。hapi 也是唯一没有外部依赖项的框架。每个依赖项均由 hapi 核心团队管理,这让 hapi 优势凸显,更安全、更可靠。
## 配置
@@ -19,7 +19,7 @@ hapi:
`npm install @hapi/hapi`
-### 创建一个 `Server`
+### 创建
Express:
```js
@@ -27,7 +27,7 @@ var express = require('express');
var app = express();
app.listen(3000, function () {
- console.log('Server is running on port 3000');
+ console.log('服务器运行在 3000 端口');
}));
```
@@ -43,22 +43,22 @@ const init = async () => {
});
await server.start();
- console.log('Server running on port 3000');
+ console.log('服务器运行在 3000 端口');
};
init();
```
-与Express不同,在hapi中,您创建了一个服务器对象,它将成为您应用程序的焦点。 服务器对象中设置的属性将确定您的应用程序的行为。 创建服务器对象后,您可以通过调用以下命令启动服务器。
+与 Express 不同,hapi 创建的服务器对象,为您应用程序的焦点。服务器对象中设置的属性将确定应用程序的行为。创建服务器对象后,可以通过调用 `server.start()` 命令启动服务器。
-## 路由 `Routes`
+## 路由
-hapi中的路由以特定顺序被调用,因此,两条路由之间相互冲突的问题将永远不会出现。 路由从最具体到最不具体。 例如,路径为 `'/ home'` 的路径将在 `'/{any*}'` 之前被调用。
+在 hapi 中,路由以特定顺序调用,因而两条路由永不冲突。其调用顺序为:从具体到模糊。例如,`'/home'` 路由先于 `'/{any*}'` 路由。
-让我们来看一下如何在hapi中设置一个基本的路由:
+在 hapi 中设置一个基本的路由:
Express:
```js
-app.get('/hello', function (req, res) {
+app.get('/hello', function (req, res) {
res.send('Hello World!');
});
```
@@ -74,11 +74,11 @@ server.route({
}
});
```
-要创建路由,Express的结构为`app.METHOD(PATH, HANDLER)`,而hapi的结构为`server.route({METHOD, PATH, HANDLER})`。该方法,路径和处理程序作为对象传递给hapi服务器。 如您所见,要在Express中返回字符串,需要调用`res.send()`,而在hapi中,您只需简单的返回字符串。
+要创建路由,Express 的结构为 `app.方法(路径, 处理函数)`,而hapi的结构为`server.route({方法, 路径, 处理函数})`。方法、路径、处理函数作为对象传递给 hapi 服务器。如上例,要在 Express 中返回字符串,需要调用 `res.send()`,而在 hapi 中,只需简单的返回字符串。
### 方法
-除了 `HEAD` 之外,hapi可以使用所有Express可以使用的所有路由方法。hapi还可以在单个路由对象上使用多种方法。 例如:
+在 Express 中能用的路由方法,hapi 亦能用。且 hapi 更进一步,除了 `HEAD` 之外,还可以在单个路由对象上使用多种方法。 例如:
```js
server.route({
@@ -90,17 +90,17 @@ server.route({
}
});
```
-如需使用所有可用的方法,像Express使用 `app.all()` 一样,使用 `method: '*'`。
+如需使用所有可用的方法,Express 用 `app.all()`,hapi 用 `method: '*'`。
### 路径
-与在Express中一样,hapi中的path选项必须是字符串,也可以包含参数。 Express中的参数以`:`开头,例如:`'/users/:userId'`。 在hapi中,您可以将参数放在花括号中,例如:`path: '/users/{userId}'`。
+hapi 的路径(path)选项必须是字符串,也可以带参数,这与 Express 一样。Express 中的参数以 `:`开头,例如:`'/users/:userId'`。在 hapi 中,参数放在花括号中,例如:`path: '/users/{userId}'`。
### 参数
-与Express相比,您已经在上面看到了hapi是如何处理简单参数的。 hapi和Express都以相同方式处理可选参数。与Express一样,要使参数在hapi中成为可选参数,只需在参数后添加一个 `?`:`path: '/hello/{user?}`。
+Express 与 hapi 处理普通参数,各不相同,但处理可选参数,两者相同。在参数后添加 `?` 即可:`path: '/hello/{user?}`。
-在hapi中访问参数与Express非常相似。 如您所知,在Express中,参数填充在 `req.params` 对象中。 在hapi中,可通过 `request.params` 对象获得参数。 这是两个示例:
+在 hapi 中,获取参数与 Express 非常相似。Express 参数在 `req.params` 对象中。hapi 在 `request.params` 对象中。例:
Express:
```js
@@ -108,7 +108,7 @@ app.get('/hello/:name', function (req, res) {
const name = req.params.name
res.send('Hello ' + name);
-});
+});
```
hapi:
@@ -124,13 +124,13 @@ server.route({
});
```
-在两个框架中,查询参数也相似。 在Express中,它们可以通过 `req.query` 获得,而hapi它们可以通过 `request.query` 获得。
+两框架查询参数也相似。Express 通过 `req.query` 获得,而 hapi 通过 `request.query` 获得。
-### Handler
+### 处理函数
-Express和hapi构造其路由处理程序的方式有所不同。 与Express的处理程序的参数为 `req` 和 `res` 不同,hapi的处理程序的参数为 `request` 和 `h`。 第二个参数 `h` 是响应工具包,它是一个具有几种用于响应请求的方法的对象。
+Express 和 hapi 的路由处理函数的结构有所不同。Express 的处理函数参数为 `req` 和 `res`,hapi的处理程序参数为 `request` 和 `h`。其中,第二个参数 `h` 为响应工具包,是个对象,有几个方法,用来响应请求。
-这里是一个关于使用Express和hapi的路由和handler用来重定向到一个新的路由的用例:
+请看用例,用路由处理函数来重定向到新路由:
Express:
```js
@@ -152,7 +152,7 @@ server.route({
});
```
-这两个路由都将重定向到 `'/'` 路由。 Express使用响应方法 `res.redirect`,而hapi使用 `h.redirect`,它是响应工具包的一部分。 hapi可以通过使用`return`来完成Express响应方法。 其中一些方法包括`res.send`和`res.json`。 以下是hapi如何使用JSON数据进行响应的示例:
+这两个路由都重定向到 `'/'`。 Express 使用响应方法 `res.redirect`,而hapi用响应工具包 `h.redirect`。但有一些 Express 的响应方法,hapi 只需 `return` 即可。诸如 `res.send`、`res.json`。 以下例子,hapi 响应 JSON 数据:
```js
server.route({
@@ -161,9 +161,9 @@ server.route({
handler: function (request, h) {
const user = {
- firstName: 'John',
- lastName: 'Doe',
- userName: 'JohnDoe',
+ lastName: '张',
+ firstName: '三',
+ userName: '张三',
id: 123
}
@@ -171,19 +171,19 @@ server.route({
}
});
```
-hapi具有默认情况下响应JSON数据的功能。唯一要做的就是返回一个有效的JavaScript对象,而hapi将为您处理其余的工作。
+hapi 默认响应 JSON。只要返回有效的 JavaScript 对象,hapi 自动处理剩余工作。
-## 中间件 vs 插件和扩展 `Middleware vs Plugins and Extensions`
+## 中间件与插件和扩展 `Middleware vs Plugins and Extensions`
-为了扩展其功能,Express使用中间件。中间件实质上是一系列使用回调执行下一个功能的功能。问题在于,随着您的应用程序规模和复杂性的增长,中间件的执行顺序变得越来越重要,也更加难以维护。 在依赖中间件之前执行中间件会导致您的应用程序失败。hapi通过其强大的插件和扩展系统解决了此问题。
+Express 用中间件来扩展功能。中间件本质上是一系列回调函数。但应用程序上规模后,中间件执行顺序难以维护。若某中间件在前,其依赖中间件执行在后,会出问题。hapi 强大的插件和扩展系统可解决此问题。
-插件使您可以将应用程序逻辑分解为孤立的业务逻辑和可重用的实用程序。每个插件都有其自己的依赖性,这些依赖性在插件本身中明确指定。这意味着您不必自己安装依赖项即可使插件正常工作。 您可以添加现有的hapi插件,也可以编写自己的插件。有关插件的更详细的教程,请参见[plugins](/tutorials/plugins/?lang=en_US)教程。
+插件可将程序逻辑分解,以复用。各插件依赖项彼此独立,依赖明确于插件内。换言之,不必额外安装插件的依赖项。可以添加已有的插件,亦能自己编写。欲知详情,参见[插件教程](/tutorials/plugins/?lang=zh_CN)。
-hapi中的每个请求都遵循预定义的路径,即请求生命周期。hapi具有扩展点,可让您在生命周期中创建自定义功能。hapi中的扩展点让您知道应用程序运行的确切顺序。有关更多信息,请参见hapi [request lifecycle](/api/#request-lifecycle)。
+hapi 中的每个请求都有请求生命周期。hapi 的请求生命周期扩展点,可创建自定义功能。经由扩展点,亦能明确应用程序运行顺序。详情请参见 hapi [请求生命周期](/api/#request-lifecycle)。
-### 延伸点
+### 扩展点
-hapi在请求周期内一共具有7个扩展点。 按顺序,它们分别都是 `onRequest`, `onPreAuth`, `onCredentials`, `onPostAuth`, `onPreHandler`, `onPostHandler` 以及 `onPreResponse`。要将功能添加到扩展点,请调用 `server.ext()`。让我们看一个例子:
+hapi 请求生命周期共有扩展点 7 个。依次是 `onRequest`、`onPreAuth`、`onCredentials`、`onPostAuth`、`onPreHandler`、`onPostHandler`、`onPreResponse`。要在扩展点上加功能,请调用 `server.ext()`。例:
```js
server.ext('onRequest', function (request, h) {
@@ -193,11 +193,11 @@ server.ext('onRequest', function (request, h) {
});
```
-该函数将在第一个扩展点 `onRequest`处运行。 在服务器接收到请求对象之后,即在路由查找之前,将运行 `onRequest`。此功能将执行的操作是将所有请求重新路由到 `'/test'` 路由。
+该扩展函数运行于 `onRequest`,此扩展点为第一个。在收到请求对象后,路由查询之前,运行 `onRequest`。此函数将全部请求重新路由到 `'/test'`。
-### 制作一个插件
+### 插件制作
-如您所知,您可以在Express中编写自己的中间件。 hapi插件也是如此。 插件是具有必需的 `name` 和 `register` 属性的对象。`register` 属性是一个带有 `async function (server, option)` 签名的功能。 让我们看一下如何创建一个基本的插件:
+众所周知,Express 可自定义中间件。hapi 插件也能。插件是个对象,包含必要属性 `name` 和 `register`。属性 `register` 是个方法, 其签名为 `async function (server, option)`。以下演示一个基础插件:
Express:
```js
@@ -230,11 +230,11 @@ const getDate = {
}
};
```
-hapi插件会将当前日期保存在 `h.getDate()` 中。然后,我们可以在任何路由处理程序中使用它。
+hapi 插件将当前日期保存于 `h.getDate()`。然后,可以在任何路由处理程序中使用。
-### 加载一个插件
+### 插件加载
-在Express中,您可以通过调用 `app.use()` 方法来加载中间件。 在hapi中,您可以调用 `server.register()` 方法。 让我们加载上一节中创建的插件:
+在 Express 中,调用 `app.use()` 方法来加载中间件。在 hapi 中,调用 `server.register()` 方法。以下演示加载上一节创建的插件:
Express:
```js
@@ -247,11 +247,11 @@ await server.register({
plugin: getDate
});
```
-您还可以通过在 `server.register()` 上设置 `options` 属性来为插件提供选项。
+此外,还可以在 `server.register()` 上设置属性 `options` 来为插件提供选项。
### 选项
-您可以通过导出接受options参数的函数来向Express中间件添加选项,该函数随后返回中间件。在hapi中,您可以在注册插件时设置选项。我们来看一下:
+Express 中,通过导出接受 options 参数的函数来向间件添加选项,该函数随后返回中间件。在 hapi 中,注册插件时设置选项。来看一下:
Express:
```js
@@ -260,7 +260,7 @@ module.exports = function (options) {
req.getDate = function() {
- const date = 'Hello ' + options.name + ', the date is ' + new Date();
+ const date = '你好 ' + options.name + ', 现在是 ' + new Date();
return date;
};
@@ -274,11 +274,11 @@ hapi:
server.register({
plugin: getDate,
options: {
- name: 'Tom'
+ name: '张三'
}
})
```
-要访问hapi中的选项,只需在创建插件时引用 `options` 对象即可:
+要访问 hapi 中的选项,只需在创建插件时引用 `options` 对象即可:
Express:
```js
@@ -296,7 +296,7 @@ const getDate = {
const currentDate = function() {
- const date = 'Hello ' + options.name + ', the date is ' + new Date();
+ const date = '你好 ' + options.name + ', 现在是 ' + new Date();
return date;
};
@@ -307,7 +307,7 @@ const getDate = {
## body-parser
-hapi的核心具有解析能力。与Express不同,您不需要中间件来解析有效载荷数据。实际上,根据要解析的数据类型,您可能需要在Express中最多安装四个附加的中间件。在hapi中,有效载荷数据(无论是JSON还是纯文本)都可以在 `request.payload` 对象中获得。这是解析简单有效负载数据的并排比较:
+hapi 在其核心内置了解析功能。不同于 Express,不需要中间件来解析负载数据。其实,要解析数据,Express 可能需要额外安装三四个中间件。而 hapi,负载数据,无论是 JSON 还是纯文本,都可以在 `request.payload` 对象中获得。例:
Express:
```js
@@ -315,10 +315,10 @@ var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extend: true}));
-app.post('/hello', function (req, res) {
+app.post('/hello', function (req, res) {
var name = req.body.name
- res.send('Hello ' + name);
+ res.send('你好 ' + name);
});
```
@@ -330,20 +330,20 @@ server.route({
handler: function (request, h) {
const name = request.payload.name;
- return `Hello ` + name;
+ return `你好 ` + name;
}
});
```
-要解析express中的JSON对象,您必须指定它:
+express 中,解析 JSON,必须指明:
```js
app.use(bodyParser.json());
```
-JSON解析内置于hapi中,因此不需要其他的步骤。
+而 hapi,内置 JSON 解析。
## cookie-parser
-在Express中设置和解析cookie要求您安装 `cookie-parser` 中间件。hapi的核心已经内置了cookie功能,因此无需安装其他中间件。 要在hapi中使用cookie,首先要使用 `server.state()` 配置cookie。 我们来看一下:
+在 Express 中,设置和解析 cookie,需安装 `cookie-parser` 中间件。hapi 直接内置了cookie 功能,因而无需其他中间件。要在 hapi 中使用 cookie,首先要用 `server.state()` 配置 cookie。例:
```js
const Hapi = require('@hapi/hapi');
@@ -357,8 +357,8 @@ server.state('data', {
});
```
-### 设定 Cookie
-设置cookie一旦配置了cookie,就可以使用 `h.state()` 设置cookie。这里有一个例子:
+### Cookie 设置
+配置了 cookie,就可以用 `h.state()` 设置 cookie。例:
Express:
```js
@@ -369,9 +369,9 @@ var cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get('/', function(req, res) {
-
- res.cookie('username', 'tom', { maxAge: null, secure: true, httpOnly: true });
- res.send('Hello');
+
+ res.cookie('username', '张三', { maxAge: null, secure: true, httpOnly: true });
+ res.send('你好');
});
```
@@ -392,17 +392,17 @@ server.route({
path: '/',
handler: function (request, h) {
- h.state('username', 'tom');
- return h.response('Hello');
+ h.state('username', '张三');
+ return h.response('你好');
}
});
```
-明确地说,您可以使用 `res.cookie` 中的 `options` 对象配置cookie。在hapi中,cookie配置使用 `server.state` 保存到服务器对象。然后,您可以使用 `h.state()` 将数据附加到cookie。
+Express 配置 cookie,用 `res.cookie` 中的 `options` 对象。hapi 用 `server.state` 保存配置到服务器对象。然后,用 `h.state()` 将数据附加到 cookie。
-### 得到一个 Cookie 的值
+### Cookie 获取
-要获取hapi中的cookie值,请调用 `request.state`。让我们看看:
+欲获取 hapi 中的 cookie,调用 `request.state`。例:
Express:
```js
@@ -413,8 +413,8 @@ var cookieParser = require('cookie-parser);
app.use(cookieParser());
app.get('/', (req, res) => {
-
- res.cookie('username', 'tom', { maxAge: null, secure: true, httpOnly: true })
+
+ res.cookie('username', '张三', { maxAge: null, secure: true, httpOnly: true })
res.send(req.cookies.username);
});
```
@@ -436,7 +436,7 @@ server.route({
path: '/',
handler: async (request, h) => {
- h.state('username', 'tom');
+ h.state('username', '张三');
return h.response(request.state.username);
}
});
@@ -444,7 +444,7 @@ server.route({
## Passport -> bell
-在Express中,第三方身份验证是通过Passport处理的。在hapi中,您可以使用 [bell](/module/bell) 模块进行第三方身份验证。`bell` 为OAuth提供者提供了30多种预定义的配置,包括Twitter,Facebook,Google,GitHub等。 它还将允许您设置自己的自定义提供程序。 有关完整列表,请参阅 [bell providers documentation](https://github.com/hapijs/bell/blob/master/Providers.md)。 `bell` 是由核心hapi团队开发并维护的,因此您知道稳定性和可靠性不会成为问题。 让我们看看如何使用您的Twitter凭据进行身份验证:
+Express 用 Passport 来确认第三方身份。在 hapi 中,可以使用 [bell](/module/bell) 模块。`bell` 预定义 OAuth 配置有 30 余种,包括 Twitter、Facebook、Google、GitHub等。也可以自定义。完整列表,请参阅 [bell 提供者](https://github.com/hapijs/bell/blob/master/Providers.md)。`bell` 由 hapi 核心团队开发并维护,因此十分稳定可靠。Twitter 例:
Express:
```
@@ -507,18 +507,18 @@ server.auth.strategy('twitter', 'bell', {
});
server.route({
- method: '*',
- path: '/auth/twitter', // The callback endpoint registered with the provider
+ method: '*',
+ path: '/auth/twitter', // 在提供者那里注册的回调端点
handler: function (request, h) {
if (!request.auth.isAuthenticated) {
return `Authentication failed due to: ${request.auth.error.message}`;
}
-
- // Perform any account lookup or registration, setup local session,
- // and redirect to the application. The third-party credentials are
- // stored in request.auth.credentials. Any query parameters from
- // the initial request are passed back via request.auth.credentials.query.
+
+ // 执行账户查询或注册,设置本地会话,并重定向到应用程序。
+ // 第三方凭证存储于 request.auth.credentials
+ // 初始请求中的任何查询参数
+ // 都通过 request.Auth.credentials.query 传回。
return h.redirect('/home');
},
@@ -530,25 +530,25 @@ server.route({
}
});
```
-要使用bell,只需注册插件并使用 `server.auth.strategy` 配置策略。
+要使用 bell,只需注册插件,并使用 `server.auth.strategy` 配置策略。
-`provider` 是第三方的名称。
+`provider` 是第三方名称。
-`password` 是cookie加密密码。
+`password` 是 cookie 加密密码。
-`clientId` 是OAuth客户端标识符,可从提供商处获得。
+`clientId` 是 OAuth 客户端标识符,可从提供商处获得。
-`clientSecret` 是OAuth客户端机密,可以从提供商处获得。
+`clientSecret` 是OAuth客户端密钥,可以从提供商处获得。
-`isSecure` 设置 cookie 安全 flag。 对于产品,应将其设置为 `true`,这也是默认设置。
+`isSecure` 设置 cookie 安全标志。生产环境,应将其置为 `true`,这也是默认设置。
## express-validator -> joi
-要在Express中验证数据,您可以使用 `express-validator` 插件。`express-validator` 的最大缺点之一是尽管您可以验证请求,但没有明确的方法来验证响应。在hapi中,可以使用 [joi](https://joi.dev) 模块,该模块可以轻松地验证请求和响应。Joi允许您使用简单干净的对象语法创建自己的验证。 要更深入地了解hapi中的验证,请参见 [validation](/tutorials/validation/?lang=en_US) 教程。
+Express 确认数据,用`express-validator` 插件。`express-validator` 有个大缺点,可以确认请求,但没有明确的方法来确认响应。在 hapi 中,可以使用 [joi](https://joi.dev) 模块,用该模块确认请求和响应,更轻松方便。Joi 使用简单干净的对象语法创建确认。 要更深入了解,请参见 [确认教程](/tutorials/validation/?lang=zh_CN)。
-### 输入验证
+### 输入确认
-输入验证使您可以验证任何输入到服务器的输入数据,包括其参数,有效负载等。下面介绍如何验证Express和hapi中的博客文章条目:
+输入确认可以核实传到服务器的数据,包括其参数,有效负载等。下面介绍如何在 Express 和 hapi 中确认博文条目:
Express:
```
@@ -566,13 +566,13 @@ app.use(expressValidator())
app.post('/post', function (req, res) {
- req.check('post', 'Post too long').isLength({ max: 140 });
+ req.check('post', '文章过长').isLength({ max: 140 });
let errors = req.validationErrors();
if (errors) {
res.status(400).send(errors);
} else {
- res.send('Blog post added!')
+ res.send('文章已添加!')
}
});
```
@@ -591,7 +591,7 @@ server.route({
path: '/post',
handler: (request, h) => {
- return 'Blog post added!';
+ return '文章已添加!';
},
options: {
validate: {
@@ -603,15 +603,15 @@ server.route({
});
```
-首先安装 `joi`,然后在项目中需要它。 要验证输入日期,请指定所需的数据类型,然后在该数据上设置规则。在这种情况下,`post` 将是一个 `string`,最多包含140个字符。在 `joi` 中,您可以将规则串在一起,例如:
+安装 `joi` 并引入。要确认输入数据,需指定数据类型,设置规则。此时,`post` 为 `string`,最长 140 个字符。在 `joi` 中,可以将规则链在一起,例如:
```js
Joi.string().min(1).max(140).
```
-### 输出验证
+### 输出确认
-如上所述,没有明确的方法可以使用 `express-validator` 进行响应验证。使用 `joi`,响应验证既快速又简单。我们来看一下:
+如上所述,`express-validator` 没有明确方法来确认响应。而 `joi`,响应确认快速且简单。我们来看一下:
hapi:
```js
@@ -638,20 +638,21 @@ server.route({
}
});
```
-此路线将返回书籍列表。 在 `options` 路线属性中,我们可以指定书籍清单应遵循的规则。 通过将 `failAction` 设置为 `log`,如果出现错误,服务器将记录该错误。
+此路由返回书籍列表。在 `options` 中,指定书籍列表应遵循的规则。将 `failAction` 设置为 `log` 后,若出现错误,服务器会记录之。
## app.set('view engine') -> vision
-hapi对模板渲染提供了广泛的支持,包括加载和利用多个模板引擎,局部函数,助手(模板中用于操纵数据的功能)和布局的能力。Express通过使用 `app.set('view engine')` 启用视图功能,其中hapi的功能由 [vision](/module/vision) 插件提供。有关hapi中视图的更详细的教程,请参见 [views](/tutorials/views/?lang=en_US) 教程。
+hapi 支持模板渲染,包括加载和利用各种模板引擎、参数,辅助函数(helpers,模板中用于操处理数据)、布局的能力。Express 使用 `app.set('view engine')` 启用视图功能,hapi由 [vision](/module/vision) 插件提供。有关 hapi 中视图的更详细的教程,请参见 [视图教程](/tutorials/views/?lang=zh_CN)。
-### 设置 View Engine
+### 设置视图引擎
-在 express 中设置 views engine 如下:
+express 中设置视图引擎如下:
```js
app.set('view engine', 'pug');
```
-要在hapi中设置view engine,首先必须注册vision plugin,然后配置 `server.views`:
+
+要在 hapi 中设置视图引擎,注册 `vision` 插件,并配置 `server.views`:
```js
await server.register(require('@hapi/vision'));
@@ -663,24 +664,23 @@ server.views({
path: 'views'
});
```
-默认情况下,Express将在 `views` 文件夹中寻找视图或模板。在hapi中,您可以使用 `relativeTo` 和 `path` 属性来指定视图的位置。像Express一样,hapi支持各种模板引擎(`template engines`),例如pug,ejs,handbars等。
-By default, Express will look for views or templates in the `views` folder. In hapi, you specify where the views are located using the `relativeTo` and `path` properties. Like Express, hapi supports a wide variety of templating engines, such as pug, ejs, handlebars, etc.
+Express 默认在 `views` 文件夹中寻找视图或模板。hapi 中,可用 `relativeTo` 和 `path` 属性来指定视图的位置。像 Express 一样,hapi支持各种模板引擎(`template engines`),例如 pug、ejs、handlebars等。
-hapi在 `server.views` 中有更多可配置的选项。要查看功能的完整列表,请转到 [views](/tutorials/views/?lang=en_US) 教程。
+hapi 的 `server.views` 配置丰富。详情参见 [views 教程](/tutorials/views/?lang=zh_CN)。
-### 渲染一个 View
+### 渲染
-要在Express中渲染视图,您可以调用 `res.render()`。 通过view,hapi有两种渲染view的方法:`h.view` 和view handler。 让我们看一下两者的用法。
+Express 中渲染视图,调用 `res.render()`。hapi 则有两种方法:`h.view`、视图处理对象。例:
-第一个是在Express中渲染 view:
+Express 中渲染:
```js
app.get('/', function (req, res) {
- res.render('index', { title: 'Homepage', message: 'Welcome' });
+ res.render('index', { title: '主页', message: '欢迎' });
});
```
-Using `h.view` in hapi:
+hapi 用 `h.view`:
```js
server.route({
@@ -688,11 +688,11 @@ server.route({
path: '/',
handler: function (request, h) {
- return h.view('index', { title: 'Homepage', message: 'Welcome' });
+ return h.view('index', { title: '主页', message: '欢迎' });
}
});
```
-然后是在hapi中使用handler来渲染 view:
+或用视图处理对象:
```js
server.route({
@@ -702,22 +702,22 @@ server.route({
view: {
template: 'index',
context: {
- title: 'Homepage',
- message: 'Welcome'
+ title: '主页',
+ message: '欢迎'
}
}
}
});
-```
-为了在 `h.view` 中传递上下文,您需要传递一个对象作为第二个参数。 要在 view 处理程序中传递 `context`,请使用 `context` 键。
+```
+欲在 `h.view` 中传递上下文,需要传递一个对象以为第二参数。要在视图处理对象中上下文,请使用 `context` 属性。
## express.static() -> inert
-hapi可以从名为 [inert](/module/inert) 的插件中提供静态内容。inert提供了用于处理静态文件和目录的新处理程序方法,以及向响应工具箱中添加了 `h.file()` 方法。有关hapi中服务器静态文件的更深入的教程,请参阅 [serving static files](/tutorials/servingfiles/?lang=en_US) 教程。
+hapi 静态文件服务用 [inert](/module/inert) 插件。inert 提供了用于处理静态文件和目录的新处理方法,并向响应工具包添加了 `h.file()` 方法。详情请参阅 [静态文件服务](/tutorials/servingfiles/?lang=zh_CN) 教程。
-### 服务单个文件
+### 单文件服务
-在Express中,您可以使用 `res.sendFile` 方法返回单个文件。在hapi中,您可以使用 `h.file()` 方法或文件处理程序,可以通过 [inert](/module/inert) 使用。一旦注册了插件,便可以提供静态文件:
+Express 用 `res.sendFile` 方法返回单个文件。在hapi中,使用 `h.file()` 方法或文件处理对象,可以通过 [inert](/module/inert) 获得。一旦注册了 `inert` 插件,便可以提供静态文件服务:
Express:
```js
@@ -749,7 +749,7 @@ server.route({
}
});
```
-hapi with file handler:
+或文件处理对象:
```js
const server = new Hapi.Server({
port: 3000,
@@ -771,10 +771,11 @@ server.route({
});
```
-要在hapi中提供静态文件,您首先必须告诉hapi静态文件位于何处。您可以通过配置 `server.options.routes` 对象来实现。您将 `relativeTo` 设置为文件所在的文件夹,就像在Express中 `res.sendFile` 的 options 对象中所做的一样。接下来,您需要注册惰性插件。这将使您能够访问允许提供静态文件的方法。现在,在路由处理程序中,您可以使用 `h.file()` 方法或文件处理程序来处理您的静态文件。
+要启用静态文件服务,须指明静态文件目录。其配置在 `server.options.routes` 中的 `files.relativeTo`,就像在 Express 中 `res.sendFile` 选项一样。接下来,注册 `inert` 插件,以添加访问静态文件的方法。而后,在路由处理函数中,用 `h.file()` 或文件处理对象来处理静态文件。
### 静态文件服务器
-要在Express中设置静态文件服务器,可以使用 `express.static()` 中间件。在hapi中,您使用 [inert](/module/inert) 插件提供的文件处理程序。您将通过告诉文件位于何处,以与服务单个静态文件相同的方式设置服务器。 然后,您将设置一条路由以捕获所有请求并返回正确的文件。 我们来看一下:
+
+Express 中设置静态文件服务器,使用 `express.static()` 中间件。在 hapi 中,使用 [inert](/module/inert) 插件提供的文件处理程序。与上一节一样,配置目录。 而后,设置路由以捕获请求并返回文件。例:
Express:
```js
@@ -804,14 +805,13 @@ server.route({
}
});
```
-现在,您可以通过访问 `localhost:3000/filename` 来访问任何静态文件。 [inert](/module/inert) 具有许多其他选项和功能。要查看其所有功能,请参阅 [serving static files](/tutorials/servingfiles/?lang=en_US) tutorial 教程。
-Now, you can access any static files by going to `localhost:3000/filename`. [inert](/module/inert) has many other options and capabilities. To see what all it can do, please see the [serving static files](/tutorials/servingfiles/?lang=en_US) tutorial.
+访问 `localhost:3000/filename` 来查看静态文件。[inert](/module/inert) 选项丰富,功能多样,详情请参阅 [静态文件服务教程](/tutorials/servingfiles/?lang=zh_CN)。
## Error Handling -> boom
-hapi使用 [boom](/module/boom) 模块来处理错误。默认情况下,boom将以JSON格式返回错误。另一方面,Express默认情况下将返回文本响应,该响应在JSON API中并非最佳。通过向不存在的 `'/hello'` 提交 `GET` 请求,以默认设置查看404错误响应:
+hapi 用 [boom](/module/boom) 模块来处理错误。默认格式为 JSON。而 Express 默认返回文本响应,这对 JSON API 不友好。下面例子演示 404 错误响应,向未知路由 `/hello` 发请求:
-Express:
+Express:
```js
Cannot GET /hello
@@ -826,9 +826,9 @@ hapi:
}
```
-### 自定义消息
+### 消息自定义
-`boom` 可让您轻松更改任何状态代码的错误消息。让我们使用上面的404错误并返回一条新消息:
+`boom` 可自定义错误消息。继续上面的 404 错误并返回新消息:
Express:
```js
@@ -840,4 +840,4 @@ hapi:
throw Boom.notFound('Page not found');
```
-在Express中,您设置状态代码,然后发送错误消息正文。在这种情况下,我们返回带有状态代码和错误消息的JSON对象。在 `boom` 中,不需要返回带有状态码的JSON对象,默认情况下会这样做。在上面的示例中,您抛出 `Boom.notFound()` 来设置错误消息。 `boom` 包含一长串4xx和5xx错误,例如 `Boom.unauthorized()`,`Boom.badRequest()`,`Boom.badImplementation()`等。有关完整列表,请参见 [boom](/module/boom) 文档。
+Express 中,自定义报错信息,需以 json 对象形式指明状态码及报错信息。而 `boom` 无需如此。在上面的示例中,抛出 `Boom.notFound()` 来设置错误消息。`boom` 内置诸多 4xx、5xx 错误处理方法,例如 `Boom.unauthorized()`、`Boom.badRequest()`、`Boom.badImplementation()`等。详情请参见 [boom](/module/boom)。
diff --git a/static/lib/tutorials/zh_CN/gettingstarted.md b/static/lib/tutorials/zh_CN/gettingstarted.md
index 3ae7ba1..17015aa 100644
--- a/static/lib/tutorials/zh_CN/gettingstarted.md
+++ b/static/lib/tutorials/zh_CN/gettingstarted.md
@@ -1,35 +1,37 @@
-## 快速入门
+# 快速入门
-_该教程适用于 hapi v17版本_
+_本教程适用于 hapi v17 及以上_
-### 安装 hapi
+## 总览
-新建一个目录 `myproject`:
+本教程演示搭建一个基础的 hapi 服务器,并在浏览器中显示 "Hello World!"。
-* 运行: `cd myproject` 跳转到目录内。
-* 运行: `npm init` 根据提示生成文件 package.json。
-* 运行: `npm install --save @hapi/hapi@17.x.x` 将安装 hapi ,并将相应的依赖并保存至 package.json 中。
+## 安装
-就是这么简单! 你现在已经做好了创建一个 hapi 应用的全部准备。
+新建目录 `myproject`:
-### 创建一个Web服务器
+* 运行: `cd myproject`,跳转到目录内。
+* 运行: `npm init`,根据提示生成文件 package.json。
+* 运行: `npm install @hapi/hapi`,安装 hapi,并将相应的依赖并保存至 package.json。
-最基本的Web服务器代码如下:
+## 创建服务器
+
+最基本的 hapi 服务器代码如下:
```javascript
'use strict';
const Hapi = require('@hapi/hapi');
-const server = Hapi.server({
- port: 3000,
- host: 'localhost'
-});
-
const init = async () => {
+ const server = Hapi.server({
+ port: 3000,
+ host: 'localhost'
+ });
+
await server.start();
- console.log(`Server running at: ${server.info.uri}`);
+ console.log('服务器运行于 %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
@@ -41,175 +43,39 @@ process.on('unhandledRejection', (err) => {
init();
```
-首先引入 hapi,之后我们创建一个 web 服务器并且配置好 host 以及需要监听的端口,最后我们启动这个服务器,并且将运行的信息输出到日志中。
+首先引入 hapi,初始化 `Hapi.server()`,传入要监听的端口号(port)和地址(host),启动服务,会在终端打印运行信息。
-Web 服务器可以通过以下方式创建:指定主机名、填写IP地址、Unix socket文件或者通过Windows管道绑定到指定的服务器。更多细节内容请参考 [API reference](/api/#-server-options).
+其中,端口号可以为以下值:主机名、IP 地址、Unix socket 文件、Windows 命名管道。更多细节,见 [API](/api/#-serveroptionsport)。
-### 添加路由
+安全起见,`host` 属性通常设置为 `localhost`。但在 docker 容器中,`localhost` 可能无法在容器外访问,此时需要设置为 `host: '0.0.0.0'`。
-现在已经有了一个简单的Web服务器,下面将添加两条路由:
+## 添加路由
+
+服务器启动后,添加一个路由,以在浏览器中显示 "Hello World!"。
```javascript
'use strict';
const Hapi = require('@hapi/hapi');
-const server = Hapi.server({
- port: 3000,
- host: 'localhost'
-});
-
-server.route({
- method: 'GET',
- path: '/',
- handler: (request, h) => {
-
- return 'Hello, world!';
- }
-});
-
-server.route({
- method: 'GET',
- path: '/{name}',
- handler: (request, h) => {
-
- return 'Hello, ' + encodeURIComponent(request.params.name) + '!';
- }
-});
-
const init = async () => {
- await server.start();
- console.log(`Server running at: ${server.info.uri}`);
-};
-
-process.on('unhandledRejection', (err) => {
-
- console.log(err);
- process.exit(1);
-});
-
-init();
-```
-
-将以上代码保存至 `server.js` 之后通过命令 `node server.js`启动服务器。在浏览器中访问 [http://localhost:3000](http://localhost:3000), 你将会看到以下文本 `Hello, world!`, 之后再访问 [http://localhost:3000/stimpy](http://localhost:3000/stimpy) 你将会看到 `Hello, stimpy!`。
-
-请注意这里使用了 URI 编码, 这可以有效地防止内容注入攻击。 因此,尽量使用内容编码去处理用户输入的信息。
-
-`method` 可以使用任何有效的 HTTP 方法或者 HTTP 方法数组,又或者使用`*`去通配所有方法。`path` 定义了该路径下所包含的参数,可以为可选参数,数字以及通配符。更多细节内容请参考 [路由教程](/tutorials/routing).
-
-### 建立静态内容
-
-我们已经验证了了可以使用 Hello World 应用程序启动一个简单的hapi应用。 之后我们将使用一个名为 **inert** 的插件去创建静态页面。 在你开始之前,通过命令 **CTRL + C** 停止你的服务器。
-
-安装 [inert](https://github.com/hapijs/inert) 使用以下命令: `npm install --save @hapi/inert` 这将会下载 [inert](https://github.com/hapijs/inert) 包,并且将其添加到用于记录依赖的 `package.json` 文件中。
-
-在你的 `server.js` 文件中修改 `init` 函数如下:
-
-``` javascript
-const init = async () => {
-
- await server.register(require('@hapi/inert'));
+ const server = Hapi.server({
+ port: 3000,
+ host: 'localhost'
+ });
server.route({
method: 'GET',
- path: '/hello',
+ path: '/',
handler: (request, h) => {
- return h.file('./public/hello.html');
+ return 'Hello World!';
}
});
await server.start();
- console.log(`Server running at: ${server.info.uri}`);
-};
-```
-
-通过 `server.register()` 命令将插件 [inert](https://github.com/hapijs/inert) 注册到你的 hapi 应用中。
-
-`server.route()` 将注册 `/hello` 路由, 并接收一个 GET 请求,此请求将会返回文件 `hello.html`。 这里我们在注册 `inert` 插件之后才注册路由,通常明智的做法是在注册插件后才运行依赖于插件的代码,这将在代码运行时可以确保插件已经存在。
-
-通过 `node server.js` 启动你的服务,并在浏览器内访问[`http://localhost:3000/hello`](http://localhost:3000/hello)。哦 糟糕! 因为我们没有创建 `hello.html` 文件所以出错了。下面我们来创建缺失的文件来修正这个问题。
-
-在你的根目录下新建一个名为 `public` 的文件夹,之后在里面创建一个名为 `hello.html` 的文件。 `hello.html` 内代码如下:
-
-```html
-
-
-
-
- Hapi.js is awesome!
-
-
- Hello World.
-
-
-```
-
-这是一份简单并符合规范的 HTML5 文档。
-
-在浏览器内重新载入这个页面,你将会看到标题 "Hello World."。
-
-当你对硬盘上的文件作出修改的时候 [Inert](https://github.com/hapijs/inert) 将会自动重新加载这些文件。 你可以根据自己的喜好调整这个页面。
-
-更多关于静态内容的细节请访问 [静态内容](/tutorials/servingfiles)。这项技术一般用于图片,css样式文件以及静态页面。
-
-### 使用插件
-
-日志对于 Web 开发的重要性毋庸置疑,我们通过 [hapi pino](https://github.com/pinojs/hapi-pino) 插件为应用提供日志服务。
-
-通过npm安装所需要的插件:
-
-```bash
-npm install hapi-pino
-```
-
-之后修改 `server.js` 如下:
-
-```javascript
-'use strict';
-
-const Hapi = require('@hapi/hapi');
-
-const server = Hapi.server({
- port: 3000,
- host: 'localhost'
-});
-
-server.route({
- method: 'GET',
- path: '/',
- handler: (request, h) => {
-
- return 'Hello, world!';
- }
-});
-
-server.route({
- method: 'GET',
- path: '/{name}',
- handler: (request, h) => {
-
- // request.log(['a', 'name'], "Request name");
- // or
- request.logger.info('In handler %s', request.path);
-
- return `Hello, ${encodeURIComponent(request.params.name)}!`;
- }
-});
-
-const init = async () => {
-
- await server.register({
- plugin: require('hapi-pino'),
- options: {
- prettyPrint: false,
- logEvents: ['response', 'onPostStart']
- }
- });
-
- await server.start();
- console.log(`Server running at: ${server.info.uri}`);
+ console.log('服务器运行于 %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
@@ -219,29 +85,16 @@ process.on('unhandledRejection', (err) => {
});
init();
-
```
-当服务器启动的时候你将会看到以下内容:
-
-```sh
-[2017-12-03T17:15:45.114Z] INFO (10412 on box): server started
- created: 1512321345014
- started: 1512321345092
- host: "localhost"
- port: 3000
- protocol: "http"
- id: "box:10412:jar12y2e"
- uri: "http://localhost:3000"
- address: "127.0.0.1"
-```
+将以上代码保存至 `server.js`,执行 `node server.js` 启动服务器。在浏览器中访问 [http://localhost:3000](http://localhost:3000), 可看到 `Hello, world!`。
-之后在浏览器内访问 [http://localhost:3000/](http://localhost:3000/) 我们可以看到请求的信息已经输出到了终端中。
+属性 `method` 为有效的 HTTP 方法或方法数组,又可为 `*`,以通配所有方法。
-日志可以通过 `options` 配置并注册到服务中。
+属性 `path` 为路径,可附参数。其参数可为:可选参数、多段参数、`*` 通配符参数。更多细节,参见 [路由教程](/tutorials/routing)。
-很好! 这只是使用插件的一个简短示例, 更多信息请查看插件教程 [插件教程](/tutorials/plugins)。
+方法 `handler` 处理请求和响应。其要么返回一个普通值或期约(promise),要么抛出错误。
-### 其他
+### 其他
-hapi 拥有许多其他的功能,此教程只记录了其中少数几个功能,你可以在右侧的列表中找到它们。 其余内容请参阅 [API reference](/api)。 欢迎在 [github](https://github.com/hapijs/discuss/issues) 提问,或者在 [slack](https://join.slack.com/t/hapihour/shared_invite/zt-g5ortpsk-ErlnRA2rUcPIWES21oXBOg) 找到我们。
+hapi 功能众多,本例只演示了冰山一角。强烈建议阅览插件教程,从中可以了解更多,以更好组织 hapi 项目。此外,本教程还提供了诸多例子,可在左侧列表查阅。其余内容请参阅 [API reference](/api)。 欢迎在 [github](https://github.com/hapijs/hapi/issues) 提问,或者在 [slack](https://join.slack.com/t/hapihour/shared_invite/zt-g5ortpsk-ErlnRA2rUcPIWES21oXBOg) 找到我们。
diff --git a/static/lib/tutorials/zh_CN/logging.md b/static/lib/tutorials/zh_CN/logging.md
index 43315ab..d8c5a43 100644
--- a/static/lib/tutorials/zh_CN/logging.md
+++ b/static/lib/tutorials/zh_CN/logging.md
@@ -1,30 +1,53 @@
-## 日志
+# 日志
-_该教程适用于 hapi v17版本_
+_本教程适用于 hapi v17 及以上_
-对于软件开发而言,日志的重要性不言而喻。 hapi 内建的日志方法已经基本可以满足日常开发的需要。
+## 总览
-### 内建方法
+服务器日志重要与否,不言而喻。hapi 内置了日志功能。
-在你的应用程序内,你可以使用 [`server.log(tags, [data, [timestamp]])`](/api#-serverlogtags-data-timestamp), 以及[`request.log(tags, [data])`](https://hapijs.com/api#-requestlogtags-data) 这两种日志方法。 你可以通过调用 `request.log()` 记录一个请求的上下文信息。 例如在路由的处理过程中. 记录请求的生命周期扩展以及授权方案。你也可以通过调用 `server.log()` 记录除请求之外的其余信息。 例如服务的启动信息以及插件的 `register()` 方法。
+## 内建方法
-以上两种方法前两个参数都一样,分别是 `tags` 和 `data`。
+有两个几乎相同的日志方法,`server.log(tags, [data, [timestamp]]` 和 `request.log(tags, [data])`,便于随时随地调用。
-`tags` 可以是一个字符串或者字符串数组用于鉴别事件。 你可以把它当作日志等级,或者添加更多可描述的内容。 例如:记录一个从数据库中的读取错误你可以这样写:
+### request.log()
-```javascript
-server.log(['error', 'database', 'read']);
+当需要在请求上下文中记录日志时,调用`request.log()`,例如路由处理函数内、请求生命周期中、鉴别时。该方法参数有二:
+
+`tags`:日志标签,为字符串或字符串数组,例如 `['error', 'database', 'read']`。以标识事件。标签代替日志级别,事件描述更细腻。
+
+`data`:可选,日志数据,为字符串或对象。如果 data 是函数,函数签名是 `function()`,会调用一次,获取返回值,存储数据。
+
+例如:
+
+```js
+server.route({
+ method: 'GET',
+ path: '/',
+ handler: function (request, h) {
+
+ request.log('error', '事件错误');
+ return 'Hello World';
+ }
+});
```
-hapi 中所有生成的日志事件,都会有一个 `hapi` 标签。
+在这个例子中,请求路由 `/`,会触发事件,并记录。此时控制台会输出传入的 `data` 参数,即 `事件错误`。`data` 可自定义。
-第二个参数 `data` 是一个可选的字符串或者对象。 这里你可以传入错误信息,或者其余相关细节。
+### server.log()
-除此之外 `server.log()` 可以接受 `timestamp` 作为第三个参数。 它的默认值是 `Date.now()`, 除非有特殊的原因,我们不建议你覆盖这个默认值。
+`server.log()` 用于没有特定请求的上下文中,例如,服务器启动后、插件的 `register()` 方法中。其参数有三,其中 `tags` 和 `data` 与 `request.log()` 一致。参数 `timestamp` 可选,默认为 `Date.now()`,通常不建议覆盖。
+
+```js
+const Hapi = require('@hapi/hapi');
+const server = Hapi.server({ port: 80 });
+
+server.log(['test', 'error'], '事件测试');
+```
-### 检索以及显示日志
+## 日志检索展示
-hapi 服务器对象为每个日志事件都发出了事件。你可以通过标准的 EventEmitter API 去监听这些事件并做处理。
+hapi 服务器对象会发出所有日志事件。可以通过标准的 EventEmitter 接口去监听处理。
```javascript
@@ -36,9 +59,9 @@ server.events.on('log', (event, tags) => {
});
```
-调用 `server.log()` 时将会发出 `log` 事件,而调用 `request.log()` 时将会发出一个 `request` 事件。
+本例,调用 `server.events.on()` 来监听全部 `log` 事件。包括 `server.log()` 和 `request.log()`。
-你可以通过 `request.logs` 一次性获取到所有日志。 这将返回一个包含所有请求日志事件的数组。想要使用这个功能需要将路由的 `log.collect` 选项设置为 `true`,否则只能得到一个空的数组。
+还可以用 `request.logs` 一次获取该请求所有日志。此方法返回一个数组,包含所有请求日志事件。欲用此功能,需将路由的 `log.collect` 选项设置为 `true`,否则只能返回空数组。
```javascript
server.route({
@@ -51,23 +74,24 @@ server.route({
},
handler: function (request, h) {
- return 'hello';
+ return '你好';
}
});
```
-### Debug 模式 (只针对开发)
+## 调试模式 (只针对开发)
-hapi 拥有 debug 模式, 它可以直接显示所有日志事件到控制台,而不需要额外去配置或者使用一些插件。
+开启调试模式,可直接显示所有日志事件到控制台,不需要额外配置或插件。
-默认情况,debug 模式只会输出应用未捕获的错误、运行时错误以及错误实现hapi API所带来错误等。你可以在服务器配置中配置 debug 模式需要打印哪些 tag 的错误。例如你想将所有请求中的错误信息在 debug 模式中输出,可以如下配置:
+默认情况,调试模式只会输出未捕获错误、运行时错误以及 hapi 调用错误。不过,在服务器配置中配置后,调试模式可以输出标签事件。例如,要获取请求错误信息,可以如下配置:
```javascript
const server = Hapi.server({ debug: { request: ['error'] } });
```
-更多关于 debug 模式的信息请参阅 [API documentation](https://hapijs.com/api#-serveroptionsdebug).
+详情请参阅 [API](/api#server.options.debug)。
-## 日志插件
+## 日志插件
-hapi 内建的日志方法已经基本满足了开发的需求。如果需要更多的日志功能,可以使用如[hapi-pino](https://www.npmjs.com/package/hapi-pino)之类的日志插件。
+hapi 内建的日志方法可基本满足开发需求。如果需要更多的日志功能,可以使用如 [hapi-pino](https://www.npmjs.com/package/hapi-pino)
+[New Relic](https://newrelic.com/instant-observability/hapi/2124da5b-0174-457a-be5a-e0068673b2b5) 之类的[日志插件](/plugins#logging)。
diff --git a/static/lib/tutorials/zh_CN/plugins.md b/static/lib/tutorials/zh_CN/plugins.md
index 2d5f776..c59167f 100644
--- a/static/lib/tutorials/zh_CN/plugins.md
+++ b/static/lib/tutorials/zh_CN/plugins.md
@@ -1,14 +1,16 @@
-## 插件
+# 插件
-_该教程适用于 hapi v17版本_
+_本教程适用于 hapi v17 及以上_
-hapi 拥有一个可扩展并且强健的插件系统,它允许你将应用分割为独立的业务逻辑或者可重用的组件。
+## 总览
-## 创建一个插件
+hapi 插件系统格外强大,可以方便地将应用分割为独立的业务逻辑,或者可重用工具。你可以添加现有插件,或自建插件。
-插件很容易去实现,它的核心是一个拥有 `register` 属性的对象。属性的值是一个拥有 `async function (server, options)` 签名的函数。 除此之外,插件必须拥有 `name` 属性以及多个包括 `version` 在内的可选属性。
+## 创建
-一个简单的插件如下:
+插件很好写,其核心是一个对象,带有 `register` 属性。`register` 属性值是一个函数,其签名为 `async function (server, options)`。 此外,插件必须有 `name` 属性,还有诸多可选属性,如 `version`。
+
+例:
```javascript
'use strict';
@@ -18,7 +20,7 @@ const myPlugin = {
version: '1.0.0',
register: async function (server, options) {
- // 创建一个路由作为示例
+ // 创建路由以为示例
server.route({
method: 'GET',
@@ -29,13 +31,15 @@ const myPlugin = {
}
});
- // etc ...
+ // 诸如此类 ...
await someAsyncMethods();
}
};
```
-当作为一个外部模块时, 你需要指定 `pkg` 属性:
+注册此插件,访问路由 `/test`,显示 `'hello, world'`。
+
+当是外部模块时, 需要指定 `pkg` 属性:
```javascript
'use strict';
@@ -44,7 +48,7 @@ exports.plugin = {
pkg: require('./package.json'),
register: async function (server, options) {
- // 创建一个路由作为示例
+ // 创建路由以为示例
server.route({
method: 'GET',
@@ -55,48 +59,89 @@ exports.plugin = {
}
});
- // etc...
+ // 诸如此类...
await someAsyncMethods();
}
};
```
-请注意在第一个例子中,我们显示的设定了 `name` 和 `version` 属性, 然而在第二个例子中,我们将 package.json 中的内容设置为 `pkg` 属性的值。两种方法都是可以的。
+请注意,第一个例子中,明确了属性 `name` 和 `version`。在第二个例子中,将 package.json 中的内容赋给属性 `pkg`。两种方法都行。
+
+当编写为模块, 插件可以当作默认模块导出,如 `module.exports = { register, name, version }`。如果希望你导出多个插件,可以 `exports.plugin = { register, name, version }`。
-当作为模块编写时, 插件可以作为模块被导出,如 `module.exports = { register, name, version }` 。如果你希望你的模块以多个hapi插件导出时,可以 `exports.plugin = { register, name, version }`。
+此外,当插件属性 `multiple` 设为 `true`,表明当前服务器内,该插件可以重复注册。
-除此之外,当一个插件的 `multiple` 属性设置为 `true`的时候,这表明一个插件可以被重复注册多次。
+另一个可用属性是 `once`,当设置为 `true`,仅注册一次,hapi 会忽略之后的注册,且也不会抛出错误。
-另外一个可用的属性是`once`,当它设置为 `true` 时意味着 hapi 将会忽略掉之后的注册,并且也不会抛出错误。
+### 注册方法
-### 注册方法
+综上所述,方法 `register` 参数有二:`server`、`options`。
-综上所述, `register` 方法接受两个参数, `server` 和 `options`。
+`register` 是个异步函数,插件注册完成即返回。函数内应当处理注册错误。
-`options` 参数只是用户在调用 `server.register(plugin, options)` 时传递给插件的选项内容。不做任何更改,这个对象将直接传递给 `register` 方法。
+参数 `server` 为当前服务器对象。
-`register` 应为异步函数,一旦你的插件完成了注册的所有步骤后它将被返回。 另外当注册插件出现异常时,需要抛出一个错误。
+参数 `options` 为插件配置对项。在调用 `server.register(plugins, [options])` 时传递给插件。该对象直接传递给的 `register` 方法。例:
-`server` 对象是需要载入你的插件的 `server` 对象的引用。
+```javascript
+'use strict';
-## 载入一个插件
+exports.plugin = {
+ pkg: require('./package.json'),
+ register: async function (server, options) {
-插件可以一次载入一个,也可以通过方法 `server.register()` 分组载入, 例如:
+ // 创建路由以为示例
+
+ server.route({
+ method: 'GET',
+ path: '/test',
+ handler: function (request, h) {
+
+ const name = options.name;
+ return `你好 ${name}`;
+ }
+ });
+
+ // 诸如此类...
+ await someAsyncMethods();
+ }
+};
+```
+
+此处, `options.name` 获取名字。而后用这个名字来返回消息给用户。来看看插件注册时:
+
+```javascript
+const start = async function () {
+
+ await server.register({
+ plugin: require('myplugin'),
+ options: {
+ name: '张三'
+ }
+ });
+};
+```
+
+注册插件时,传递选项可以用 `server.register(plugins, [options])`。这里把 `{name: "张三" }` 传给插件,如上述,创建插件时,可以用 `options` 对象来获取。
+
+## 插件加载
+
+插件可以单独载入,也可以按数组载入,用到方法 `server.register()`。例如:
```javascript
const start = async function () {
- // 载入一个插件
+ // 载入单插件
await server.register(require('myplugin'));
- // 载入多个插件
+ // 载入多插件
await server.register([require('myplugin'), require('yourplugin')]);
};
```
-要将选项传递给插件时,我们将传递一个拥有 `plugin` 和 `options` 作为键的对象, 例如:
+要给插件传选项,改用对象注册,属性有 `plugin` 和 `options`,例如:
```javascript
const start = async function () {
@@ -104,13 +149,13 @@ const start = async function () {
await server.register({
plugin: require('myplugin'),
options: {
- message: 'hello'
+ message: '你好'
}
});
};
```
-这些对象也可以以数组的方式传入:
+数组对象方式:
```javascript
const start = async function () {
@@ -125,13 +170,13 @@ const start = async function () {
};
```
-### 注册选项
+### 注册选项
-你也可以传递第二个可选参数到 `server.register()`。 关于这个对象的文档可以在 [API reference](/api#-await-serverregisterplugins-options) 中找到。
+`server.register()` 第二个参数,可选。参见 [API](/api#server.register() 。
-options 对象将被 hapi 使用,并且 *不会* 传递到被装载的插件中。它可以将`vhost`或`prefix`修饰符添加到使用插件的路由上。
+该选项 hapi 自用,**不会** 传给插件。比如将 `vhost` 或 `prefix` 属性添加到使用插件的路由上。
-例如我们有这样一个插件:
+例:
```javascript
'use strict';
@@ -155,11 +200,13 @@ exports.plugin = {
};
```
-通常来说, 当插件载入的时候将会创建一个 `GET` 路由在 `/test` 路径上。 我们可以通过添加 `prefix` 的值来修改任意使用这个插件生成的路由。
+通常,加载此插件,会创建 `GET` 路由 `/test`。添加选项 `prefix` 来修改这个插件生成的路由。
```javascript
const start = async function () {
+ const server = Hapi.server();
+
await server.register(require('myplugin'), {
routes: {
prefix: '/plugins'
@@ -168,6 +215,6 @@ const start = async function () {
};
```
-现在当插件被载入时, 因为修改了 `prefix` ,`GET` 路由将注册为 `/plugins/test`.
+现在,因为修改了 `prefix`,加载插件,`GET` 路由注册为 `/plugins/test`.
-与之类似,通过修改 `options.routes.vhost` 属性,将为所有使用插件创建的路由分配默认的 `vhost`配置。 更多关于 `vhost` 的配置信息可以在这里找到 [API reference](/api#-serverrouteroute).
+与之类似,修改 `options.routes.vhost` 属性,将为所有使用插件创建的路由分配默认的 `vhost` 配置。更多关于 `vhost` 的配置信息参见[API](/api#server.route())。
diff --git a/static/lib/tutorials/zh_CN/routing.md b/static/lib/tutorials/zh_CN/routing.md
index ecc2878..4370ebe 100644
--- a/static/lib/tutorials/zh_CN/routing.md
+++ b/static/lib/tutorials/zh_CN/routing.md
@@ -1,8 +1,10 @@
-## 路由
+# 路由
-_该教程适用于 hapi v17版本_
+_本教程适用于 hapi v17 及以上_
-在 hapi 定义路由与其他框架类似,你需要指定三个基本的元素: method, path 以及 handler。 他们将作为一个对象传入到你的服务器对象中,如下:
+## 总览
+
+hapi 路由三要素: method、path、handler。将其组合成对象传给服务器,如下:
```javascript
server.route({
@@ -10,14 +12,14 @@ server.route({
path: '/',
handler: function (request, h) {
- return 'Hello!';
+ return 'Hello World!';
}
});
```
-## 方法
+## 方法(Methods)
-上面的代码将对 `/` 路径下的 `GET` 请求作出响应,并返回一个 `Hello!` 字符串。 method 可以接收任何有效的 HTTP 方法或者方法数组。当你希望响应 `PUT` 或`POST` 请求时,可以做如下修改:
+上面代码,发送 `GET` 请求到路由 `/`,返回 `Hello World!`。 `method` 是有效的 HTTP 方法或者方法数组。若需要发送 `PUT` 或`POST` 请求,修改如下:
```javascript
server.route({
@@ -30,9 +32,9 @@ server.route({
});
```
-## 路径
+## 路径(Path)
-path 参数必须为一个字符串, 虽然他们可以包含一个命名的参数。 如果需要对路径中的参数进行命名,需要使用 `{}` 包围起来,例如:
+选项 `path` 必须为字符串。同时可包含命名参数。需命名参数,用 `{}` 括住,例如:
```javascript
server.route({
@@ -40,16 +42,22 @@ server.route({
path: '/hello/{user}',
handler: function (request, h) {
- return `Hello ${encodeURIComponent(request.params.user)}!`;
+ return `Hello ${request.params.user}!`;
}
});
```
-如上所示,在路径中我们使用了字符串 `{user}`。 着意味着路径中这段位置的内容将赋值于一个命名的参数,这个参数将在这个 handler 内位于 `request.params` 对象中。 我们现在可以通过 `request.params.user` 访问 user 的值, 在 URI 编码后,这也可以防范内容注入攻击。
+注:用户输入,如查询参数,最好先行转义或确认,可避免 XSS 攻击。可以用 [Hoek](/module/hoek) 的 `escapeHtml()` 方法:
+
+```js
+return `Hello ${Hoek.escapeHtml(request.params.user)}!`
+```
+
+如上所示,在路径中用到了字符串 `{user}`,此时会生成明明参数。这个参数可 `request.params` 对象中获取,即,`request.params.user`,而后,再 URI 编码,这可以防范内容注入攻击。访问路由 `/hello/john`,返回 `Hello john!`。
-### 可选参数
+## 可选命名参数
-在上面的示例中,user 参数是必须要有的: 一个请求如 `/hello/bob` 或 `/hello/susan` 将会正常工作。 但是一个请求如 `/hello` 就不能工作了。可以通过在参数的最后添加一个问号使得参数变为可选的。以下代码为在同样的路由下使用可选参数:
+上面示例中,`user` 参数必传,比如请求 `/hello/bob` 或 `/hello/susan`。但不能请求如 `/hello` 这样的。欲使命名参数可选,只需在其后添加问号。同上边路由,将参数可选:
```javascript
server.route({
@@ -57,71 +65,231 @@ server.route({
path: '/hello/{user?}',
handler: function (request, h) {
- const user = request.params.user ?
- encodeURIComponent(request.params.user) :
- 'stranger';
+ const user = request.params.user ? request.params.user : 'stranger';
return `Hello ${user}!`;
}
});
```
-现在一个请求发往 `/hello/mary` 将会返回 `Hello mary!` 如果只是发给 `/hello` 将会返回 `Hello stranger!`。 这里需要注意的是在路径中只有 *最后* 一个命名的参数可以为可选的。 这意味着 `/{one?}/{two}/` 是不合法的路径, 因为在可选参数后有另一个命名的参数。你可以针对路径内容的一部分进行命名,例如 `/{filename}.jpg` 是合法的。你可以指定多个命名参数,但是请注意需要用非参的分隔符进行分割。 这意味着 `/{filename}.{ext}` 是合法的而 `/{filename}{ext}` 不是。
+现在请求 `/hello/mary`,会返回 `Hello mary!`,而请求 `/hello` 则返回 `Hello stranger!`。需注意,在路径中只有 **最后** 一个命名参数可以为可选的。换言之,路径 `/{one?}/{two}/` 无效,因为在可选参数后有另一个命名参数。命名参数也可以是路径的一部分,例如 `/{filename}.jpg`。也可以每一段指定多个命名参数,但相邻命名参数间要有分隔符。比如 `/{filename}.{ext}` 有效,而 `/{filename}{ext}` 无效。
-### 多段参数
+## 多段命名参数
-除了可选的路径参数,参数也可以匹配多段内容,可以通过 * 或数字来完成。例如:
+命名参数不仅可选,还可以让其匹配多段内容。加以星号和数字即可。例如:
-```javascript
+```js
server.route({
method: 'GET',
path: '/hello/{user*2}',
handler: function (request, h) {
const userParts = request.params.user.split('/');
- return `Hello ${encodeURIComponent(userParts[0])} ${encodeURIComponent(userParts[1])}!`;
+
+ return `Hello ${userParts[0]} ${userParts[1]}!`;
}
});
```
-通过这个配置, 请求 `/hello/john/doe` 将会返回字符串 `Hello john doe!`。重点要注意的是,这里的参数实际上是字符串 `"john/doe"`. 所以需要通过 `split` 分割来获取里面的内容。 * 后面的数字表示有多少段内容将会被赋予参数。 你也可以完全省略这个数字,该参数将会匹配任意数量的可用段。 正如同可选参数一样, 通配参数 (如 `/{files*}`) *只会* 只能出现在路径的最后。
+此时, 请求 `/hello/john/doe`,会返回字符串 `Hello john doe!`。注意看,这里参数是字符串 `"john/doe"`. 所以要用 `split` 分割来获取其内容。星号后的数字表示应截取几段路径,分配给命名。数字也可以省略,此时匹配后面所有路径。带通配符参数(如 `/{files*}`)只能在路径最后,这点与可选参数一样。
-对于处理请求时,越是具体的路径参数,hapi将越优先使用。这意味着当你拥有两个路由时,一个路径为 `/filename.jpg` 另一个路径为 `/filename.{ext}`。当一个请求访问 `/filename.jpg` 将会匹配到第一个而不是第二个。 这意味着一个路由的路径参数为 `/{files*}`时,将会是 *最后一个* 使用的路由,而且只会在前面的路由都无法匹配才会这样。
+处理请求时,路径越具体,越优先匹配。比如,有路径 `/filename.jpg` 和 `/filename.{ext}`。当请求 `/filename.jpg`,会匹配到第一个。也就是说,路径参数为 `/{files*}` 时,只有所有路由匹配失败,才会走此路有。
-## Handler 方法
+## 查询参数(Query Parameters)
-Handler 是一个接收两个参数的函数, `request` 和 `h`。
+查询参数很常见。在 URL 中以 `key=value` 格式传送。如:
-`request` 是用户请求对象的具体信息,例如参数、请求内容数据, 授权信息以及http头部信息等。 关于 `request` 对象所包含的具体信息的文档可以在这里查阅 [API reference](/api#request-properties)。
+`localhost:3000?name=panda&location=chengdu`
-第二个参数 `h` 是响应处理对象,它包含了一些处理请求的方法。 如同你之前看到的, 如果你需要一些返回值, 你可以直接通过 handler 返回。 返回的内容可以是字符串,buffer,JSON 序列化对象, stream 或者 promise。
+此处有两个查询参数,`name=panda`,`location=chengdu`。在 hapi中,可以用 `request.query` 对象获取查询参数。
-另外你也可以将上述的返回值传递给 `h.response(value)` 来返回。 这个方法调用的返回值是一个响应对象, 可以在响应发出前通过链式调用增加额外的方法。例如 `h.response('created').code(201)` 将会返回 `created` 并且 HTTP 状态码为 `201`。 你也可以设置响应头、内容类型、内容长度、重定向请求和文档中记录的其余类型 [API reference](/api#response-toolkit)。
+```js
+server.route({
+ method: 'GET',
+ path: '/',
+ handler: function (request, h) {
-## 配置
+ return `Hello ${request.query.name}!`;
+ }
+});
+```
-除以上基本的三个要素之外, 你也可以为每一个路由指定 `options` 参数。 这如同你在 [数据验证](/tutorials/validation),[身份认证](/tutorials/auth)章节中的配置一样。包括预处理, 内容处理以及缓存配置。更多细节内容请参阅 [API reference](/api#route-options)。
+上述代码,获取查询参数 `name`,并返回 `Hello panda!`。
-以下代码是为路由添加文档的示例:
+如果查询结构复杂,可用 `qs` 模块。思考如下代码:
-```javascript
+```js
server.route({
method: 'GET',
- path: '/hello/{user?}',
+ path: '/',
handler: function (request, h) {
- const user = request.params.user ?
- encodeURIComponent(request.params.user) :
- 'stranger';
+ return request.query;
+ }
+});
+```
+
+请求 `localhost:3000?foo[bar]=baz`,hapi 默认会返回 `{ "foo[bar]"。"baz" }`。
- return `Hello ${user}!`;
+要正确解析,请用 [qs](https://github.com/ljharb/qs) 模块。例:
+
+```js
+const Hapi = require('@hapi/hapi');
+const Qs = require('qs');
+
+const server = Hapi.server({
+ port: 3000,
+ host: 'localhost',
+ query: {
+ parser: (query) => Qs.parse(query)
+ }
+});
+
+server.route({
+ method: 'GET',
+ path: '/',
+ handler: function (request, h) {
+
+ return request.query;
+ }
+});
+
+const init = async () => {
+
+ await server.start();
+ console.log('服务器运行于 %s', server.info.uri);
+};
+
+process.on('unhandledRejection', (err) => {
+
+ console.log(err);
+ process.exit(1);
+});
+
+init();
+```
+
+首先,导入 [qs](https://github.com/ljharb/qs)。
+
+而后,设置 `server.options.query.parser` 值,配置查询参数解析方法。本例使用 `Qs.parse()`,其中 `query` 即 `request.query`。现在,所有 `request.query` 都会用 `Qs.parse()` 解析一遍。
+
+最后,返回解析后的查询字符串:
+
+```
+{
+ "foo": {
+ "bar": "baz"
+ }
+}
+```
+
+注:上面例子中,用到了 [qs](https://github.com/ljharb/qs) 模块,其实别的解析器也行,或`npm` 上找,或自定义。只要注意方法必须返回一个对象,每个键是一个参数,匹配的值是参数值。
+
+## 请求负载(Request Payload)
+
+接口的请求数据,可以从路由处理程序中的 `request.payload` 处获得。如下:
+
+```js
+server.route({
+ method: 'POST',
+ path: '/signup',
+ handler: function (request, h) {
+
+ const payload = request.payload;
+
+ return `Welcome ${payload.username}!`;
+ }
+});
+```
+
+在上面的例子中,用 `request.payload` 获取数据。比如获取用户注册数据:
+
+`{ username: 'panda', password: 'bamboo' }`
+
+注:负载建议提前确认,以确保安全。更多信息请参见确认教程。
+
+## 路由处理方法(Handler)
+
+路由处理方法(Handler)参数有二:`request`、`h`。
+
+参数 `request` 是个对象,包含用户请求详情,例如路由[参数](#parameters)、[请求负载](#requestpayload), [查询参数](#query)、鉴别信息、请求头信息等。关于 `request` 对象具体信息请查阅[相关接口](/api#request-properties)。
+
+第二个参数 `h` 是响应工具包,便于处理响应。如同前述,假若需要响应请求, 可直接在 `handler` 内返回之。其内容可以是字符串、缓冲区(buffer)、JSON 对象, 流(stream)、期约(promise)。
+
+另外,上述返回值,也可以传递给 `h.response(value)`,此方法调用的返回值是一个响应对象, 可以在响应发出前,链式调用以改变响应。例如 `h.response('created').code(201)`,这句会返回 `created`,并改 HTTP 状态码为 `201`。在其中,还可以设置响应头(headers)、内容类型(content type)、内容长度(content length)、重定向等等。更多内容,参见[接口文档](/api#response-toolkit)。
+
+需要强调,方法 `handler` 必须有返回值,期约(promise),错误(error)皆可。
+
+注:处理方法(handle)写成箭头函数,因箭头函数没有自己的 `this`,无法获取 `server.bind()` 属性。但,绑定的上下文可在[`h.context`](/api#h.context) 内访问。
+
+## 选项(Options)
+
+上述三个要素之外, 路由也可指定 `options` 参数。此参数可配置 [数据确认](/tutorials/validation),[鉴别](/tutorials/auth)、预处理、负载处理、缓存配置登。详情请参阅[相关文档](/api#route-options)。
+
+以下实例,为路由添加确认:
+
+```javascript
+server.route({
+ method: 'POST',
+ path: '/signup',
+ handler: function (request, h) {
+
+ const payload = request.payload;
+
+ return `Welcome ${payload.username}!`;
},
options: {
- description: 'Say hello!',
- notes: 'The user parameter defaults to \'stranger\' if unspecified',
- tags: ['api', 'greeting']
+ auth: false,
+ validate: {
+ payload: {
+ username: Joi.string().min(1).max(20),
+ password: Joi.string().min(7)
+ }
+ }
}
});
```
-从功能上讲,除非你使用了一些插件如 [lout](https://github.com/hapijs/lout) 去生成你的 API 文档,否则这些选项并不会生效。 这些元数据将会被赋予路由,你可以在之后的处理过程中使用它们。
+`options` 下的第一个属性是 `auth`。`auth` 用来设置路由鉴别。因这是注册路由,故而禁用鉴别。
+
+第二个属性是 `validate`。此处配置各种确认规则,如确认 `请求头(headers)`、`参数(params)`、`负载(payload)`,还可配置确认失败处理方法:`failAction`。欲确认 `request.payload`,可使用[joi](https://joi.dev)。详情请查看确认教程。
+
+## 处理 404(404 Handling)
+
+发请求时,服务器找不到资源,会返回 404 错误。建议以适当的方式处理这些错误。这在 hapi 中很容易处理,只需配置一个路由,就可以捕捉一些未匹配路由。如何配置路由来返回自定义的 `404` 响应,请看例子:
+
+```js
+'use strict';
+
+const Hapi = require('@hapi/hapi');
+
+const internals = {};
+
+const init = async () => {
+
+ const server = Hapi.server({
+ port: 3000,
+ host: 'localhost'
+ });
+
+ server.route({
+ method: '*',
+ path: '/{any*}',
+ handler: function (request, h) {
+
+ return '错误 404!未找到!';
+ }
+ });
+
+ await server.start();
+ console.log('服务器启动用 %s', server.info.uri);
+};
+
+init();
+```
+
+首先,配置服务器。
+
+接着,配置路由,返回自定义 404 响应。此处用通配符 `*`,来匹配所有请求方法。而后,路径写成 `'/{any*}`,广泛且通用。hapi 路由匹配规则是,从具体到模糊,直到匹配到本路由。例如,`localhost:3000/login`将进入 `/login` 路由,而不是`/{any*}`路由。
+
+最后,在处理函数中返回自定义的 404 响应,以让用户知道所请求资源没找到。
diff --git a/static/lib/tutorials/zh_CN/servermethods.md b/static/lib/tutorials/zh_CN/servermethods.md
index 2aedbca..262c99a 100644
--- a/static/lib/tutorials/zh_CN/servermethods.md
+++ b/static/lib/tutorials/zh_CN/servermethods.md
@@ -1,8 +1,14 @@
-## 服务器方法
+# 服务器方法(Server Methods)
-_该教程适用于 hapi v17版本_
+_本教程适用于 hapi v17 及以上_
-服务器方法是一种非常有用的函数共享方式,与引入公用的模块不同,它只需附加在服务器对象上。注册一个服务器方法,你只需调用 [`server.method()`](https://hapijs.com/api#server.method())。 有两种方式调用这个函数,你可以通过方法 `server.method(name, method, [options])` 调用, 如:
+## 总览
+
+服务器方法非常有用,其本身附加在服务器对象上,无需额外的公共模块,便于功能共享。服务器方法也多用于缓存,因其利用了 hapi 的本地缓存,可以将样板代码减到最少。要注册服务器,只需调用 [`server.method()`](/api#server.method()) 即可。调用方式有二:`server.method(name, method, [options])`、`server.method(method)`。其中,参数 `method` 是个对象或数组对象,其对象属性为:`name`、`method`、`options`。
+
+## server.method()
+
+调用 `server.method()` 第一种方法是使用 `server.method(name, method, [options])`:
```javascript
const add = function (x, y) {
@@ -13,7 +19,9 @@ const add = function (x, y) {
server.method('add', add, {});
```
-又或者你可以通过方法 `server.method(method)`调用, 这里 `method` 是一个拥有 `name`, `method` 以及 `options` 参数的对象 (注意你也可以传递以上对象的数组):
+此处,创建函数 `add`,其接收两个参数,并相加之。然后调用 `server.method()`,方法的名是 `add`,欲注册的方法刚创建的函数 `add`,不带选项。
+
+调用 `server.method()` 第二种方式,使用 `server.method(method)`:
```javascript
const add = function (x, y) {
@@ -28,19 +36,21 @@ server.method({
});
```
-### 名称
+再次声明相同函数,即 `add`。这次注册,配置 `method` 对象。其中,`name` 是方法名,`method` 是要注册的方法,`options` 是各种配置。
-`name` 参数是一个字符串,用于之后在服务器中通过 `server.methods[name]` 访问这个方法。注意如果你指定了一个 `name` 含有一个 `.` 字符, 它注册了一个内嵌对象,而不是一个字符串字面值,如:
+### 名称(Name)
+
+`name` 参数是个字符串,用于之后在服务器中通过 `server.methods[name]` 访问这个方法。注意,如果 `name` 含有字符 `.`, 会注册一个内嵌对象,而不是字符串字面量,如:
```javascript
server.method('math.add', add);
```
-这个服务器方法可以通过 `server.methods.math.add()` 来调用。
+这个服务器方法就要用 `server.methods.math.add()` 来调用。
-### 函数
+### 方法(Method)
-`method` 参数是当服务器方法被执行时实际调用的函数,它可以携带任意数量的参数,也可以是一个 `async` 函数,例如:
+参数 `method` 是当服务器方法被执行时,实际调用的函数,其参数任意,也可以是一个 `async` 函数,例如:
```js
const add = async function (x, y) {
@@ -52,11 +62,15 @@ const add = async function (x, y) {
server.method('add', add, {});
```
-你的服务器方法函数应该返回一个有效的结果,又或者当异常发生时抛出一个错误。
+服务器方法函数应当有返回值,又或抛出错误。
+
+## 选项(Options)
+
+注册 `server.method()` 时,可以配置三个 `选项(options)`:`cache`、`generateKey`、`bind`。
-## 缓存
+### 缓存(Cache)
-服务器方法的一个主要优点是它们可以利用 hapi 的本机缓存。默认情况下不缓存,但是如果在注册方法时传递了相应的配置,则返回值将会被缓存。返回的结果将从缓存中获取这个值,而不是每次调用时重新运行方法。配置如下所示:
+服务器方法有个主要优势,是可以利用 hapi 的本地缓存。默认情况下不缓存,但如果注册方法时配置了,则方法返回值将会缓存之。之后的返回结果从缓存中获取,而不用每次重新调用该方法。配置如下所示:
```javascript
server.method('add', add, {
@@ -70,26 +84,26 @@ server.method('add', add, {
});
```
-参数可以是:
+参数:
-* `expiresIn`: 以项目保存在缓存中的时间起,超过这个时间将失效。其中时间以毫秒数表示,不能与 `expiresAt`一同使用。
-* `expiresAt`: 使用以 'HH:MM' 格式以24小时为指定时间, 这个时间之后路由所有的缓存记录都将失效。这个时间使用本地时间,不能与 `expiresIn` 一同使用。
-* `staleIn`: 缓存失效时,尝试重新生成它毫秒数,必须小于 `expiresIn`。
-* `staleTimeout`: 在 generateFunc 生成新值时返回之前,可以允许返回过期值的毫秒数。
-* `generateTimeout`: 当返回时间太长时,如返回超时错误之前,等待的毫秒数。但当这个值最终被返回时,它将储存在缓存中以便将来的请求使用。
-* `segment`: 用于隔离缓存项的可选分段名称。
-* `cache`: 一个可选字符串,其中包含要使用的服务器上配置的缓存连接的名称。
+* `expiresIn`:过期时间,单位毫秒。不能与 `expiresAt` 一同使用。
+* `expiresAt`:过期时间,24 小时制,'HH:MM' 格式, 过了这个时间,路由所有缓存记录失效。这个时间使用本地时间,不能与 `expiresIn` 一同使用。
+* `staleIn`:过了这个时间,缓存标记为失效,并尝试重新生成。必须小于 `expiresIn`。单位毫秒。
+* `staleTimeout`:`staleIn` 超时后,调用 `generateFunc` 生成新值,若生成时间超过此时间,则返回被标记为过期的旧值。单位毫秒。
+* `generateTimeout`: 当生成新值时间超过此时间,则抛出超时异常。但当随后新值最仍会获取并缓存。单位毫秒。
+* `segment`: 用于隔离缓存项的分段名称。
+* `cache`: 服务器上配置的缓存连接名。
-更多关于缓存的选项的信息可以通过 [API Reference](/api#servermethodmethod) 以及 [catbox](https://github.com/hapijs/catbox#policy) 中找到。
+更多关于缓存选项的信息可以查阅[相关接口](/api#server.methods),以及 [catbox](/module/catbox#policy)。
-通过设置 `ttl` 标记,可以修改每次调用服务器方法结果的 `ttl` (生存时间)。我们来看一下如何让它与之前的例子工作:
+还可以设置 `ttl` 标记,修改每次调用服务器方法结果的 `ttl` (生存时间)。例:
```js
const add = async function (x, y, flags) {
const result = await someLongRunningFunction(x, y);
- flags.ttl = 5 * 60 * 1000; // 5 mins
+ flags.ttl = 5 * 60 * 1000; // 5 分钟
return result;
};
@@ -100,15 +114,13 @@ server.method('add', add, {
generateTimeout: 100
}
});
-
-server.methods.add(5, 12);
```
-这里,我们定义了我们的服务器方法函数,这里多传递了一个参数,这个额外的 `flags` 参数由 hapi 提供。然后,只需将 `ttl` 标记的值设置为我们希望结果被缓存的时间 (以毫秒为单位)即可。 如果这个值被设为 `0` 那么返回的结果将永远不会被缓存。如果我们没有设置,那么 `ttl` 将从缓存的配置中获取。
+此处,定义了服务器方法函数,比之前多传了个参数,这个参数 `flags` 由 hapi 提供。而后,只需更改 `ttl` 即可,单位毫秒。假若将其设为 `0`,则永不缓存。假若没有更改,则 `ttl` 从缓存的配置中获取。
-### 自定义缓存键
+### 自定义缓存键
-除上述选项外,你还可以指定一个自定义函数,用于根据方法的参数来生成缓存的键。如果你的方法参数是由字符串、数字和布尔值构成的组合,那么 hapi 将会为你生成适用的键。但是,如果你的方法接受对象参数,则你需要指定一个如下类似用于生产键的函数:
+除上述选项,还可以写个方法,赋给 `generateKey`,来生成缓存键。如果绑定方法的参数是字符串、数字、布尔值的组合,hapi 会自动生成合适的键。但是,如果方法接受对象参数,则需要指定 `generateKey`,来明确按何种规则生成键:
```javascript
const sum = function (array) {
@@ -128,11 +140,11 @@ server.method('sum', sum, {
});
```
-这样传递给方法的任何参数都可用于 `generateKey` 方法。
+这样,传递给方法参数都会传给 `generateKey`。
-### 绑定
+### 绑定(Bind)
-服务器方法可用的最后一个选项是 `bind`。`bind` 选项可以修改方法中的 `this` 上下文。添加方法时,它默认为当前上下文。这对于传入数据库数据库客户端非常有用,因为无需将其作为参数传递,并且不用自定义 `generateKey` 函数,如下:
+服务器方法上还有一个配置项是 `bind`。`bind` 用以修改方法中的 `this` 上下文。注册方法时,默认为当前上下文。比如可以数据库上下文,就无需将其当作参数传递,而且不用自定义 `generateKey` 函数,如下:
```javascript
const lookup = async function (id) {
@@ -144,3 +156,26 @@ const lookup = async function (id) {
server.method('lookup', lookup, { bind: myDB });
```
+
+## server.methods
+
+要调用上面注册的服务器方法,可以使用`server.methods()`。比如:
+
+
+```js
+// 定义
+const add = function (x, y) {
+
+ return x + y;
+};
+
+// 绑定
+server.method({
+ name: 'add',
+ method: add,
+ options: {}
+});
+
+// 调用
+server.methods.add(1, 2); // 3
+```
diff --git a/static/lib/tutorials/zh_CN/servingfiles.md b/static/lib/tutorials/zh_CN/servingfiles.md
index 89fcd63..6a72766 100644
--- a/static/lib/tutorials/zh_CN/servingfiles.md
+++ b/static/lib/tutorials/zh_CN/servingfiles.md
@@ -1,20 +1,40 @@
-## 静态内容
+# 静态内容
-_该教程适用于 hapi v17版本_
+_本教程适用于 hapi v17 及以上_
-在构建任意一个 web 应用时,不可避免的需要从磁盘提供一些静态文件。有一个名为 [inert](https://github.com/hapijs/inert) 的 hapi 插件可以完成这个操作。
+## 总览
-首先你需要安装它,并且将 inert 作为依赖添加到你的项目中:
+构建网络应用时,经常要从磁盘获取静态文件。处理这些静态文件,hapi 插件可以胜任,比如 [inert](/module/inert)。
-`npm install --save @hapi/inert`
+首先安装 inert,并导入:
-## `h.file(path, [options])`
+`npm install @hapi/inert`
-首先我们看如何使用 [`h.file()`](https://github.com/hapijs/inert#hfilepath-options) 方法:
+## Inert
+
+插件 `inert` 有一些处理方法,为静态文件和目录提供服务,并在工具包(`h`)中增加了方法 `h.file()`,其可以响应文件资源。
+
+## 相对路径
+
+可以配置文件相对路径,来简化后续操作:
```javascript
+'use strict';
+
+const Hapi = require('@hapi/hapi');
+const Path = require('path');
+
const start = async () => {
+ const server = Hapi.server({
+ routes: {
+ files: {
+ // 配置相对路径
+ relativeTo: Path.join(__dirname, 'public')
+ }
+ }
+ });
+
await server.register(require('@hapi/inert'));
server.route({
@@ -22,40 +42,28 @@ const start = async () => {
path: '/picture.jpg',
handler: function (request, h) {
- return h.file('/path/to/picture.jpg');
+ return h.file('picture.jpg');
}
});
await server.start();
- console.log('Server running at:', server.info.uri);
+ console.log('服务器运行于:', server.info.uri);
};
start();
```
-如上所示,这就是你返回 `h.file(path)` 的基本方式。
+如上所述,在`server.options.routes` 配置好后,相对路径会应用于**全部**路由。当然,也可以在某条路由上单独配置。
-### 相对路径
+## `h.file(path, [options])`
-为了简化操作,特别是如果有多个需要返回文件的路由,你可以在服务器中配置基本路径,之后只将相对路径传递给 `h.file()`:
```javascript
-'use strict';
-
-const Hapi = require('@hapi/hapi');
-const Path = require('path');
-
-const server = Hapi.server({
- routes: {
- files: {
- relativeTo: Path.join(__dirname, 'public')
- }
- }
-});
-
const start = async () => {
+ const server = Hapi.server();
+
await server.register(require('@hapi/inert'));
server.route({
@@ -63,23 +71,23 @@ const start = async () => {
path: '/picture.jpg',
handler: function (request, h) {
- return h.file('picture.jpg');
+ return h.file('/path/to/picture.jpg');
}
});
await server.start();
- console.log('Server running at:', server.info.uri);
+ console.log('服务器运行于:', server.info.uri);
};
start();
```
-当在 `server.options.routes` 中设置一个选项时,如上所述,它将应用于 _所有的_ 路由。你还可以修改这些选项,包括每个路由级别的 `relativeTo` 选项。
+注册了 `inert` 插件,就可以访问 `h.file()` 方法。在这里,`h.file()` 要返回的图片路径是 `'/path/to/picture.jpg`。
-## 文件 handler
+## 文件处理(File handler)
-上述路由的替代方法是使用 `file` handler:
+另一种方法在 `handler` 里配置属性 `file` :
```javascript
server.route({
@@ -91,9 +99,9 @@ server.route({
});
```
-### 文件 handler 选项
+### 文件选项
-我们还可以将参数指定为接受 `request` 对象,并返回表示文件路径字符串的函数 (绝对路径或者相对路径):
+`file` 可以为方法,其接收 `request` 对象,并返回文件路径字符串:
```javascript
server.route({
@@ -107,7 +115,7 @@ server.route({
});
```
-它也可以是具有 `path` 属性的对象。当在 handler 中使用对象时,我们可以附加一些别的东西,如设 `Content-Disposition` 头并允许压缩文件。如下:
+也可以是对象,有 `path` 属性。用对象可以设置 `Content-Disposition` 并压缩文件。如下:
```javascript
server.route({
@@ -116,17 +124,17 @@ server.route({
handler: {
file: {
path: 'script.js',
- filename: 'client.js', // 修改 Content-Disposition 头中的文件名
+ filename: 'client.js', // 覆盖 Content-Disposition 头中的文件名
mode: 'attachment', // 指定 Content-Disposition 是一个附件
- lookupCompressed: true // 如果请求允许,将允许查找 script.js.gz
+ lookupCompressed: true // 如果请求允许,查找 script.js.gz
}
}
});
```
-## 目录 handler
+## 目录处理
-除 `file` handler 之外,inert 也有一个额外的 `directory` handler 允许一个路由提供多份文件。要使用它,必须使用参数指定路径。 参数的名称无关紧要,你也可以在参数上使用星号扩展名来限制文件的深度。目录 handler 的基本用法如下:
+inert 除了处理文件,也可以配置属性 `directory` 以处理目录。要使其生效,必须指定路径。路由地址无关紧要,也可以使用星号。基本用法如下:
```javascript
server.route({
@@ -140,9 +148,9 @@ server.route({
});
```
-### 目录 handler 选项
+### 目录选项
-上述路由将通过在 `public` 目录中查找匹配的文件名来响应任何请求。这里需要注意的是,在此配置中对 `/` 的请求将会使用 HTTP `403` 响应进行回复。我们可以通过添加索引文件的方式来解决这个问题。默认情况下,hapi 会在目录中搜索名为 `index.html` 的文件。我们可以通过将 index 选项设置为 `false` 来禁用提供索引文件,或者我们可以指定 inert 可以查找的索引文件数组:
+上述路由可以匹配所有请求,并在 `public` 目录内寻找文件。这里需要注意,如果请求对 `/`,用这个配置会响应 HTTP `403`。要解决此问题,就要添加索引文件。默认,hapi 会在目录中搜索文件 `index.html`。设置选项 index 选项为 `false`,可禁止提供索引文件,或者指定索引文件数组:
```javascript
server.route({
@@ -157,7 +165,7 @@ server.route({
});
```
-一个发往 `/` 的请求首先尝试去加载 `/index.html`, 之后再加载 `/default.html`。当没有可用的索引文件时, inert 可以以列表的形式显示这个目录下的内容。你可以通过设置 `listing` 属性为 `true` 来开启这个选项,如:
+且看这个配置,请求 `/`,首先尝试去加载 `/index.html`, 之后再加载 `/default.html`。当没有可用的索引文件时, inert 会显示这个目录为列表页。可以设置属性 `listing` 为 `true` 来开启,如:
```javascript
server.route({
@@ -171,4 +179,51 @@ server.route({
}
});
```
-现在,对 `/` 的请求将会回复一个显示目录内容的 HTML。使用启用了列表的目录 handler 时,默认情况下隐藏文件不会显示在列表中,可以通过将 `showHidden` 选项设置为 `true` 来显示。与文件 handler 一样,目录 handler 也有一个 `lookupCompressed` 的选项用于提供预压缩文件。你还可以设置一个 `defaultExtension`,如果找不到原始路径,它将附加到请求中。这意味着对 `/bacon` 的请求也会尝试文件 `/bacon.html`。
+现在,请求 `/`,会明确返回带目录内容的网页。开启目录显示后,默认不显示隐藏文件,可以将 `showHidden` 选项设置为 `true` 来显示。与处理文件一样,目录处理也有一个 `lookupCompressed` 选项,用于预压缩文件。还可以设置默认扩展名 `defaultExtension`,意思是若找不到原始路径,将其附加到请求中。比如请求 `/bacon` 也会尝试请求 `/bacon.html`。
+
+## 静态文件服务器
+
+要提供静态内容,通常会建立文件服务器。在hapi中启用文件服务器:
+
+```js
+const Path = require('path');
+const Hapi = require('@hapi/hapi');
+const Inert = require('@hapi/inert');
+
+const init = async () => {
+
+ const server = new Hapi.Server({
+ port: 3000,
+ routes: {
+ files: {
+ relativeTo: Path.join(__dirname, 'public')
+ }
+ }
+ });
+
+ await server.register(Inert);
+
+ server.route({
+ method: 'GET',
+ path: '/{param*}',
+ handler: {
+ directory: {
+ path: '.',
+ redirectToSlash: true
+ }
+ }
+ });
+
+ await server.start();
+
+ console.log('服务器运行于', server.info.uri);
+};
+
+init();
+```
+
+首先,引入 `inert` 和 `path`。这是必备项。
+
+接下来,配置 `server.options.rouse`。可以设置 `relativeTo` 指定文件目录。
+
+配置好后,注册 `inert` 插件。以加载目录处理程序。在 `directory` 中,配置 `path`,这项必填。第二个选项是 `redirectToSlash`。设置为 `true`,意思是,若路由末尾没有斜杠,则加之。
diff --git a/static/lib/tutorials/zh_CN/testing.md b/static/lib/tutorials/zh_CN/testing.md
index 88cecf8..5cd40ba 100644
--- a/static/lib/tutorials/zh_CN/testing.md
+++ b/static/lib/tutorials/zh_CN/testing.md
@@ -1,33 +1,32 @@
-# Testing
+# 测试
-此教程兼容hapi v17以及更高版本.
+_本教程适用于 hapi v17 及以上_
-## Overview
+## 总览
-Hapi设计理念是创建健壮的、可测试的应用.为此,Hapi具备不用启动服务就可以测试路由的功能, 减少了TCP协议连接的时间成本,降低了复杂度.
-
-此教程带你进行路由测试的基本设置, 并提出了一种通过[lab](/module/lab)和[code](/module/code)创建可测试应用的一种可能.
+Hapi 设计理念是创建健壮的、可测试的应用。为此,无需真启动 Hapi 服务器,也可测试路由,没有了 TCP 协议连接开销,降低了复杂度。
+此教程演示路由测试的基本设置,并概述测试套件 [lab](/module/lab) 和 [code](/module/code)。
## lab
-`lab` 是Node.js的一个简单的测试套件. 区别于其他测试套件, lab 只使用 async/await 特性,并且包含了所有你在现代化Node.js测试套件里所需要的工具. `lab` 可以和任何其他的断言库兼容,在条件不符合时候掏出错误. 在本教程中,你将使用 `code` 断言库.
+`lab` 是个简单测试套件。与其他测试套件不同,lab 不仅有 async/await 特性,而且内置了诸多测试 Node.js 所需工具。`lab` 兼容其他断言库,在条件不符合时抛出错误。本教程中,使用 `code` 断言库。
-首先安装 `lab`, 在terminal里面输入:
+在终端执行以下命令以安装 `lab`:
`npm install --save-dev @hapi/lab`
## code
-`code` 建立在 `chai` 断言库基础上,被设计成小巧,简单且直观的断言库. 可以不通需要任何插件,拓展库直接运行,并且开销很小.
+`code` 断言库出自于 `chai`,小巧、简单、直观。可以直接运行,无需额外插件或扩展,开销很小。
-然后安装 `code`, 在terminal里输入:
+安装 `code`:
`npm install --save-dev @hapi/code`
-## Server Setup
+## 服务器设置
-从Getting Started教程中拿到例子的服务代码, 我们对它进行一下小修改, 比如让它在被我们的测试用例引用的时候不自动重启服务. 你可以把这个文件保存为`server.js`,然后放在工程的 `lib`目录下:
+以快速入门教程中的代码为例。小小修改一下,以在测试时,不自动启动服务。可以将此文件保存为 `server.js`,放在项目 `lib` 目录下:
```js
'use strict';
@@ -57,7 +56,7 @@ exports.init = async () => {
exports.start = async () => {
await server.start();
- console.log(`Server running at: ${server.info.uri}`);
+ console.log(`服务器运行于: ${server.info.uri}`);
return server;
};
@@ -67,7 +66,7 @@ process.on('unhandledRejection', (err) => {
process.exit(1);
});
```
-现在用导出 `init()` 和 `start()`方法代替原来的调用这两个方法. 这样我们可以在别的文件中来初始化、启动服务. `init()` 方法可以初始化 (开启缓存, 完成插件注册) 但是并未启动服务. 接下来要在测试用例中启动服务. `start()` 会自动启动服务. 我们将要在入口文件中调用:
+导出两个函数:`init()`、`start()`,不直接调用,以在其他文件中初始化、启动服务。函数 `init()` 用来初始化服务器,开启缓存,完成插件注册,但并未启动服务。测试用例中用到此函数。函数 `start()` 会真启动服务。这个函数要在入口文件中调用:
```js
`use strict`;
@@ -76,17 +75,17 @@ const { start } = require('lib/server');
start();
```
-你在这里创建的,是通常启动服务时候使用的,在入口文件通过调用start方法来启动服务的一种方式, 这种方式对外暴露出一个外部HTTP端口, 此外你也可以在我们的测试用例中使用一个什么都不做的模块.
+正常启动服务时,调用 `start` 函数,会对外暴露 HTTP 端口。在测试用例中,不需要真启动服务,只需初始化服务即可。因此,调用 `init` 函数,而不是 `start` 函数。
-## Writing a Route Test
+## 路由测试
-在这个例子中你将使用 [lab](/module/lab), 同样的方法也可以在任何测试工具中使用,例如 [Mocha](https://mochajs.org/), [Jest](https://jestjs.io/), [Tap](https://www.node-tap.org/), [Ava](https://github.com/avajs) 等.
+这个例子用到的 [lab](/module/lab),可以推广应用到其他测试工具,例如 [Mocha](https://mochajs.org/)、[Jest](https://jestjs.io/)、[Tap](https://www.node-tap.org/)、[Ava](https://github.com/avajs) 等。
-默认情况下, `lab` 加载所有文件'*.js' 在本地的 `test` 目录里,并执行所有发现的测试用例. 如果想设置其他的文件夹或者文件, 需要将文件或者文件夹路径作为参数传入:
+默认, `lab` 加载本立目录 `test`,及其中 '*.js',而后执行测试用例。如果想自定义文件夹或者文件,可参数传入路径:
`$ lab unit.js`
-准备开始,创建一个 `example.test.js` 在 `test` 目录里:
+测试前,在 `test` 目录里,创建文件 `example.test.js`:
```js
'use strict';
@@ -116,15 +115,15 @@ describe('GET /', () => {
});
});
```
-这里你将要测试我们的 `'GET'` 路由是否会响应 `200` 状态码. 首先调用 `describe()` 来给你的测试用例提供一个构造函数. `describe()` 接受2个参数, 一个是测试用例的描述, 另一个是设置测试用例的方法.
+此处测试 `'GET'` 请求,看是否会响应状态码 `200`。首先调用 `describe()`,`describe()` 接收 2 个参数, 一个是测试用例描述, 一个是测试用例方法。
-请注意 `init` 代替 `start` 来设置服务, 意味着我们启动了服务, 但是没有监听任何端口. 每个测试用例后都会调用 `stop` 来清理和停止服务.
+请注意,启动服务用 `init`,而不是 `start`。以启动服务器,而不监听端口。每次测试后,调用 `stop` 来清理并停止服务。
-`it()` 方法将会运行你的测试用例. `it()` 接受2个参数, 一个是测试用例通过的描述语句, 另一个是运行测试用例的方法.
+函数 `it()` 用来执行测试用例。`it()` 接受 2 个参数,一个是测试用例通过的描述语句,一个是运行测试用例的函数。
-你需要了解服务器上的 `inject`. `inject` 使用 [Shot](/module/shot) 来 `inject` 直接向hapi的hanlder注入请求. 这就是让我们能够测试HTTP方法的关键所在.
+例子中用到了 `inject`。`inject` 用到了 [Shot](/module/shot),直接将请求注入到路由处理方法,这就是不启动服务而能够测试的关键所在。
-为了让我们的测试用例运行, 你可以修改项目中的 `package.json` 来测试你的测试用例:
+要运行测试,可以修改项目中的 `package.json`:
```json
"scripts": {
diff --git a/static/lib/tutorials/zh_CN/validation.md b/static/lib/tutorials/zh_CN/validation.md
index 6dc5e2e..c112152 100644
--- a/static/lib/tutorials/zh_CN/validation.md
+++ b/static/lib/tutorials/zh_CN/validation.md
@@ -1,14 +1,53 @@
-## 数据验证
+# 确认
-_该教程适用于 hapi v17版本_
+_本教程适用于 hapi v17 及以上、joi v16 及以上_
-对于你的应用来说,数据验证可以保证应用的稳定和安全。 hapi 通过模块 [Joi](https://github.com/hapijs/joi) 帮助你实现更为简洁清晰的验证代码。
+## 总览
-## 输入
+数据确认,乃应用安全稳定之关键。hapi 确认数据,用到 [Joi](https://joi.dev)。这个模块语法十分简单明了。
-第一种验证类型是输入验证,它可以通过路由的 `options` 进行定义。 它可以验证 HTTP 头信息、路径参数、查询参数以及请求的内容。
+## Joi
-示例代码如下:
+[Joi](https://joi.dev),用对象描述确认信息。先安装:
+
+`npm install joi`
+
+再引入:
+
+`const Joi = require('joi');`
+
+## 输入确认
+
+输入确认,在路由的 `options` 中定义,具体位置是 `route.options.validate`。其可以验证 HTTP 头信息、路由参数、查询参数、请求负载。`validate` 默认值为:
+
+```js
+{
+ headers: true,
+ params: true,
+ query: true,
+ payload: true,
+ state: true,
+ failAction: 'error'
+}
+```
+
+如果某个键为 `true`,则不确认。键也可以为方法,方法签名为 `async function(value, options)`,或者 `joi` 对象,joi 也可以配置。下面例子是[查询参数](#queryparams)的一部分:
+
+```js
+options: {
+ validate: {
+ query: Joi.object({
+ limit: Joi.number().integer().min(1).max(100).default(10)
+ }).options({ stripUnknown: true });
+ }
+}
+```
+
+具体配置,请看[这儿](https://joi.dev)。
+
+### 路由参数
+
+例:
```javascript
server.route({
@@ -20,19 +59,17 @@ server.route({
},
options: {
validate: {
- params: {
+ params: Joi.object({
name: Joi.string().min(3).max(10)
- }
+ })
}
}
});
```
-### 路径参数
-
-正如同上例,我们在 `option` 对象中添加 `validate.params` 来完成这个操作,这就是 hapi 路径参数验证的配置。 Joi 的语法非常简介并且易读, 以上代码将会验证参数字符串的长度,其最小长度为3,最大长度为10。
+例子中,在 `option` 对象里添加 `validate.params`,以指定那些命名参数应当确认。 Joi 语法非常简洁易懂,上述代码确认参数应为字符串,其最小长度为 3,最大长度为 10。
-通过以上配置,如果发送请求 `/hello/jennifer` 将会得到期望的 `Hello jennifer!` 返回。 而请求 `/hello/a` 将会得到如下 HTTP `400` 响应错误:
+配置好后,请求 `/hello/panda`,会如期返回 `Hello panda!`。而请求 `/hello/a`,则会得到如下 HTTP `400` 响应错误:
```json
{
@@ -42,13 +79,13 @@ server.route({
}
```
-同样,如果我们发送请求 `/hello/thisnameiswaytoolong` 也会得到相同的错误。
+同样,请求 `/hello/thisnameiswaytoolong`,也会得到如上错误。
-### 查询参数
+### 查询参数
-验证查询参数我们只需要在路由选项中配置 `validate.query`。默认情况下 hapi 不做任何验证,但是如果一但为一个查询参数添加验证时,这将意味着你 *必须* 为你所接受的所有查询参数添加验证。
+确认查询参数,只需要在路由选项中配置 `validate.query`。一旦配置,**必须**为所有所需的查询参数添加确认。
-例如,一个路由返回 blog 列表,并且你希望限制用户的请求条目的数量,你可以通过以下配置来完成:
+例如,一个路由返回博客列表,且限制请求条量,可以如下配置:
```javascript
server.route({
@@ -68,65 +105,122 @@ server.route({
});
```
-这里 `limit` 查询参数将被限定在 1 与 100之间,如果没有指定这个参数的时候将会有一个为 10 的默认值。当我们发送请求 `/posts?limit=15&offset=15` 将会获得一个 HTTP `400` 响应错误。
+这里 `limit` 查询参数会限制在 1 与 100 之间,如果没有指定这个参数,则默认为 10。当请求 `/posts?limit=15&offset=15`,会响应 HTTP `400` 错误。
-之所以得到这个错误是因为 `offset` 参数没有设置验证,因为我们只提供了 `limit` 参数的验证。
+之所以这样,是因为 `offset` 参数没有配置确认信息。
-### Headers
+### 负载参数
-你可以通过 `validate.headers` 选项验证 HTTP 头部信息。
+设置 `validate.payload` 选项,来确认负载。其格式与查询参数完全相同,如果要确认一个键,则必须确认所有键。例:
-### Payload 参数
+```js
+server.route({
+ method: 'POST',
+ path: '/post',
+ handler: function (request, h) {
-可以通过 `validate.payload` 选项验证 payload参数。 如同查询参数一样,你必须验证所有的内容。
+ return '添加成功';
+ },
+ options: {
+ validate: {
+ payload: Joi.object({
+ post: Joi.string().min(1).max(140),
+ date: Joi.date().required()
+ })
+ }
+ }
+});
+```
-## 输出
+上面的例子是一个非常基本的路由,处理传入博客文章。用户在 `request.payload` 对象中提交了博客文章和日期。通常,这鞋数据会存储到数据库。但存之前,必须确认负载。首先,`joi` 规定 `post` 必须最小 1 个字符,最大 140 个字符。还规定,`date` 必须是 MM-DD-YYY 格式的有效日期,并且必填。
-hapi 可以在返回之前验证响应信息,这个定义在路由 `options` 对象中的 `response`属性内。
+确认失败,则返回:
-如果一个响应没有通过验证,客户端将会收到一个默认的 (500) 内部服务错误 (参见 `response.failAction` 如下)。
+```json
+{
+ "error": "Bad Request",
+ "message": "Invalid request payload input",
+ "statusCode": 400
+}
+```
-输出验证对于服务是否满足文档规定的数据格式很有帮助。除此之外如同 [hapi-swagger](https://github.com/glennjones/hapi-swagger) 和 [lout](https://github.com/hapijs/lout) 之类的插件会根据验证规则自动生成文档,这可以确保我们的文档总是最新的。
+### 请求头
-hapi 支持多种选项来调整输出验证,这里简单列举一下:
+用 `validate.headers` 来验证 HTTP 头:
-### response.failAction
+```js
+server.route({
+ method: 'GET',
+ path:'/hello/{name}',
+ handler: (request, h) => {
-你可以通过以下 `response.failAction` 的设置来修改验证失败的行为:
-* `error`: 发送内部服务器错误 (500) 请求 (默认)
-* `log`: 只记录错误的日志并按原样返回
-* `ignore`: 继续处理请求而不做任何事情
-* 或者一个带有签名 `async function(request, h, err)` 的方法,其中 `request` 是请求对象, `h` 是 response toolkit ,`err` 是验证错误。
+ return `Hello ${request.params.name}!`;
+ },
+ options: {
+ validate: {
+ headers: Joi.object({
+ cookie: Joi.string().required()
+ }),
+ options: {
+ allowUnknown: true
+ }
+ }
+ }
+});
+```
-### response.sample
+此处验证 cookie,必须为字符串,且必填。`allowUnknown` 表示允许接受其他头信息,而无需验证。
-如果你担心性能问题,hapi 可以通过百分比配置只验证一部分请求。这个配置在路由 `config` 对象的 `response.sample`对象中。你可以指定一个`0`到`100`之间的数字以用来指定验证请求的百分比。
+## 输出
-### response.status
+hapi 也可以确认响应信息,这定义在路由 `options` 对象中的 `response` 属性内。
-有时候同样的endpoint会有不同的响应类型,比如一个`POST`路由可能会返回以下内容:
-* `201` 代表一个新的资源被创建。
-* `202` 代表一个资源被更新。
+响应不通过,客户端默认会收到 (500) 响应 (参见 `response.failAction` 如下)。
-hapi 可以通过不同的验证模式对此进行支持。`response.status` 是一个以返回码作为Key的对象, 它的属性是 joi 模式:
+输出确认,可验证返回数据是否与接口文档一致。此外,像 [hapi-swagger](https://github.com/glennjones/hapi-swagger) 和 [lout](https://github.com/hapijs/lout) 之类的插件,会根据验证规则自动生成文档,从而确接口文档始终如新。
-```json5
-{
- response: {
- status: {
- 201: dataSchema,
- 202: Joi.object({ original: dataSchema, updated: dataSchema })
+hapi 支持多种选项来确认输出,简单列举如下:
+
+### response.failAction
+
+可以设置 `response.failAction`,来修改失败确认:
+* `error`: 响应内部服务器错误 (500) (默认)
+* `log`: 只记录错误日志,并按原样响应
+* `ignore`: 忽略错误
+* 或者一个带有签名 `async function(request, h, err)` 的方法,其中 `request` 是请求对象, `h` 是响应工具包 ,`err` 是错误信息。
+
+例:
+
+```js
+const bookSchema = Joi.object({
+ title: Joi.string().required(),
+ author: Joi.string().required(),
+ isbn: Joi.string().length(10),
+ pageCount: Joi.number(),
+ datePublished: Joi.date().iso()
+});
+
+server.route({
+ method: 'GET',
+ path: '/books',
+ handler: async function (request, h) {
+
+ return await getBooks();
+ },
+ options: {
+ response: {
+ schema: Joi.array().items(bookSchema),
+ failAction: 'log'
}
}
-}
+});
```
-### response.options
-选项可以在验证的时候传递给 joi。 更多细节请参阅 [API docs](/api#-routeoptionsresponseoptions)。
+此路由返回书籍列表。请看,由于 `failAction` 设置为`log`,服务器将只是记录错误,并按原样发送响应。
-### 示例
+### response.sample
-这里是一个返回图书列表的路由配置:
+如果担心性能,hapi 可以只确认一部分响应。这在路由 `options` 的 `response.sample` 处设置。其值为 `0` 到 `100`,表示确认几成响应,请看:
```javascript
const bookSchema = Joi.object({
@@ -151,17 +245,38 @@ server.route({
}
}
});
+```
+书籍路由的响应确认百分比设置为,`sample: 50`。表示服务器只确认一半响应。
+
+### response.status
+
+有时,同一路由不同响应,比如一个 `POST` 路由可能会返回以下内容:
+* `201` 代表一个新建。
+* `202` 代表一个更改。
+
+hapi 可以为不同响应,配置不同确认规则。`response.status` 是一个对象,其键为状态码,其值为 joi 对象:
+```json5
+{
+ response: {
+ status: {
+ 201: dataSchema,
+ 202: Joi.object({ original: dataSchema, updated: dataSchema })
+ }
+ }
+}
```
-这里只验证一半的请求 (`sample: 50`)。如果 `books` 不精确匹配 `bookSchema`时,之所以 hapi 返回 `500` 错误码,是因为 `response.failAction` 没有被定义。与此同时,错误请求 *不* 再能鉴别错误的原因。但是如果你配置了日志, 你依然可以通过查阅日志来发现验证错误的具体原因。如果 `response.failAction` 设置为 `log`, hapi 将会返回原始的内容信息,并将验证错误记录在日之内。
+### response.options
+
+此选项可以在确认时候传给 joi,用于设置全局选项,如`stripUnknown`、`abortEarly`。如果通过 `schema` 或 `status` 定义了自定义确认函数,则 `options` 可以为任意对象,并变成确认函数的第二个参数。。
-### 除Joi之外的选择
+## Joi 替代品
-我们建议你使用 Joi 做验证, 但是 hapi 也为每个错误验证提供了一些不同的选项。
+通常建议用 Joi 来确认,但 hapi 也可以有别的配置。
-最简单的你可以使用一个 boolean 验证器。默认情况下所有可用的验证都设置为 `true` 它表示不会做任何验证。
+最简单的是,可以给确认选项指定布尔值。默认情况下,所有可用的确认都设置为 `true`,表示不做确认。
-如果验证的参数设置为 `false` 它表示该参数不允许任何值。
+如果确认的选项设置为 `false`,表示该选项不允许任何值。
-你也可以传递一个自定义拥有`async function (value, options)`签名的函数, `value` 代表要被验证的数据 `options` 是服务器对象内定义的验证的选项。当一个值返回时,这个值将会替换被验证的原始对象。 例如,你正在验证 `request.headers`,返回后的值将会代替 `request.headers`,原始的值将会被保留在 `request.orig.headers`。如果没有返回 headers 将保持不变。 如果发生了错误, 可以通过 `failAction` 进行错误处理。
+也可以传递一个自定义函数,其签名为 `async function (value, options)`。其中,`value` 是要确认的数据,`options` 是服务器对象内定义的确认选项。其返回值会替换原始确认对象。例如,确认 `request.headers`,返回后的值会代替 `request.headers`,原始值则保存在 `request.orig.headers`。否则 `headers` 保持不变。另外,可以用 `failAction` 处理错误。
diff --git a/static/lib/tutorials/zh_CN/views.md b/static/lib/tutorials/zh_CN/views.md
index f00da55..d033f31 100644
--- a/static/lib/tutorials/zh_CN/views.md
+++ b/static/lib/tutorials/zh_CN/views.md
@@ -1,12 +1,22 @@
-## 视图
+# 视图
-_该教程适用于 hapi v17版本_
+_本教程适用于 hapi v17 及以上、joi v16 及以上_
-hapi 对模板渲染有广泛的支持,包括加载以及使用多个模板引擎、部分渲染、辅助工具 (模板中用于操作数据的函数)以及布局功能。这些功能由 [vision](https://github.com/hapijs/vision) 插件提供。
+## 总览
-## 配置服务器
+hapi 支持模板渲染,且功能众多,包括加载使用多个模板引擎、部分渲染、辅助工具 (一系列函数,用于在模板中用于操作数据)、布局。这些由 [vision](/module/vision) 插件提供。
-如果要使用视图,首先需要在服务器上配置一个模板引擎。 这可以通过 vision 提供的 [`server.views()`](https://github.com/hapijs/vision/blob/master/API.md#serverviewsoptions) 方法来完成:
+## Vision
+
+模版渲染插件 [Vision](/module/vision),是 hapi.js 提供的。`Vision` 扩展了 `server`、`request`、`h`响应工具包接口,增加了方法以管理视图引擎。`Vision` 还提供了一个内置的处理程序,用于创建模板响应。
+
+`Vision` 开箱即用,与其他引擎蕾丝,如 ejs、handlebars、pug、twig,等等。不遵循常规 API 模式的引擎,仍然可以将其API映射到 [Vision API](/module/vision)来使用。
+
+详情参见[这儿](/module/vision)。
+
+## 服务器配置
+
+要用视图,先配置模板引擎。可以用 vision 提供的 [`server.views()`](https://github.com/hapijs/vision/blob/master/API.md#serverviewsoptions) 方法:
```javascript
'use strict';
@@ -33,18 +43,17 @@ const start = async () => {
start();
```
-我们在这里做了许多工作:
-首先,我们加载 [vision](https://github.com/hapijs/vision) 插件,它可以为hapi提供模板渲染引擎。
+此处,先加载 [vision](/module/vision) 插件,以支持模板渲染。
-其次,我们注册 `handlebars` 模块为引擎提供渲染 `.html` 文件的能力。
+接着,注册 `handlebars` 模块,以负责渲染扩展名为`.html`的模板。
-最后,我们告诉服务器我们的模板在 `templates` 目录下。我们可以通过 `relativeTo` 选项指定相对路径,默认情况下 hapi 在当前工作目录下查找模板文件。
+最后,配置模板路径,位于 `templates` 目录。设置 `relativeTo` 选项,指明 `template` 位置。默认,hapi 再当前项目目录下寻找模板文件。
-### 视图选项
+## 视图选项
-hapi 的视图引擎有众多的选项。完整的文档可以在这里找到 [vision API reference](https://github.com/hapijs/vision/blob/master/API.md#serverviewsoptions),我们这里只示例一部分。
+hapi 的视图引擎选项众多。完整文档请参阅 [vision 接口](/module/vision/api#serverviewsoptions),这里只示例一部分。
-这些选项可以全局配置,也可以配置给指定的引擎,例如:
+这些选项可以全局配置,也可以配置给指定引擎,例如:
```javascript
server.views({
@@ -58,36 +67,37 @@ server.views({
});
```
-### 引擎
+### 引擎
+
+要启用视图,至少要有一个模板引擎。引擎可以是同步的也可以是异步的,并且这个对象需要以 `compile` 为名导出。
-如果需要在 hapi 中使用视图,你需要在服务器中至少注册一个模板引擎。引擎可以是同步的也可以是异步的,并且这个对象需要以 `compile` 为名导出。
+同步引擎 `compile` 方法的签名为 `function (template, options)`。该方法应返回签名为 `function (context, options)` 的函数,该函数或抛出错误,或返回编译后的 html。
-同步引擎 `compile` 方法的签名为 `function (template, options)`。 并且这个方法需要返回一个签名为 `function (context, options)` 的函数,在函数内或者抛出错误或者返回编译后的 html 文件。
+异步引擎 `compile` 方法的签名为 `function (template, options, callback)`。其以标准错误优先格式调用 `callback`,并返回签名为 `function (context,options,callback)` 的新函数。该函数也应该以错误优先格式调用 `callback`,而编译的 html 是第二个参数。
-异步引擎 `compile` 方法的签名为 `function (template, options, callback)` 它以标准错误优先格式调用 `callback` 并返回一个带有签名`function (context,options,callback)` 的新方法。返回的方法也应该以错误优先格式调用 `callback`,而编译的 html 是第二个参数。
+默认情况下 hapi 认为模板引擎是同步的 (即 `compileMode` 默认是 `sync`),要使用异步引擎需要将 `compileMode` 设置为 `async`。
-默认情况下 hapi 认为所有的模板引擎都是同步的 (例如 `compileMode` 默认是 `sync`), 如果使用异步引擎你需要将 `compileMode` 设置为 `async`。
+想要修改两种 `compile` 方法中的 `options` 参数或者返回方法,可以修改 `compileOptions` 和 `runtimeOptions`。这两个选项都默认为空对象 `{}`。
-想要修改两种 `compile` 方法中的 `options` 参数或者返回方法,可以通过 `compileOptions` 和 `runtimeOptions`设置来完成。这两个选项都默认为空对象 `{}`。
-`compileOptions` 是作为第二个参数传递给 `compile` 的对象, 而 `runtimeOptions` 传递给 `compile` 返回的方法。
+`compileOptions` 是 `compile` 的第二个参数, 而 `runtimeOptions` 传递给 `compile` 的返回函数。
-如果只注册了一个模板引擎,他将自动成为默认值,允许在渲染视图的时候不使用文件扩展名。但是如果过注册了多个模板引擎,则必须使用文件扩展名,或者将 `defaultExtension` 设置为最常用的引擎。对于不使用默认引擎的任何视图,依然需要指定文件扩展名。
+如果只注册了一个模板引擎,会自动成为默认引擎,允许在渲染视图时,不使用文件扩展名。但是如果注册了多个模板引擎,则必须使用文件扩展名,或者将 `defaultExtension` 设置为最常用的引擎。对于不使用默认引擎视图,依然需要指定文件扩展名。
-另外一个有用的选项是 `isCached`。如果设置为 `false` , hapi 将不会缓存模板引擎渲染的结果,而是每次都去读取模板文件并渲染。 在开发你的应用时, 这个设置非常方便,它可以防止您在处理模板时重新启动应用程序。 我们建议在生产环境中将 `isCached` 的值设置为 `true` 。
+另外一个有用的选项是 `isCached`。如果设置为 `false`,hapi 将不缓存渲染结果,而每次都重新渲染。在开发阶段,这个设置非常方便,在处理模板,可以不用重启服务器。建议在生产环境中,将 `isCached` 设置为 `true`。
-### 路径
+### 路径
-由于视图可以包含多个不同位置的文件, 因为hapi 提供了一些选项帮助你配置多个路径用于内容查找:
+由于视图文件可能在不同位置,因此 hapi 提供了一些选项,以配置多个路径用于内容查找:
-- `path` : 包含你主要模板的路径
-- `partialsPath` : 部分内容的路径
-- `helpersPath` : 模板辅助工具的路径
-- `layoutPath` : 布局模板的路径
-- `relativeTo` : 用于指定其他路径的前缀。当指定后,其余路径则都可以相对于此路径
+- `path` : 主模板路径
+- `partialsPath` : 组件路径
+- `helpersPath` : 辅助工具路径
+- `layoutPath` : 布局路径
+- `relativeTo` : 用于指定其他路径前缀。当指定后,其余路径则都可以相对于此路径
-此外, 还有两个设置可以改变 hapi 允许你使用路径的方式。默认情况下,不允许使用绝对路径和遍历 `path` 目录,但是可以通过将 `allowAbsolutePaths` 和 `allowInsecureAccess` 设置为 true 来开启这些功能。
+此外,还有两个设置可以路径方式。默认情况下,不允许使用绝对路径和遍历 `path` 目录,但是可以将 `allowAbsolutePaths` 和 `allowInsecureAccess` 设置为 true 来开启这些功能。
-例如你的目录结构如下:
+例如,目录结构如下:
```
templates\
@@ -99,7 +109,7 @@ templates\
fortune.js
```
-你可以如下去配置:
+按如下配置:
```javascript
server.views({
@@ -113,63 +123,11 @@ server.views({
});
```
-## 渲染一个视图
-
-渲染一个视图有两种选择:你可以使用 `h.view()` 方法,这里的 `h` 是 [response toolkit](/api#response-toolkit) 。而另一种方法是通过视图 handler。
-
-### [`h.view()`](https://github.com/hapijs/vision/blob/master/API.md#hviewtemplate-context-options)
-
-首先我们来看一下如何使用 `h.view()` 渲染视图。 使用这种方法的路由配置如下:
-
-```javascript
-server.route({
- method: 'GET',
- path: '/',
- handler: function (request, h) {
-
- return h.view('index');
- }
-});
-```
-
-你可以通过第二个参数向 `h.view()` 传递要渲染的内容,如:
-
-```javascript
-return h.view('index', { title: 'My home page' });
-```
-
-### 视图 handler
-
-第二种是可以通过使用 hapi 内建的视图 handler 来渲染。 这种方式的路由如下配置:
-
-```javascript
-server.route({
- method: 'GET',
- path: '/',
- handler: {
- view: 'index'
- }
-});
-```
-
-当使用视图 handler 时,内容作为 `context` 的值传入, 如:
-
-```json5
-handler: {
- view: {
- template: 'index',
- context: {
- title: 'My home page'
- }
- }
-}
-```
-
-### 全局内容
+### 全局上下文
-我们已经知道了如何传递内容去一个视图,但是我们有一些默认的内容 *都必须* 显示在所有的模板上该如何呢?
+在[渲染视图](#render)处,了解如何将上下文直接传递到视图中。但如果有一些默认的上下文,要**始终**应用于所有模板上,该当如何呢?
-最简单的方式是在调用 `server.views()` 使用 `context` 选项 :
+最简单的方式是在调用 `server.views()` 使用 `context` 选项:
```javascript
const context = {
@@ -187,28 +145,26 @@ server.views({
});
```
-这个默认的全局内容将会以最低的优先级合并到你所有的视图中。
+这个默认的全局上下文,会以最低优先级合并到所有视图中。
+
-### 视图辅助工具
-位于定义在 `helpersPath` 中的 JavaScript 模块可以在模板中使用。在这个例子中,我们将创建一个视图 helper `fortune`,当在模板中使用时,它将从字符串数组中挑选并打印出一个元素。
+### 视图辅助函数
-下面的代码片段是完整的帮助函数,我们将它存储在 `helpers` 目录中名为 `fortune.js` 的文件中。
+辅助函数目录定义在 `helpersPath`。下面例子中,将创建一个辅助函数 `fortune`,在模板中使用时,会从字符串数组中挑选并打印出一个元素。
+
+下面的代码片段是完整的辅助函数,存储在 `helpers` 目录中,名为 `fortune.js` 的文件中。
```javascript
module.exports = function () {
const fortunes = [
- 'Heisenberg may have slept here...',
- 'Wanna buy a duck?',
- 'Say no, then negotiate.',
- 'Time and tide wait for no man.',
- 'To teach is to learn.',
- 'Never ask the barber if you need a haircut.',
- 'You will forget that you ever knew me.',
- 'You will be run over by a beer truck.',
- 'Fortune favors the lucky.',
- 'Have a nice day!'
+ '桃之夭夭,灼灼其华',
+ '之子于归,宜其室家',
+ '桃之夭夭,有蕡其实',
+ '之子于归,宜其家室',
+ '桃之夭夭,其叶蓁蓁',
+ '之子于归,宜其家人',
];
const x = Math.floor(Math.random() * fortunes.length);
@@ -216,16 +172,16 @@ module.exports = function () {
};
```
-现在我们可以在模板中使用刚刚创建的视图助手。 这里是一个在 `templates/index.html` 使用 handlebars 作为渲染引擎的代码示例:
+现在,可以在模板中使用这个辅助函数。本例使用引擎 handlebars,渲染 `templates/index.html`:
```html
-Your fortune
+一言
{{fortune}}
```
-现在,当我们启动服务器并用浏览器访问使用我们模板的路径(使用我们的视图helper)时,我们应该会在标题下方看到一个随机显示的段落。
+现在,启动服务器,访问这个模板路由,会在标题下方看到一条随机段落。
-作为参考,这是一个完整的服务器代码,它将在模板中使用 fortune 视图 helper 方法。
+完整例子:
```javascript
'use strict';
@@ -260,11 +216,11 @@ const start = async () => {
start();
```
-### 布局
+### 布局
-vision 对于视图布局有一些内建的支持,这个功能默认是关闭的,因为它可能会和一些特定的模板引擎冲突,因此我们建议只使用一种布局系统。
+`vision` 支持视图布局,默认关闭。因为,其他模板引擎也可能提供布局系统,二者可能会冲突。建议只使用一种布局系统。
-如果要使用内建的布局系统,首先需要设置视图引擎:
+如果要使用内建布局系统,首先需要设置视图引擎:
```javascript
server.views({
@@ -274,9 +230,9 @@ server.views({
});
```
-这将开启内建的布局系统,并且将默认的布局页定义在 `templates/layout/layout.html` (或者你正在使用的其他扩展名)。
+此设置开启布局功能,并且射者默认布局路径为 `templates/layout/layout.html` (或者用其他扩展名)。
-在你的 `layout.html` 中设置一块内容区域:
+在 `layout.html` 中设置一块内容区域:
```html
@@ -286,15 +242,15 @@ server.views({
```
-之后你的视图应该只包含有:
+之后你的视图应该只有:
```html
Content
```
-当渲染这个视图时 `{{{content}}}` 将会被视图中的内容替换。
+渲染时,`{{{content}}}` 会替换为上述内容。
-你可以通过全局配置来修改这个默认布局:
+可以全局配置默认布局:
```javascript
server.views({
@@ -303,8 +259,60 @@ server.views({
});
```
-你也可以为每一个视图指定一个布局:
+也可以为单独定制:
```javascript
return h.view('myview', null, { layout: 'another_layout' });
```
+
+## 视图渲染
+
+视图渲染有两种选择:使用 [`h.view()`](/module/vision/api#hviewtemplate-context-options) 方法,这里的 `h` 是[响应工具包](/api#response-toolkit)。而另一种方法用视图处理对象(handler)。
+
+### h.view()
+
+首先来看一下如何使用 `h.view()` 渲染视图。路由配置如下:
+
+```javascript
+server.route({
+ method: 'GET',
+ path: '/',
+ handler: function (request, h) {
+
+ return h.view('index');
+ }
+});
+```
+
+可以向第二个参数 `h.view()` 传递要渲染的内容,如:
+
+```javascript
+return h.view('index', { title: '首页' });
+```
+
+### 视图处理对象
+
+第二种是使用 hapi 内建的视图处理函数来渲染。路由配置如下:
+
+```javascript
+server.route({
+ method: 'GET',
+ path: '/',
+ handler: {
+ view: 'index'
+ }
+});
+```
+
+当使用视图处理对象时,内容当作 `context` 的值传入, 如:
+
+```json5
+handler: {
+ view: {
+ template: 'index',
+ context: {
+ title: '主页'
+ }
+ }
+}
+```