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