Fastify 系列教程:
- Fastify 系列教程一 (路由和日志)
- Fastify 系列教程二 (中間件、鈎子函數和裝飾器)
- Fastify 系列教程三 (驗證、序列化和生命周期)
- 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
。也可以是一個包含上述類型的數組。
-
schema:
{ "type": "number" }
valid:
1
,1.5
invalid:
"abc"
,"1"
,[]
,{}
,null
,true
-
schema:
{ "type": "integer" }
valid:
1
,2
invalid:
"abc"
,"1"
,1.5
,[]
,{}
,null
,true
-
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: {...}}
的格式)或者沒有type
和properties
屬性,而只有查詢字符串列表。(查看下面的例子)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 查看我的個人博客