Fastify 系列教程三 (验证、序列化和生命周期)

时间:2023-02-15 19:32:41

Fastify 系列教程:

验证

Fastify 可以验证请求信息,只有符合验证规则的请求才会被处理。

JSON Schema

什么是 JSON Schema ,通俗来讲,JSON Schema 就是“描述 JSON 数据格式的一段 JSON”。

首先,JSON Schema 也是一个 JSON 字符串,下面来看一个简单的 JSON Schema:

{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Product",
"description": "A product from Acme's catalog",
"type": "object",
"properties": {
"id": {
"description": "The unique identifier for a product",
"type": "integer"
},
"name": {
"description": "Name of the product",
"type": "string"
},
"price": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
}
},
"required": ["id", "name", "price"]
}

上面这段规则描述了这样一个JSON:

1、type 表示该 JSON 的类型是一个 "object"。

type 的参数可以是:number, integer(整型), string, boolean, array, object 或者 null。也可以是一个包含上述类型的数组。

  1. schema: { "type": "number" }

    valid: 1, 1.5

    invalid: "abc", "1", [], {}, null, true

  2. schema: { "type": "integer" }

    valid: 1, 2

    invalid: "abc", "1", 1.5, [], {}, null, true

  3. schema: { "type": ["number", "string"] }

    valid: 1, 1.5, "abc", "1"

    invalid: [], {}, null, true

2、properties 定义了 JSON 的字段规则。

3、requirede 定义了必须存在的属性列表。

我们来看一下可以用于这一模式中的各种重要关键字:

关键词 描述
$schema $schema 关键字状态,表示这个模式与 v4 规范草案书写一致。
title 用它给我们的模式提供了标题。
description 关于模式的描述。
type type 关键字在我们的 JSON 数据上定义了第一个约束:必须是一个 JSON 对象。
properties 定义各种键和他们的值类型,以及用于 JSON 文件中的最小值和最大值。
required 存放必要属性列表。
minimum 给值设置的约束条件,表示可以接受的最小值。
exclusiveMinimum 如果存在 "exclusiveMinimum" 并且具有布尔值 true,如果它严格意义上大于 "minimum" 的值则实例有效。
maximum 给值设置的约束条件,表示可以接受的最大值。
exclusiveMaximum 如果存在 "exclusiveMinimum" 并且具有布尔值 true,如果它严格意义上小于 "maximum" 的值则实例有效。
multipleOf 如果通过这个关键字的值分割实例的结果是一个数字则表示紧靠 "multipleOf" 的数字实例是有效的。
maxLength 字符串实例字符的最大长度数值。
minLength 字符串实例字符的最小长度数值。
pattern 如果正则表达式匹配实例成功则字符串实例被认为是有效的。

通过上面的配置,我们就可以验证某个 JSON 是否符合要求了:

validate(JSONSchema, myJson)

有同学肯定会问,这个验证函数 validate 从哪来?github 上有各种第三方验证器:

语言 程序库
C WJElement (LGPLv3)
Java json-schema-validator (LGPLv3)
.NET Json.NET (MIT)
ActionScript 3 Frigga (MIT)
Haskell aeson-schema (MIT)
Python Jsonschema
Ruby autoparse (ASL 2.0); ruby-jsonschema (MIT)
PHP php-json-schema (MIT). json-schema (Berkeley)
JavaScript Orderly (BSD); JSV; json-schema; Matic (MIT); Dojo; Persevere (modified BSD or AFL 2.0); schema.js.

而 Fastify 所使用的 ajv 也是一个 JSON Schema 验证器,号称:

The fastest JSON Schema validator for Node.js and browser with draft 6 support.

有了上面的介绍,我们就来看一下 Fastify 是怎么验证请求信息的吧:

非常简单,只需要添加需要验证的字段即可。

  • body:验证请求体,必须是 POST 或者 PUT 请求。
  • querystring: 验证查询字符串。可以是一个完成的 JSON Schema 对象(符合 {type: "object", properties: {...}} 的格式)或者没有 typeproperties 属性,而只有查询字符串列表。(查看下面的例子)
  • params: 验证路由参数。
  • headers: 验证请求头。

示例:

fastify.post('/add', {
schema: {
body: {
type: 'object',
properties: {
name: {
type: 'string'
},
id: {
type: 'number'
}
},
required: ['name', 'id']
}
}
}, function(request, reply){
reply.send('validate successful')
})

当发送一个body为

