跳至主要內容
版本:最新 (v5.0.x)

鉤子

鉤子

鉤子透過 fastify.addHook 方法註冊,並允許您監聽應用程式或請求/回應生命週期中的特定事件。您必須在事件觸發之前註冊鉤子,否則事件將遺失。

透過使用鉤子,您可以直接與 Fastify 的生命週期互動。有請求/回應鉤子和應用程式鉤子

注意:當使用 async/await 或返回 Promise 時,done 回調不可用。 如果在這種情況下調用 done 回調,可能會發生意外行為,例如重複調用處理程式。

請求/回應鉤子

請求回覆是 Fastify 的核心物件。

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 }
})

注意:如果酬載是 stringBufferstreamnull,則不會呼叫此鉤子。

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 標頭。

注意:如果您變更酬載,則只能將其變更為 stringBufferstreamReadableStreamResponsenull

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,否則鉤子鏈將會執行兩次。

如果您使用 onRequestpreHandler,請使用 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 (onRequestonResponsepreParsingpreValidationpreHandlerpreSerializationonSendonTimeoutonError) hook,這些 hook 對於路由將是 **唯一** 的。如果您這樣做,這些 hook 始終作為其類別中的最後一個 hook 執行。

如果您需要實作身份驗證,其中 preParsingpreValidation 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 }

對於與給定請求相關的所有事件,物件實例保持不變。所有酬載都包含 requestreply 屬性,它們是 Fastify 的 RequestReply 實例。它們還包含一個 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)
})