フック
フック
フックは fastify.addHook
メソッドで登録され、アプリケーションまたはリクエスト/レスポンスのライフサイクル内の特定のイベントをリッスンできます。イベントが発生する前にフックを登録する必要があります。そうしないと、イベントは失われます。
フックを使用すると、Fastify のライフサイクルを直接操作できます。リクエスト/レスポンスフックとアプリケーションフックがあります。
注意: async
/await
または Promise
を返す場合は、done
コールバックは使用できません。この状況で done
コールバックを呼び出すと、予期しない動作 (ハンドラーの重複呼び出しなど) が発生する可能性があります。
リクエスト/レスポンスフック
リクエスト と 応答 は、Fastify のコアオブジェクトです。
done
は、ライフサイクル を続行するための関数です。
ライフサイクルページ を見ると、各フックがどこで実行されるかを簡単に理解できます。
フックは Fastify のカプセル化の影響を受けるため、選択したルートに適用できます。詳細については、スコープ セクションを参照してください。
リクエスト/レスポンスで使用できる 8 つの異なるフックがあります (実行順)。
onRequest
fastify.addHook('onRequest', (request, reply, done) => {
// Some code
done()
})
または async/await
fastify.addHook('onRequest', async (request, reply) => {
// Some code
await asyncMethod()
})
注意: onRequest フックでは、本体の解析は preValidation フックの前に行われるため、request.body
は常に undefined
になります。
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 フックでは、本体の解析は preValidation フックの前に行われるため、request.body
は常に undefined
になります。
注意: 返されたストリームに 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()
})
onTimeout
は、サービスでリクエストがタイムアウトした場合に監視する必要がある場合に役立ちます (Fastify インスタンスで connectionTimeout
プロパティが設定されている場合)。onTimeout
フックは、リクエストがタイムアウトし、HTTP ソケットがハングアップした場合に実行されます。したがって、クライアントにデータを送信することはできません。
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
の場合、reply.send()
は関数が返る前または Promise が解決される前に呼び出す必要があります。それ以外の場合、リクエストは続行されます。reply.send()
が Promise チェーンの外部で呼び出された場合は、リクエストが 2 回実行されるのを防ぐために、必ず return reply
を指定することが重要です。
コールバックと async
/Promise
を混合しないことが重要です。そうしないと、フックチェーンが 2 回実行されます。
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()
が呼び出されたときにトリガーされます。ルートを変更したり、新しいフックを追加したりすることはできません。登録されたフック関数は順番に実行されます。すべての onReady
フック関数が完了した後にのみ、サーバーはリクエストをリッスンし始めます。フック関数は、1 つの引数 (フック関数が完了した後に呼び出されるコールバック done
) を受け入れます。フック関数は、関連付けられた Fastify インスタンスにバインドされた this
で呼び出されます。
// 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
サーバーがリクエストのリスニングを開始したときにトリガーされます。フックは順番に実行されます。フック関数がエラーを引き起こした場合、ログに記録され、無視されます。これにより、フックのキューは続行できます。フック関数は、フック関数の完了後に呼び出されるコールバックdone
を引数として1つ受け取ります。フック関数は、関連付けられた Fastify インスタンスにバインドされたthis
で呼び出されます。
これは、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()
を使用してサーバーが起動された場合には実行されません。
onClose
fastify.close()
が呼び出されてサーバーが停止されるとき、すべて進行中のHTTPリクエストが完了した後にトリガーされます。これは、プラグインが、例えばデータベースへのオープンな接続を閉じるための「シャットダウン」イベントを必要とする場合に役立ちます。
フック関数は、最初の引数としてFastifyインスタンスを、同期フック関数用の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サーバーにアタッチされたサーバーのシャットダウンを妨げる可能性のある状態を設定している場合に役立ちます。このフックを使用する必要はおそらくないでしょう。最も一般的なケースでは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
オブジェクトが渡されます。インターフェイスは同期的であり、そのため、リスナーにコールバックは渡されません。このフックはカプセル化されています。
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
})
プラグインを作成しており、オプションの変更や新しいルートフックの追加など、アプリケーションのルートをカスタマイズする必要がある場合、ここが適切な場所です。
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フック内でさらにルートを追加するには、ルートに正しくタグ付けする必要があります。タグ付けされていない場合、フックは無限ループに陥ります。推奨されるアプローチを以下に示します。
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
新しいプラグインが登録され、新しいカプセル化コンテキストが作成されたときにトリガーされます。フックは登録されたコードの前に実行されます。
このフックは、プラグインコンテキストが形成されたときに知る必要があり、その特定のコンテキストで操作したい場合に役立つ可能性があります。したがって、このフックはカプセル化されています。
注意: プラグインがfastify-plugin
内にラップされている場合、このフックは呼び出されません。
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)
})
スコープ
onCloseを除き、すべてのフックはカプセル化されています。つまり、プラグインガイドで説明されているように、register
を使用することでフックを実行する場所を決定できます。関数を渡すと、その関数は適切なFastifyコンテキストにバインドされ、そこからFastify APIへのフルアクセスが可能になります。
fastify.addHook('onRequest', function (request, reply, done) {
const self = this // Fastify context
done()
})
各フックの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ではなく、現在のスコープのものがになります。
ルートレベルフック
ルートに対して一意となる1つ以上のカスタムライフサイクルフック(onRequest、onResponse、preParsing、preValidation、preHandler、preSerialization、onSend、onTimeout、およびonError)を宣言できます。その場合、これらのフックは常に、そのカテゴリの最後のフックとして実行されます。
これは、認証を実装する必要がある場合に役立ちます。ここでは、preParsingまたはpreValidationフックがまさに必要なものです。複数のルートレベルフックを配列として指定することもできます。
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' })
}
})
注意:どちらのオプションも関数の配列を受け入れます。
フックを使用したカスタムプロパティの挿入
フックを使用して、カスタムプロパティを受信リクエストに挿入できます。これは、コントローラーでフックから処理されたデータを再利用する場合に便利です。
非常に一般的なユースケースは、例えば、ユーザーのトークンに基づいてユーザー認証をチェックし、復元されたデータを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;
}
}
これは非常に実用的なアプローチですが、これらのコアオブジェクトを変更するより複雑なことを試みている場合は、代わりにカスタムプラグインを作成することを検討してください。
診断チャネルフック
1つのdiagnostics_channel
publishイベントである'fastify.initialization'
は、初期化時に発生します。Fastifyインスタンスは、渡されるオブジェクトのプロパティとしてフックに渡されます。この時点で、フック、プラグイン、ルート、またはその他の種類の変更を追加するためにインスタンスを操作できます。
例えば、トレースパッケージは、次のような処理を行う可能性があります(もちろん、これは簡略化されたものです)。これは、追跡パッケージの初期化時にロードされるファイル内で、一般的な「最初にインストルメント化ツールを要求する」方法で行われます。
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 Channelの命名規則に従って、リクエストごとに他の5つのイベントが発行されます。チャネル名と受信するイベントのリストは次のとおりです。
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 }
オブジェクトインスタンスは、特定のリクエストに関連付けられたすべてのイベントで同じままです。すべてのペイロードには、FastifyのRequest
およびReply
インスタンスであるrequest
およびreply
プロパティが含まれています。また、一致するurl
パターン(例:/collection/:id
)とmethod
HTTPメソッド(例:GET
)を持つオブジェクトであるroute
プロパティも含まれています。: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)
})