鉤子 (hooks) 讓你能夠監(jiān)聽(tīng)?wèi)?yīng)用或請(qǐng)求/響應(yīng)生命周期之上的特定事件。使用 fastify.addHook 可以注冊(cè)鉤子。你必須在事件被觸發(fā)之前注冊(cè)相應(yīng)的鉤子,否則,事件將得不到處理。
通過(guò)鉤子方法,你可以與 Fastify 的生命周期直接進(jìn)行交互。有用于請(qǐng)求/響應(yīng)的鉤子,也有應(yīng)用級(jí)鉤子:
注意:使用 async/await 或返回一個(gè) Promise 時(shí),done 回調(diào)不可用。在這種情況下,仍然使用 done 可能會(huì)導(dǎo)致難以預(yù)料的行為,例如,處理函數(shù)的重復(fù)調(diào)用。
Request 與 Reply 是 Fastify 核心的對(duì)象。done 是調(diào)用生命周期下一階段的函數(shù)。
生命周期一文清晰地展示了各個(gè)鉤子執(zhí)行的位置。鉤子可被封裝,因此可以運(yùn)用在特定的路由上。更多信息請(qǐng)看作用域一節(jié)。
在請(qǐng)求/響應(yīng)中,有八個(gè)可用的鉤子 (按執(zhí)行順序排序):
fastify.addHook('onRequest', (request, reply, done) => {
// 其他代碼
done()
})
或使用 async/await:
fastify.addHook('onRequest', async (request, reply) => {
// 其他代碼
await asyncMethod()
return
})
注意:在 onRequest 鉤子中,request.body 的值總是 null,這是因?yàn)?body 的解析發(fā)生在 preValidation 鉤子之前。
fastify.addHook('preParsing', (request, reply, done) => {
// 其他代碼
done()
})
或使用 async/await:
fastify.addHook('preParsing', async (request, reply) => {
// 其他代碼
await asyncMethod()
return
})
注意:在 preParsing 鉤子中,request.body 的值總是 null,這是因?yàn)?body 的解析發(fā)生在 preValidation 鉤子之前。
fastify.addHook('preValidation', (request, reply, done) => {
// 其他代碼
done()
})
或使用 async/await:
fastify.addHook('preValidation', async (request, reply) => {
// 其他代碼
await asyncMethod()
return
})
fastify.addHook('preHandler', (request, reply, done) => {
// 其他代碼
done()
})
或使用 async/await:
fastify.addHook('preHandler', async (request, reply) => {
// 其他代碼
await asyncMethod()
return
})
preSerialization 鉤子讓你可以在 payload 被序列化之前改動(dòng) (或替換) 它。舉個(gè)例子:
fastify.addHook('preSerialization', (request, reply, payload, done) => {
const err = null;
const newPayload = { wrapped: payload }
done(err, newPayload)
})
或使用 async/await
fastify.addHook('preSerialization', async (request, reply, payload) => {
return { wrapped: payload }
})
注:payload 為 string、Buffer、stream 或 null 時(shí),該鉤子不會(huì)被調(diào)用。
fastify.addHook('onError', (request, reply, error, done) => {
// 其他代碼
done()
})
或使用 async/await:
fastify.addHook('onError', async (request, reply, error) => {
// 當(dāng)自定義錯(cuò)誤日志時(shí)有用處
// 你不應(yīng)該使用這個(gè)鉤子去更新錯(cuò)誤
})
onError 鉤子可用于自定義錯(cuò)誤日志,或當(dāng)發(fā)生錯(cuò)誤時(shí)添加特定的 header。該鉤子并不是為了變更錯(cuò)誤而設(shè)計(jì)的,且調(diào)用 reply.send 會(huì)拋出一個(gè)異常。它只會(huì)在 customErrorHandler 向用戶(hù)發(fā)送錯(cuò)誤之后被執(zhí)行 (要注意的是,默認(rèn)的 customErrorHandler 總是會(huì)發(fā)送錯(cuò)誤)。 注意:與其他鉤子不同,onError 不支持向 done 函數(shù)傳遞錯(cuò)誤。
使用 onSend 鉤子可以改變 payload。例如:
fastify.addHook('onSend', (request, reply, payload, done) => {
const err = null;
const newPayload = payload.replace('some-text', 'some-new-text')
done(err, newPayload)
})
或使用 async/await:
fastify.addHook('onSend', async (request, reply, payload) => {
const newPayload = payload.replace('some-text', 'some-new-text')
return newPayload
})
你也可以通過(guò)將 payload 置為 null,發(fā)送一個(gè)空消息主體的響應(yīng):
fastify.addHook('onSend', (request, reply, payload, done) => {
reply.code(304)
const newPayload = null
done(null, newPayload)
})
將 payload 設(shè)為空字符串 '' 也可以發(fā)送空的消息主體。但要小心的是,這么做會(huì)造成 Content-Length header 的值為 0。而 payload 為 null 則不會(huì)設(shè)置 Content-Length header。
注:你只能將 payload 修改為 string、Buffer、stream 或 null。
fastify.addHook('onResponse', (request, reply, done) => {
// 其他代碼
done()
})
或使用 async/await:
fastify.addHook('onResponse', async (request, reply) => {
// 其他代碼
await asyncMethod()
return
})
onResponse 鉤子在響應(yīng)發(fā)出后被執(zhí)行,因此在該鉤子中你無(wú)法再向客戶(hù)端發(fā)送數(shù)據(jù)了。但是你可以在此向外部服務(wù)發(fā)送數(shù)據(jù),比如收集數(shù)據(jù)。
在鉤子的執(zhí)行過(guò)程中如果發(fā)生了錯(cuò)誤,只需將錯(cuò)誤傳遞給 done(),F(xiàn)astify 就會(huì)自動(dòng)關(guān)閉請(qǐng)求,并發(fā)送一個(gè)相應(yīng)的錯(cuò)誤碼給用戶(hù)。
fastify.addHook('onRequest', (request, reply, done) => {
done(new Error('Some error'))
})
如果你想自定義發(fā)送給用戶(hù)的錯(cuò)誤碼,使用 reply.code() 即可:
fastify.addHook('preHandler', (request, reply, done) => {
reply.code(400)
done(new Error('Some error'))
})
錯(cuò)誤最終會(huì)在 Reply 中得到處理。
需要的話,你可以在路由控制器執(zhí)行前響應(yīng)一個(gè)請(qǐng)求,例如進(jìn)行身份驗(yàn)證。如果你在 onRequest 或 preHandler 中發(fā)出響應(yīng),請(qǐng)使用 reply.send。如果是在中間件中,使用 res.end。
fastify.addHook('onRequest', (request, reply, done) => {
reply.send('Early response')
})
// 也可使用 async 函數(shù)
fastify.addHook('preHandler', async (request, reply) => {
reply.send({ hello: 'world' })
})
如果你想要使用流 (stream) 來(lái)響應(yīng)請(qǐng)求,你應(yīng)該避免使用 async 函數(shù)。必須使用 async 函數(shù)的話,請(qǐng)參考 test/hooks-async.js 中的示例來(lái)編寫(xiě)代碼。
fastify.addHook('onRequest', (request, reply, done) => {
const stream = fs.createReadStream('some-file', 'utf8')
reply.send(stream)
})
你也可以在應(yīng)用的生命周期里使用鉤子方法。要格外注意的是,這些鉤子并未被完全封裝。鉤子中的 this 得到了封裝,但處理函數(shù)可以響應(yīng)封裝界線外的事件。
使用 fastify.close() 停止服務(wù)器時(shí)被觸發(fā)。當(dāng)插件需要一個(gè) "shutdown" 事件時(shí)有用,例如關(guān)閉一個(gè)數(shù)據(jù)庫(kù)連接。該鉤子的第一個(gè)參數(shù)是 Fastify 實(shí)例,第二個(gè)為 done 回調(diào)函數(shù)。
fastify.addHook('onClose', (instance, done) => {
// 其他代碼
done()
})
當(dāng)注冊(cè)一個(gè)新的路由時(shí)被觸發(fā)。它的監(jiān)聽(tīng)函數(shù)擁有一個(gè)唯一的參數(shù):routeOptions 對(duì)象。該函數(shù)是同步的,其本身并不接受回調(diào)作為參數(shù)。
fastify.addHook('onRoute', (routeOptions) => {
// 其他代碼
routeOptions.method
routeOptions.schema
routeOptions.url
routeOptions.bodyLimit
routeOptions.logLevel
routeOptions.logSerializers
routeOptions.prefix
})
如果在編寫(xiě)插件時(shí),需要自定義程序的路由,比如修改選項(xiàng)或添加新的路由層鉤子,你可以在這里添加。
fastify.addHook('onRoute', (routeOptions) => {
function onPreSerialization(request, reply, payload, done) {
// 其他代碼
done(null, payload)
}
// preSerialization 可以是數(shù)組或 undefined
routeOptions.preSerialization = [...(routeOptions.preSerialization || []), onPreSerialization]
})
當(dāng)注冊(cè)一個(gè)新的插件,或創(chuàng)建了新的封裝好的上下文后被觸發(fā)。該鉤子在注冊(cè)的代碼之前被執(zhí)行。當(dāng)你的插件需要知曉上下文何時(shí)創(chuàng)建完畢,并操作它們時(shí),可以使用這一鉤子。注意:被 fastify-plugin 所封裝的插件不會(huì)觸發(fā)該鉤子。
fastify.decorate('data', [])
fastify.register(async (instance, opts) => {
instance.data.push('hello')
console.log(instance.data) // ['hello']
instance.register(async (instance, opts) => {
instance.data.push('world')
console.log(instance.data) // ['hello', 'world']
}), { prefix: '/hola' })
}), { prefix: '/ciao' })
fastify.register(async (instance, opts) => {
console.log(instance.data) // []
}), { prefix: '/hello' })
fastify.addHook('onRegister', (instance, opts) => {
// 從舊數(shù)組淺拷貝,
// 生成一個(gè)新數(shù)組,
// 使用戶(hù)獲得一個(gè)
// 封裝好的 `data` 的實(shí)例
instance.data = instance.data.slice()
// 新注冊(cè)實(shí)例的選項(xiàng)
console.log(opts.prefix)
})
除了應(yīng)用鉤子,所有的鉤子都是封裝好的。這意味著你可以通過(guò) register 來(lái)決定在何處運(yùn)行它們,正如插件指南所述。如果你傳遞一個(gè)函數(shù),那么該函數(shù)會(huì)獲得 Fastify 的上下文,如此你便能使用 Fastify 的 API 了。
fastify.addHook('onRequest', function (request, reply, done) {
const self = this // Fastify 上下文
done()
})
注:使用箭頭函數(shù)會(huì)破壞 Fastify 實(shí)例對(duì) this 的綁定。
你可以為單個(gè)路由聲明一個(gè)或多個(gè)自定義的 onRequest、onReponse、preParsing、 preValidation、preHandler 與 preSerialization 鉤子。 如果你這么做,這些鉤子總是會(huì)作為同一類(lèi)鉤子中的最后一個(gè)被執(zhí)行。當(dāng)你需要進(jìn)行認(rèn)證時(shí),這會(huì)很有用,而 preParsing 與 preValidation 鉤子正是為此而生。 你也可以通過(guò)數(shù)組定義多個(gè)路由層鉤子。
fastify.addHook('onRequest', (request, reply, done) => {
// 你的代碼
done()
})
fastify.addHook('onResponse', (request, reply, done) => {
// 你的代碼
done()
})
fastify.addHook('preParsing', (request, reply, done) => {
// 你的代碼
done()
})
fastify.addHook('preValidation', (request, reply, done) => {
// 你的代碼
done()
})
fastify.addHook('preHandler', (request, reply, done) => {
// 你的代碼
done()
})
fastify.addHook('preSerialization', (request, reply, payload, done) => {
// 你的代碼
done(null, payload)
})
fastify.route({
method: 'GET',
url: '/',
schema: { ... },
onRequest: function (request, reply, done) {
// 該鉤子總是在共享的 `onRequest` 鉤子后被執(zhí)行
done()
},
onResponse: function (request, reply, done) {
// 該鉤子總是在共享的 `onResponse` 鉤子后被執(zhí)行
done()
},
preParsing: function (request, reply, done) {
// 該鉤子總是在共享的 `preParsing` 鉤子后被執(zhí)行
done()
},
preValidation: function (request, reply, done) {
// 該鉤子總是在共享的 `preValidation` 鉤子后被執(zhí)行
done()
},
preHandler: function (request, reply, done) {
// 該鉤子總是在共享的 `preHandler` 鉤子后被執(zhí)行
done()
},
// // 使用數(shù)組的例子。所有鉤子都支持這一語(yǔ)法。
//
// preHandler: [function (request, reply, done) {
// // 該鉤子總是在共享的 `preHandler` 鉤子后被執(zhí)行
// done()
// }],
preSerialization: (request, reply, payload, done) => {
// 該鉤子總是在共享的 `preSerialization` 鉤子后被執(zhí)行
done(null, payload)
},
handler: function (request, reply) {
reply.send({ hello: 'world' })
}
})
注:兩個(gè)選項(xiàng)都接受一個(gè)函數(shù)數(shù)組作為參數(shù)。
更多建議: