鉤子
鉤子
鉤子透過 fastify.addHook
方法註冊,並允許您監聽應用程式或請求/回應生命週期中的特定事件。您必須在事件觸發之前註冊鉤子,否則事件將遺失。
透過使用鉤子,您可以直接與 Fastify 的生命週期互動。有請求/回應鉤子和應用程式鉤子
注意:當使用 async
/await
或返回 Promise
時,done
回調不可用。 如果在這種情況下調用 done
回調,可能會發生意外行為,例如重複調用處理程式。
請求/回應鉤子
done
是一個函式,用於繼續生命週期。
透過查看生命週期頁面,很容易理解每個鉤子的執行位置。
鉤子會受到 Fastify 封裝的影響,因此可以應用於選定的路由。 有關更多資訊,請參閱範圍部分。
您可以在請求/回應中使用八個不同的鉤子 (按執行順序)
onRequest
fastify.addHook('onRequest', (request, reply, done) => {
// Some code
done()
})
或 async/await
fastify.addHook('onRequest', async (request, reply) => {
// Some code
await asyncMethod()
})
注意:在onRequest 鉤子中,request.body
將始終為 undefined
,因為主體解析發生在preValidation 鉤子之前。
preParsing
如果您使用 preParsing
鉤子,則可以在解析之前轉換請求酬載串流。 它接收請求和回覆物件作為其他鉤子,以及具有目前請求酬載的串流。
如果它返回一個值(透過 return
或透過回呼函式),則必須返回一個串流。
例如,您可以解壓縮請求主體
fastify.addHook('preParsing', (request, reply, payload, done) => {
// Some code
done(null, newPayload)
})
或 async/await
fastify.addHook('preParsing', async (request, reply, payload) => {
// Some code
await asyncMethod()
return newPayload
})
注意:在preParsing 鉤子中,request.body
將始終為 undefined
,因為主體解析發生在preValidation 鉤子之前。
注意:您還應該為返回的串流新增一個 receivedEncodedLength
屬性。 此屬性用於將請求酬載與 Content-Length
標頭值正確匹配。 理想情況下,此屬性應在每個收到的區塊上更新。
注意:會檢查返回的串流大小是否超過bodyLimit
選項中設定的限制。
preValidation
如果您使用 preValidation
鉤子,則可以在驗證之前變更酬載。 例如
fastify.addHook('preValidation', (request, reply, done) => {
request.body = { ...request.body, importantKey: 'randomString' }
done()
})
或 async/await
fastify.addHook('preValidation', async (request, reply) => {
const importantKey = await generateRandomString()
request.body = { ...request.body, importantKey }
})
preHandler
preHandler
鉤子允許您指定在路由的處理程式之前執行的函式。
fastify.addHook('preHandler', (request, reply, done) => {
// some code
done()
})
或 async/await
fastify.addHook('preHandler', async (request, reply) => {
// Some code
await asyncMethod()
})
preSerialization
如果您使用 preSerialization
鉤子,則可以在序列化之前變更(或取代)酬載。 例如
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 }
})
注意:如果酬載是 string
、Buffer
、stream
或 null
,則不會呼叫此鉤子。
onError
fastify.addHook('onError', (request, reply, error, done) => {
// Some code
done()
})
或 async/await
fastify.addHook('onError', async (request, reply, error) => {
// Useful for custom error logging
// You should not use this hook to update the error
})
如果您需要在發生錯誤時執行一些自訂錯誤記錄或新增一些特定標頭,則此鉤子非常有用。
它不適用於變更錯誤,呼叫 reply.send
將會擲回例外狀況。
此鉤子僅在執行setErrorHandler
設定的自訂錯誤處理程式之後執行,並且僅在自訂錯誤處理程式將錯誤傳送回給使用者時執行 (請注意,預設錯誤處理程式始終將錯誤傳送回給使用者)。
注意:與其他鉤子不同,不支援將錯誤傳遞給 done
函式。
onSend
如果您使用 onSend
鉤子,則可以變更酬載。 例如
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
})
您也可以將酬載替換為 null
,以清除酬載並傳送具有空白主體的回應
fastify.addHook('onSend', (request, reply, payload, done) => {
reply.code(304)
const newPayload = null
done(null, newPayload)
})
您也可以透過將酬載替換為空字串
''
來傳送空白主體,但請注意,這將導致Content-Length
標頭設定為0
,而如果酬載為null
,則不會設定Content-Length
標頭。
注意:如果您變更酬載,則只能將其變更為 string
、Buffer
、stream
、ReadableStream
、Response
或 null
。
onResponse
fastify.addHook('onResponse', (request, reply, done) => {
// Some code
done()
})
或 async/await
fastify.addHook('onResponse', async (request, reply) => {
// Some code
await asyncMethod()
})
當傳送回應時,會執行 onResponse
鉤子,因此您將無法再向用戶端傳送更多資料。 不過,它對於向外部服務傳送資料(例如,收集統計資料)可能很有用。
注意:將 disableRequestLogging
設定為 true
將會停用 onResponse
鉤子內的任何錯誤記錄。 在這種情況下,請使用 try - catch
來記錄錯誤。
onTimeout
fastify.addHook('onTimeout', (request, reply, done) => {
// Some code
done()
})
或 async/await
fastify.addHook('onTimeout', async (request, reply) => {
// Some code
await asyncMethod()
})
如果您需要監控服務中請求逾時的情況(如果在 Fastify 執行個體上設定了 connectionTimeout
屬性),則 onTimeout
非常有用。 當請求逾時且 HTTP Socket 已掛斷時,會執行 onTimeout
鉤子。 因此,您將無法向用戶端傳送資料。
onRequestAbort
fastify.addHook('onRequestAbort', (request, done) => {
// Some code
done()
})
或 async/await
fastify.addHook('onRequestAbort', async (request) => {
// Some code
await asyncMethod()
})
當用戶端在處理完整請求之前關閉連線時,會執行 onRequestAbort
鉤子。 因此,您將無法向用戶端傳送資料。
注意:用戶端中止偵測並非完全可靠。 請參閱:Detecting-When-Clients-Abort.md
從鉤子管理錯誤
如果在執行鉤子的過程中發生錯誤,只需將其傳遞給 done()
,Fastify 就會自動關閉請求並將適當的錯誤碼傳送給使用者。
fastify.addHook('onRequest', (request, reply, done) => {
done(new Error('Some error'))
})
如果您想將自訂錯誤碼傳遞給使用者,只需使用 reply.code()
fastify.addHook('preHandler', (request, reply, done) => {
reply.code(400)
done(new Error('Some error'))
})
該錯誤將由Reply
處理。
或者,如果您使用 async/await
,則只需擲回錯誤即可
fastify.addHook('onRequest', async (request, reply) => {
throw new Error('Some error')
})
從鉤子回應請求
如果需要,您可以在到達路由處理程式之前回應請求,例如在實作驗證鉤子時。 從鉤子回覆表示鉤子鏈會停止,並且不會執行其餘的鉤子和處理程式。 如果鉤子使用回呼方法,也就是說,它不是 async
函式或它返回 Promise
,則只需呼叫 reply.send()
並避免呼叫回呼即可。 如果鉤子是 async
,則必須在函式返回或 Promise 解析之前呼叫 reply.send()
,否則請求將會繼續。 當在 Promise 鏈之外呼叫 reply.send()
時,請務必 return reply
,否則請求將會執行兩次。
請務必不要混合回呼和 async
/Promise
,否則鉤子鏈將會執行兩次。
如果您使用 onRequest
或 preHandler
,請使用 reply.send
。
fastify.addHook('onRequest', (request, reply, done) => {
reply.send('Early response')
})
// Works with async functions too
fastify.addHook('preHandler', async (request, reply) => {
setTimeout(() => {
reply.send({ hello: 'from prehandler' })
})
return reply // mandatory, so the request is not executed further
// Commenting the line above will allow the hooks to continue and fail with FST_ERR_REP_ALREADY_SENT
})
如果您想使用串流回應,則應避免為鉤子使用 async
函式。 如果您必須使用 async
函式,則程式碼需要遵循 test/hooks-async.js 中的模式。
fastify.addHook('onRequest', (request, reply, done) => {
const stream = fs.createReadStream('some-file', 'utf8')
reply.send(stream)
})
如果您在沒有 await
的情況下傳送回應,請務必始終 return reply
fastify.addHook('preHandler', async (request, reply) => {
setImmediate(() => { reply.send('hello') })
// This is needed to signal the handler to wait for a response
// to be sent outside of the promise chain
return reply
})
fastify.addHook('preHandler', async (request, reply) => {
// the @fastify/static plugin will send a file asynchronously,
// so we should return reply
reply.sendFile('myfile')
return reply
})
應用程式鉤子
您也可以掛接到應用程式生命週期中。
onReady
在伺服器開始監聽請求之前,以及當 .ready()
被調用時觸發。它無法變更路由或新增新的 hook。已註冊的 hook 函式會依序執行。只有在所有 onReady
hook 函式完成後,伺服器才會開始監聽請求。Hook 函式接受一個參數:一個回呼函式 done
,在 hook 函式完成後調用。Hook 函式會將 this
綁定到相關的 Fastify 實例上調用。
// callback style
fastify.addHook('onReady', function (done) {
// Some code
const err = null;
done(err)
})
// or async/await style
fastify.addHook('onReady', async function () {
// Some async code
await loadCacheFromDatabase()
})
onListen
在伺服器開始監聽請求時觸發。這些 hook 會依序執行。如果一個 hook 函式導致錯誤,它會被記錄並忽略,允許 hook 佇列繼續執行。Hook 函式接受一個參數:一個回呼函式 done
,在 hook 函式完成後調用。Hook 函式會將 this
綁定到相關的 Fastify 實例上調用。
這是 fastify.server.on('listening', () => {})
的替代方案。
// callback style
fastify.addHook('onListen', function (done) {
// Some code
const err = null;
done(err)
})
// or async/await style
fastify.addHook('onListen', async function () {
// Some async code
})
注意
當伺服器使用fastify.inject()
或fastify.ready()
啟動時,此 hook 不會執行。
onClose
當調用 fastify.close()
以停止伺服器時觸發,在所有正在處理中的 HTTP 請求都完成之後。當 外掛 需要一個「關閉」事件時很有用,例如,關閉與資料庫的開啟連線。
hook 函式將 Fastify 實例作為第一個參數,並為同步 hook 函式提供 done
回呼。
// callback style
fastify.addHook('onClose', (instance, done) => {
// Some code
done()
})
// or async/await style
fastify.addHook('onClose', async (instance) => {
// Some async code
await closeDatabaseConnections()
})
preClose
當調用 fastify.close()
以停止伺服器時觸發,在所有正在處理中的 HTTP 請求完成之前。當 外掛 設定了一些附加到 HTTP 伺服器的狀態,而這些狀態會阻止伺服器關閉時,這很有用。*您不太可能需要使用此 hook*,在最常見的情況下,請使用 onClose
。
// callback style
fastify.addHook('preClose', (done) => {
// Some code
done()
})
// or async/await style
fastify.addHook('preClose', async () => {
// Some async code
await removeSomeServerState()
})
onRoute
當註冊新路由時觸發。監聽器會被傳遞一個 routeOptions
物件作為唯一參數。介面是同步的,因此,不會向監聽器傳遞回呼函式。此 hook 是封裝的。
fastify.addHook('onRoute', (routeOptions) => {
//Some code
routeOptions.method
routeOptions.schema
routeOptions.url // the complete URL of the route, it will include the prefix if any
routeOptions.path // `url` alias
routeOptions.routePath // the URL of the route without the prefix
routeOptions.bodyLimit
routeOptions.logLevel
routeOptions.logSerializers
routeOptions.prefix
})
如果您正在編寫外掛程式,並且需要自訂應用程式路由,例如修改選項或新增新的路由 hook,那麼這裡就是正確的地方。
fastify.addHook('onRoute', (routeOptions) => {
function onPreSerialization(request, reply, payload, done) {
// Your code
done(null, payload)
}
// preSerialization can be an array or undefined
routeOptions.preSerialization = [...(routeOptions.preSerialization || []), onPreSerialization]
})
要在 onRoute hook 內新增更多路由,必須正確標記路由。如果未標記,則 hook 將進入無限迴圈。建議的方法如下所示。
const kRouteAlreadyProcessed = Symbol('route-already-processed')
fastify.addHook('onRoute', function (routeOptions) {
const { url, method } = routeOptions
const isAlreadyProcessed = (routeOptions.custom && routeOptions.custom[kRouteAlreadyProcessed]) || false
if (!isAlreadyProcessed) {
this.route({
url,
method,
custom: {
[kRouteAlreadyProcessed]: true
},
handler: () => {}
})
}
})
有關更多詳細資訊,請參閱此 issue。
onRegister
當註冊新外掛程式並建立新的封裝上下文時觸發。此 hook 將在註冊的程式碼 **之前** 執行。
如果您正在開發一個需要知道何時形成外掛程式上下文的外掛程式,並且您希望在該特定上下文中操作,那麼此 hook 很有用,因此此 hook 是封裝的。
注意: 如果外掛程式包裝在 fastify-plugin
內,則不會呼叫此 hook。
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) => {
// Create a new array from the old one
// but without keeping the reference
// allowing the user to have encapsulated
// instances of the `data` property
instance.data = instance.data.slice()
// the options of the new registered instance
console.log(opts.prefix)
})
Scope
除了 onClose 之外,所有 hook 都是封裝的。這表示您可以決定您的 hook 應該在哪裡運行,方法是使用 外掛程式指南 中說明的 register
。如果您傳遞一個函式,該函式會綁定到正確的 Fastify 上下文,並且您可以從那裡完全存取 Fastify API。
fastify.addHook('onRequest', function (request, reply, done) {
const self = this // Fastify context
done()
})
請注意,每個 hook 中的 Fastify 上下文與註冊路由的外掛程式相同,例如
fastify.addHook('onRequest', async function (req, reply) {
if (req.raw.url === '/nested') {
assert.strictEqual(this.foo, 'bar')
} else {
assert.strictEqual(this.foo, undefined)
}
})
fastify.get('/', async function (req, reply) {
assert.strictEqual(this.foo, undefined)
return { hello: 'world' }
})
fastify.register(async function plugin (fastify, opts) {
fastify.decorate('foo', 'bar')
fastify.get('/nested', async function (req, reply) {
assert.strictEqual(this.foo, 'bar')
return { hello: 'world' }
})
})
警告:如果您使用 箭頭函式 宣告函式,則 this
將不會是 Fastify,而是當前範圍的物件。
路由級別 hook
您可以宣告一個或多個自訂生命週期 hook (onRequest、onResponse、preParsing、preValidation、preHandler、preSerialization、onSend、onTimeout 和 onError) hook,這些 hook 對於路由將是 **唯一** 的。如果您這樣做,這些 hook 始終作為其類別中的最後一個 hook 執行。
如果您需要實作身份驗證,其中 preParsing 或 preValidation hook 正是您需要的,這會很有用。也可以將多個路由級別的 hook 指定為陣列。
fastify.addHook('onRequest', (request, reply, done) => {
// Your code
done()
})
fastify.addHook('onResponse', (request, reply, done) => {
// your code
done()
})
fastify.addHook('preParsing', (request, reply, done) => {
// Your code
done()
})
fastify.addHook('preValidation', (request, reply, done) => {
// Your code
done()
})
fastify.addHook('preHandler', (request, reply, done) => {
// Your code
done()
})
fastify.addHook('preSerialization', (request, reply, payload, done) => {
// Your code
done(null, payload)
})
fastify.addHook('onSend', (request, reply, payload, done) => {
// Your code
done(null, payload)
})
fastify.addHook('onTimeout', (request, reply, done) => {
// Your code
done()
})
fastify.addHook('onError', (request, reply, error, done) => {
// Your code
done()
})
fastify.route({
method: 'GET',
url: '/',
schema: { ... },
onRequest: function (request, reply, done) {
// This hook will always be executed after the shared `onRequest` hooks
done()
},
// // Example with an async hook. All hooks support this syntax
//
// onRequest: async function (request, reply) {
// // This hook will always be executed after the shared `onRequest` hooks
// await ...
// }
onResponse: function (request, reply, done) {
// this hook will always be executed after the shared `onResponse` hooks
done()
},
preParsing: function (request, reply, done) {
// This hook will always be executed after the shared `preParsing` hooks
done()
},
preValidation: function (request, reply, done) {
// This hook will always be executed after the shared `preValidation` hooks
done()
},
preHandler: function (request, reply, done) {
// This hook will always be executed after the shared `preHandler` hooks
done()
},
// // Example with an array. All hooks support this syntax.
//
// preHandler: [function (request, reply, done) {
// // This hook will always be executed after the shared `preHandler` hooks
// done()
// }],
preSerialization: (request, reply, payload, done) => {
// This hook will always be executed after the shared `preSerialization` hooks
done(null, payload)
},
onSend: (request, reply, payload, done) => {
// This hook will always be executed after the shared `onSend` hooks
done(null, payload)
},
onTimeout: (request, reply, done) => {
// This hook will always be executed after the shared `onTimeout` hooks
done()
},
onError: (request, reply, error, done) => {
// This hook will always be executed after the shared `onError` hooks
done()
},
handler: function (request, reply) {
reply.send({ hello: 'world' })
}
})
注意:這兩個選項也都接受函式陣列。
使用 Hook 注入自訂屬性
您可以使用 hook 將自訂屬性注入到傳入的請求中。這對於重複使用控制器中 hook 處理的資料很有用。
一個非常常見的用例是,例如,根據使用者的權杖檢查使用者身份驗證,然後將其恢復的資料儲存到 Request 實例中。這樣,您的控制器就可以使用 request.authenticatedUser
或您想要呼叫的任何名稱輕鬆讀取它。它可能看起來像這樣
fastify.addHook('preParsing', async (request) => {
request.authenticatedUser = {
id: 42,
name: 'Jane Doe',
role: 'admin'
}
})
fastify.get('/me/is-admin', async function (req, reply) {
return { isAdmin: req.authenticatedUser?.role === 'admin' || false }
})
請注意,.authenticatedUser
實際上可以是您自己選擇的任何屬性名稱。使用您自己的自訂屬性可以防止您變更現有的屬性,這將是一種危險且具破壞性的操作。因此請小心,並確保您的屬性是全新的,並且僅將此方法用於非常特殊和小的情況,例如此範例。
關於此範例中的 TypeScript,您需要更新 FastifyRequest
核心介面以包含您新的屬性類型 (有關更多資訊,請參閱 TypeScript 頁面),例如
interface AuthenticatedUser { /* ... */ }
declare module 'fastify' {
export interface FastifyRequest {
authenticatedUser?: AuthenticatedUser;
}
}
雖然這是一種非常務實的方法,但如果您嘗試執行更複雜的操作,會變更這些核心物件,請考慮建立自訂的 外掛程式。
診斷通道 Hook
在初始化時會發生一個 diagnostics_channel
發佈事件,'fastify.initialization'
。Fastify 實例會作為傳入物件的屬性傳遞到 hook 中。此時,可以與該實例互動以新增 hook、外掛程式、路由或任何其他類型的修改。
例如,追蹤套件可能會執行以下操作 (這當然是一個簡化版本)。這將在追蹤套件初始化時載入的檔案中,以典型的「先要求檢測工具」的方式。
const tracer = /* retrieved from elsewhere in the package */
const dc = require('node:diagnostics_channel')
const channel = dc.channel('fastify.initialization')
const spans = new WeakMap()
channel.subscribe(function ({ fastify }) {
fastify.addHook('onRequest', (request, reply, done) => {
const span = tracer.startSpan('fastify.request.handler')
spans.set(request, span)
done()
})
fastify.addHook('onResponse', (request, reply, done) => {
const span = spans.get(request)
span.finish()
done()
})
})
注意: TracingChannel 類別 API 目前處於實驗階段,即使在 Node.js 的 semver 修補程式版本中也可能會發生重大變更。
另外五個事件會按照 追蹤通道 命名法,按每個請求發布。通道名稱列表及其接收的事件為
tracing:fastify.request.handler:start
:始終觸發{ request: Request, reply: Reply, route: { url, method } }
tracing:fastify.request.handler:end
:始終觸發{ request: Request, reply: Reply, route: { url, method }, async: Bool }
tracing:fastify.request.handler:asyncStart
:觸發用於 promise/async 處理程式{ request: Request, reply: Reply, route: { url, method } }
tracing:fastify.request.handler:asyncEnd
:觸發用於 promise/async 處理程式{ request: Request, reply: Reply, route: { url, method } }
tracing:fastify.request.handler:error
:發生錯誤時觸發{ request: Request, reply: Reply, route: { url, method }, error: Error }
對於與給定請求相關的所有事件,物件實例保持不變。所有酬載都包含 request
和 reply
屬性,它們是 Fastify 的 Request
和 Reply
實例。它們還包含一個 route
屬性,它是一個包含相符 url
模式 (例如 /collection/:id
) 和 method
HTTP 方法 (例如 GET
) 的物件。:start
和 :end
事件始終針對請求觸發。如果請求處理程式是一個 async
函式或返回 Promise
的函式,則也會觸發 :asyncStart
和 :asyncEnd
事件。最後,:error
事件包含一個與請求失敗相關的 error
屬性。
這些事件可以像這樣接收
const dc = require('node:diagnostics_channel')
const channel = dc.channel('tracing:fastify.request.handler:start')
channel.subscribe((msg) => {
console.log(msg.request, msg.reply)
})