数据验证

该教程适用于 hapi v17版本

对于你的应用来说,数据验证可以保证应用的稳定和安全。 hapi 通过模块 Joi 帮助你实现更为简洁清晰的验证代码。

输入

第一种验证类型是输入验证,它可以通过路由的 options 进行定义。 它可以验证 HTTP 头信息、路径参数、查询参数以及请求的内容。

示例代码如下:

  1. server.route({
  2. method: 'GET',
  3. path: '/hello/{name}',
  4. handler: function (request, h) {
  5.  
  6. return `Hello ${request.params.name}!`;
  7. },
  8. options: {
  9. validate: {
  10. params: {
  11. name: Joi.string().min(3).max(10)
  12. }
  13. }
  14. }
  15. });

路径参数

正如同上例,我们在 option 对象中添加 validate.params 来完成这个操作,这就是 hapi 路径参数验证的配置。 Joi 的语法非常简介并且易读, 以上代码将会验证参数字符串的长度,其最小长度为3,最大长度为10。

通过以上配置,如果发送请求 /hello/jennifer 将会得到期望的 Hello jennifer! 返回。 而请求 /hello/a 将会得到如下 HTTP 400 响应错误:

  1. {
  2. "error": "Bad Request",
  3. "message": "Invalid request params input",
  4. "statusCode": 400
  5. }

同样,如果我们发送请求 /hello/thisnameiswaytoolong 也会得到相同的错误。

查询参数

验证查询参数我们只需要在路由选项中配置 validate.query。默认情况下 hapi 不做任何验证,但是如果一但为一个查询参数添加验证时,这将意味着你 必须 为你所接受的所有查询参数添加验证。

例如,一个路由返回 blog 列表,并且你希望限制用户的请求条目的数量,你可以通过以下配置来完成:

  1. server.route({
  2. method: 'GET',
  3. path: '/posts',
  4. handler: function (request, h) {
  5.  
  6. return posts.slice(0, request.query.limit);
  7. },
  8. options: {
  9. validate: {
  10. query: {
  11. limit: Joi.number().integer().min(1).max(100).default(10)
  12. }
  13. }
  14. }
  15. });

这里 limit 查询参数将被限定在 1 与 100之间,如果没有指定这个参数的时候将会有一个为 10 的默认值。当我们发送请求 /posts?limit=15&offset=15 将会获得一个 HTTP 400 响应错误。

之所以得到这个错误是因为 offset 参数没有设置验证,因为我们只提供了 limit 参数的验证。

Headers

你可以通过 validate.headers 选项验证 HTTP 头部信息。

Payload 参数

可以通过 validate.payload 选项验证 payload参数。 如同查询参数一样,你必须验证所有的内容。

输出

hapi 可以在返回之前验证响应信息,这个定义在路由 options 对象中的 response属性内。

如果一个响应没有通过验证,客户端将会收到一个默认的 (500) 内部服务错误 (参见 response.failAction 如下)。

输出验证对于服务是否满足文档规定的数据格式很有帮助。除此之外如同 hapi-swaggerlout 之类的插件会根据验证规则自动生成文档,这可以确保我们的文档总是最新的。

hapi 支持多种选项来调整输出验证,这里简单列举一下:

response.failAction

你可以通过以下 response.failAction 的设置来修改验证失败的行为:

  • error: 发送内部服务器错误 (500) 请求 (默认)
  • log: 只记录错误的日志并按原样返回
  • ignore: 继续处理请求而不做任何事情
  • 或者一个带有签名 async function(request, h, err) 的方法,其中 request 是请求对象, h 是 response toolkit ,err 是验证错误。

response.sample

如果你担心性能问题,hapi 可以通过百分比配置只验证一部分请求。这个配置在路由 config 对象的 response.sample对象中。你可以指定一个0100之间的数字以用来指定验证请求的百分比。

response.status

有时候同样的endpoint会有不同的响应类型,比如一个POST路由可能会返回以下内容:

  • 201 代表一个新的资源被创建。
  • 202 代表一个资源被更新。hapi 可以通过不同的验证模式对此进行支持。response.status 是一个以返回码作为Key的对象, 它的属性是 joi 模式:
  1. {
  2. response: {
  3. status: {
  4. 201: dataSchema,
  5. 202: Joi.object({ original: dataSchema, updated: dataSchema })
  6. }
  7. }
  8. }

response.options

选项可以在验证的时候传递给 joi。 更多细节请参阅 API docs

示例

这里是一个返回图书列表的路由配置:

  1. const bookSchema = Joi.object({
  2. title: Joi.string().required(),
  3. author: Joi.string().required(),
  4. isbn: Joi.string().length(10),
  5. pageCount: Joi.number(),
  6. datePublished: Joi.date().iso()
  7. });
  8.  
  9. server.route({
  10. method: 'GET',
  11. path: '/books',
  12. handler: async function (request, h) {
  13.  
  14. return await getBooks();
  15. },
  16. options: {
  17. response: {
  18. sample: 50,
  19. schema: Joi.array().items(bookSchema)
  20. }
  21. }
  22. });

这里只验证一半的请求 (sample: 50)。如果 books 不精确匹配 bookSchema时,之所以 hapi 返回 500 错误码,是因为 response.failAction 没有被定义。与此同时,错误请求 再能鉴别错误的原因。但是如果你配置了日志, 你依然可以通过查阅日志来发现验证错误的具体原因。如果 response.failAction 设置为 log, hapi 将会返回原始的内容信息,并将验证错误记录在日之内。

除Joi之外的选择

我们建议你使用 Joi 做验证, 但是 hapi 也为每个错误验证提供了一些不同的选项。

最简单的你可以使用一个 boolean 验证器。默认情况下所有可用的验证都设置为 true 它表示不会做任何验证。

如果验证的参数设置为 false 它表示该参数不允许任何值。

你也可以传递一个自定义拥有async function (value, options)签名的函数, value 代表要被验证的数据 options 是服务器对象内定义的验证的选项。当一个值返回时,这个值将会替换被验证的原始对象。 例如,你正在验证 request.headers,返回后的值将会代替 request.headers,原始的值将会被保留在 request.orig.headers。如果没有返回 headers 将保持不变。 如果发生了错误, 可以通过 failAction 进行错误处理。