{
"name": "lavyun",
"id": "hello"
}

post 请求时,会得到错误:

{
"error": "Bad Request",
"message": "[{\"keyword\":\"type\",\"dataPath\":\".id\",\"schemaPath\":\"#/properties/id/type\",\"params\":{\"type\":\"number\"},\"message\":\"should be number\"}]",
"statusCode": 400
}

因为 id 不符合 number 类型,把 id 改成 1 就可以了。

注意:Fastify 配置了 avj 默认会自动把不符合类型的值强制转换成规则中定义的类型,如果仍然不符合类型,则返回错误:

例如

{
"name": null,
"id": "2"
}

也会验证通过,因为被强转成:

  "name": "null",
"id": 2

如果不想被强制转换,可以通过配置 avj 关闭该功能:

const fastify = require('fastify')({
ajv: {
coerceTypes: false
}
})

Schema Compiler

schemaCompiler 是一个指定 schema 编译器的方法。(用来验证 body, params, headers, querystring)。默认的 schemaCompiler 返回一个实现 ajv 接口的编译器。

如果你想更改默认的 ajv 实例,可以传入 ajv 配置项, 查看 Ajv documentation 了解更多。

或许想直接更换验证的库,比如使用 Joi:

const Joi = require('joi')

fastify.post('/the/url', {
schema: {
body: Joi.object().keys({
hello: Joi.string().required()
}).required()
},
schemaCompiler: schema => data => Joi.validate(data, schema)
})

序列化

通常,我们会通过 JSON 将数据发送给客户端, Fastify 提供了一个强大的工具: fast-json-stringify,这是一个比原生 JSON.stringify() 还快的 JSON 格式化器,其原理就是通过配合 JSON Schema,快速定位字段的类型,省去了原生 JSON.stringify() 内部判断字段类型的步骤,实现了 two times faster than JSON.stringify(). 的效果。

在路由选项中传入了 output schema,fastify 就会使用它。

const schema = {
response: {
200: {
type: 'object',
properties: {
value: { type: 'string' },
otherValue: { type: 'boolean' }
}
}
}
}

response schema 是基于状态码的,如果想应用相同的 schema 给多个同级状态码, 可以使用 2xx

const schema = {
response: {
'2xx': {
type: 'object',
properties: {
value: { type: 'string' },
otherValue: { type: 'boolean' }
}
},
201: {
type: 'object',
properties: {
value: { type: 'string' }
}
}
}
}

patternProperties

fast-json-stringify 支持属性匹配,符合属性正则的字段都会被验证:

const stringify = fastJson({
title: 'Example Schema',
type: 'object',
properties: {
nickname: {
type: 'string'
}
},
patternProperties: {
'num': {
type: 'number'
},
'.*foo$': {
type: 'string'
}
}
}) const obj = {
nickname: 'nick',
matchfoo: 42,
otherfoo: 'str'
matchnum: 3
} console.log(stringify(obj)) // '{"matchfoo":"42","otherfoo":"str","matchnum":3,"nickname":"nick"}'

更多 fast-json-stringify 的使用可以查看文档

生命周期

Fastify 严格遵循内部生命周期的架构。在每个部分的右侧分支上都有生命周期的下一个阶段,左侧的分支上有相应的错误状态码,如果父代引发错误,则会生成相应的错误状态码(注意,所有错误都由Fastify自动处理)。

Fastify 生命周期图示:

Incoming Request (请求到达)

└─▶ Instance Logger (实例化 Logger)

└─▶ Routing (路由匹配)

404 ◀─┴─▶ onRequest Hook (onRequest钩子)

4**/5** ◀─┴─▶ run Middlewares (执行中间件)

4**/5** ◀─┴─▶ Parsing (解析请求对象)

415 ◀─┴─▶ Validation (验证)

400 ◀─┴─▶ preHandler Hook (preHandler钩子)

4**/5** ◀─┴─▶ beforeHandler

4**/5** ◀─┴─▶ User Handler

└─▶ Reply (响应)
│ │
│ └─▶ Outgoing Response (发出响应)

└─▶ onResponse Hook (onResponese钩子

Fastify 的更多使用将在接下来的博客中说明。

参考文档:

JSON 模式 / http://wiki.jikexueyuan.com/project/json/schema.html

Tips:访问 https://lavyun.gitbooks.io/fastify/content/ 查看我翻译的 Fastify 中文文档。

访问lavyun.cn 查看我的个人博客