Skip to main content

钩子

钩子

¥Hooks

钩子使用 fastify.addHook 方法注册,允许你监听应用或请求/响应生命周期中的特定事件。你必须在事件触发之前注册一个钩子,否则事件将丢失。

¥Hooks are registered with the fastify.addHook method and allow you to listen to specific events in the application or request/response lifecycle. You have to register a hook before the event is triggered, otherwise, the event is lost.

通过使用钩子,你可以直接与 Fastify 的生命周期进行交互。有请求/响应钩子和应用钩子:

¥By using hooks you can interact directly with the lifecycle of Fastify. There are Request/Reply hooks and application hooks:

注意:使用 async/await 或返回 Promise 时,done 回调不可用。如果在这种情况下调用 done 回调,可能会发生意外行为,例如重复调用处理程序。

¥Notice: the done callback is not available when using async/await or returning a Promise. If you do invoke a done callback in this situation unexpected behavior may occur, e.g. duplicate invocation of handlers.

Request/Reply 钩子

¥Request/Reply Hooks

请求响应 是核心 Fastify 对象。

¥Request and Reply are the core Fastify objects.

done 是继续 lifecycle 的函数。

¥done is the function to continue with the lifecycle.

通过查看 生命周期页面,很容易理解每​​个钩子在哪里执行。

¥It is easy to understand where each hook is executed by looking at the lifecycle page.

Hooks 受到 Fastify 封装的影响,因此可以应用于选定的路由。有关更多信息,请参阅 Scopes 部分。

¥Hooks are affected by Fastify's encapsulation, and can thus be applied to selected routes. See the Scopes section for more information.

你可以在请求/响应中使用八种不同的钩子(按执行顺序):

¥There are eight different hooks that you can use in Request/Reply (in order of execution):

onRequest

fastify.addHook('onRequest', (request, reply, done) => {
// Some code
done()
})

async/await

¥Or async/await:

fastify.addHook('onRequest', async (request, reply) => {
// Some code
await asyncMethod()
})

注意:在 onRequest 钩子中,request.body 将始终是 undefined,因为主体解析发生在 preValidation 钩子之前。

¥Notice: in the onRequest hook, request.body will always be undefined, because the body parsing happens before the preValidation hook.

preParsing

如果你正在使用 preParsing 钩子,则可以在解析请求有效负载流之前对其进行转换。它像其他钩子一样接收请求和响应对象,以及具有当前请求负载的流。

¥If you are using the preParsing hook, you can transform the request payload stream before it is parsed. It receives the request and reply objects as other hooks, and a stream with the current request payload.

如果它返回一个值(通过 return 或通过回调函数),它必须返回一个流。

¥If it returns a value (via return or via the callback function), it must return a stream.

例如,你可以解压缩请求正文:

¥For instance, you can decompress the request body:

fastify.addHook('preParsing', (request, reply, payload, done) => {
// Some code
done(null, newPayload)
})

async/await

¥Or async/await:

fastify.addHook('preParsing', async (request, reply, payload) => {
// Some code
await asyncMethod()
return newPayload
})

注意:在 preParsing 钩子中,request.body 将始终是 undefined,因为主体解析发生在 preValidation 钩子之前。

¥Notice: in the preParsing hook, request.body will always be undefined, because the body parsing happens before the preValidation hook.

注意:你还应该将 receivedEncodedLength 属性添加到返回的流中。此属性用于将请求负载与 Content-Length 标头值正确匹配。理想情况下,应在每个收到的块上更新此属性。

¥Notice: you should also add a receivedEncodedLength property to the returned stream. This property is used to correctly match the request payload with the Content-Length header value. Ideally, this property should be updated on each received chunk.

注意:检查返回流的大小是否不超过 bodyLimit 选项中设置的限制。

¥Notice: The size of the returned stream is checked to not exceed the limit set in bodyLimit option.

preValidation

如果你正在使用 preValidation 钩子,则可以在验证有效负载之前更改它。例如:

¥If you are using the preValidation hook, you can change the payload before it is validated. For example:

fastify.addHook('preValidation', (request, reply, done) => {
request.body = { ...request.body, importantKey: 'randomString' }
done()
})

async/await

¥Or async/await:

fastify.addHook('preValidation', async (request, reply) => {
const importantKey = await generateRandomString()
request.body = { ...request.body, importantKey }
})

preHandler

preHandler 钩子允许你指定在路由处理程序之前执行的函数。

¥The preHandler hook allows you to specify a function that is executed before a routes's handler.

fastify.addHook('preHandler', (request, reply, done) => {
// some code
done()
})

async/await

¥Or async/await:

fastify.addHook('preHandler', async (request, reply) => {
// Some code
await asyncMethod()
})

preSerialization

如果你正在使用 preSerialization 钩子,则可以在序列化之前更改(或替换)有效负载。例如:

¥If you are using the preSerialization hook, you can change (or replace) the payload before it is serialized. For example:

fastify.addHook('preSerialization', (request, reply, payload, done) => {
const err = null
const newPayload = { wrapped: payload }
done(err, newPayload)
})

async/await

¥Or async/await:

fastify.addHook('preSerialization', async (request, reply, payload) => {
return { wrapped: payload }
})

注意:如果有效负载是 stringBufferstreamnull,则不会调用钩子。

¥Note: the hook is NOT called if the payload is a string, a Buffer, a stream, or null.

onError

fastify.addHook('onError', (request, reply, error, done) => {
// Some code
done()
})

async/await

¥Or async/await:

fastify.addHook('onError', async (request, reply, error) => {
// Useful for custom error logging
// You should not use this hook to update the error
})

如果你需要执行一些自定义错误日志记录或添加一些特定标头以防出现错误,则此钩子非常有用。

¥This hook is useful if you need to do some custom error logging or add some specific header in case of error.

它不用于更改错误,调用 reply.send 将引发异常。

¥It is not intended for changing the error, and calling reply.send will throw an exception.

此钩子仅在执行 setErrorHandler 设置的自定义错误处理程序 后执行,并且仅当自定义错误处理程序将错误发送回用户时才会执行(请注意,默认错误处理程序始终将错误发送回用户)。

¥This hook will be executed only after the Custom Error Handler set by setErrorHandler has been executed, and only if the custom error handler sends an error back to the user (Note that the default error handler always sends the error back to the user).

注意:与其他钩子不同,不支持将错误传递给 done 函数。

¥Notice: unlike the other hooks, passing an error to the done function is not supported.

onSend

如果你正在使用 onSend 钩子,则可以更改有效负载。例如:

¥If you are using the onSend hook, you can change the payload. For example:

fastify.addHook('onSend', (request, reply, payload, done) => {
const err = null;
const newPayload = payload.replace('some-text', 'some-new-text')
done(err, newPayload)
})

async/await

¥Or async/await:

fastify.addHook('onSend', async (request, reply, payload) => {
const newPayload = payload.replace('some-text', 'some-new-text')
return newPayload
})

你还可以通过将有效负载替换为 null 来清除有效负载以发送带有空主体的响应:

¥You can also clear the payload to send a response with an empty body by replacing the payload with null:

fastify.addHook('onSend', (request, reply, payload, done) => {
reply.code(304)
const newPayload = null
done(null, newPayload)
})

你还可以通过将有效负载替换为空字符串 '' 来发送空主体,但请注意,这将导致 Content-Length 标头设置为 0,而如果有效负载为 null,则不会设置 Content-Length 标头。

¥You can also send an empty body by replacing the payload with the empty string '', but be aware that this will cause the Content-Length header to be set to 0, whereas the Content-Length header will not be set if the payload is null.

注意:如果更改有效负载,则只能将其更改为 stringBufferstreamReadableStreamResponsenull

¥Note: If you change the payload, you may only change it to a string, a Buffer, a stream, a ReadableStream, a Response, or null.

onResponse

fastify.addHook('onResponse', (request, reply, done) => {
// Some code
done()
})

async/await

¥Or async/await:

fastify.addHook('onResponse', async (request, reply) => {
// Some code
await asyncMethod()
})

当发送响应时,将执行 onResponse 钩子,因此你将无法向客户端发送更多数据。但是,它对于将数据发送到外部服务(例如收集统计数据)很有用。

¥The onResponse hook is executed when a response has been sent, so you will not be able to send more data to the client. It can however be useful for sending data to external services, for example, to gather statistics.

注意:将 disableRequestLogging 设置为 true 将禁用 onResponse 钩子内的任何错误日志。在这种情况下,使用 try - catch 记录错误。

¥Note: setting disableRequestLogging to true will disable any error log inside the onResponse hook. In this case use try - catch to log errors.

onTimeout

fastify.addHook('onTimeout', (request, reply, done) => {
// Some code
done()
})

async/await

¥Or async/await:

fastify.addHook('onTimeout', async (request, reply) => {
// Some code
await asyncMethod()
})

如果你需要监视服务中的请求超时(如果在 Fastify 实例上设置了 connectionTimeout 属性),则 onTimeout 很有用。当请求超时且 HTTP 套接字已挂断时,将执行 onTimeout 钩子。因此,你将无法向客户端发送数据。

¥onTimeout is useful if you need to monitor the request timed out in your service (if the connectionTimeout property is set on the Fastify instance). The onTimeout hook is executed when a request is timed out and the HTTP socket has been hung up. Therefore, you will not be able to send data to the client.

onRequestAbort

fastify.addHook('onRequestAbort', (request, done) => {
// Some code
done()
})

async/await

¥Or async/await:

fastify.addHook('onRequestAbort', async (request) => {
// Some code
await asyncMethod()
})

当客户端在处理整个请求之前关闭连接时,将执行 onRequestAbort 钩子。因此,你将无法向客户端发送数据。

¥The onRequestAbort hook is executed when a client closes the connection before the entire request has been processed. Therefore, you will not be able to send data to the client.

注意:客户端中止检测并不完全可靠。看:Detecting-When-Clients-Abort.md

¥Notice: client abort detection is not completely reliable. See: Detecting-When-Clients-Abort.md

通过钩子管理错误

¥Manage Errors from a hook

如果在执行钩子时出现错误,只需将其传递给 done(),Fastify 就会自动关闭请求并向用户发送相应的错误代码。

¥If you get an error during the execution of your hook, just pass it to done() and Fastify will automatically close the request and send the appropriate error code to the user.

fastify.addHook('onRequest', (request, reply, done) => {
done(new Error('Some error'))
})

如果你想将自定义错误代码传递给用户,只需使用 reply.code()

¥If you want to pass a custom error code to the user, just use reply.code():

fastify.addHook('preHandler', (request, reply, done) => {
reply.code(400)
done(new Error('Some error'))
})

错误将由 Reply 处理。

¥The error will be handled by Reply.

或者如果你使用 async/await,你可以抛出一个错误:

¥Or if you're using async/await you can just throw an error:

fastify.addHook('onRequest', async (request, reply) => {
throw new Error('Some error')
})

响应来自钩子的请求

¥Respond to a request from a hook

如果需要,你可以在到达路由处理程序之前响应请求,例如在实现身份验证钩子时。从钩子回复意味着钩子链已停止,其余钩子和处理程序均未执行。如果钩子使用回调方法,即它不是 async 函数或它返回 Promise,则只需调用 reply.send() 并避免调用回调即可。如果钩子是 async,则必须在函数返回或 promise 解析之前调用 reply.send(),否则请求将继续。当在 promise 链之外调用 reply.send() 时,return reply 很重要,否则请求将被执行两次。

¥If needed, you can respond to a request before you reach the route handler, for example when implementing an authentication hook. Replying from a hook implies that the hook chain is stopped and the rest of the hooks and handlers are not executed. If the hook is using the callback approach, i.e. it is not an async function or it returns a Promise, it is as simple as calling reply.send() and avoiding calling the callback. If the hook is async, reply.send() must be called before the function returns or the promise resolves, otherwise, the request will proceed. When reply.send() is called outside of the promise chain, it is important to return reply otherwise the request will be executed twice.

重要的是不要混合回调和 async/Promise,否则钩子链将被执行两次。

¥It is important to not mix callbacks and async/Promise, otherwise the hook chain will be executed twice.

如果你正在使用 onRequestpreHandler,请使用 reply.send

¥If you are using onRequest or preHandler use 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 中的模式。

¥If you want to respond with a stream, you should avoid using an async function for the hook. If you must use an async function, your code will need to follow the pattern in test/hooks-async.js.

fastify.addHook('onRequest', (request, reply, done) => {
const stream = fs.createReadStream('some-file', 'utf8')
reply.send(stream)
})

如果你发送的响应没有 await,请确保始终 return reply

¥If you are sending a response without await on it, make sure to always 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
})

应用钩子

¥Application Hooks

你也可以连接到应用生命周期。

¥You can hook into the application-lifecycle as well.

onReady

在服务器开始监听请求之前以及调用 .ready() 时触发。它无法更改路由或添加新的钩子。注册的钩子函数是串行执行的。只有在所有 onReady 钩子函数完成后,服务器才会开始监听请求。钩子函数接受一个参数:在钩子函数完成后调用的回调 done。使用绑定到相关 Fastify 实例的 this 调用钩子函数。

¥Triggered before the server starts listening for requests and when .ready() is invoked. It cannot change the routes or add new hooks. Registered hook functions are executed serially. Only after all onReady hook functions have completed will the server start listening for requests. Hook functions accept one argument: a callback, done, to be invoked after the hook function is complete. Hook functions are invoked with this bound to the associated Fastify instance.

// 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。使用绑定到相关 Fastify 实例的 this 调用钩子函数。

¥Triggered when the server starts listening for requests. The hooks run one after another. If a hook function causes an error, it is logged and ignored, allowing the queue of hooks to continue. Hook functions accept one argument: a callback, done, to be invoked after the hook function is complete. Hook functions are invoked with this bound to the associated Fastify instance.

这是 fastify.server.on('listening', () => {}) 的替代方案。

¥This is an alternative to 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() 启动服务器时,此钩子将不会运行

¥This hook will not run when the server is started using fastify.inject() or fastify.ready()

onClose

在所有正在进行的 HTTP 请求完成后,调用 fastify.close() 停止服务器时触发。当 plugins 需要 "shutdown" 事件时,它很有用,例如,关闭与数据库的打开连接。

¥Triggered when fastify.close() is invoked to stop the server, after all in-flight HTTP requests have been completed. It is useful when plugins need a "shutdown" event, for example, to close an open connection to a database.

钩子函数将 Fastify 实例作为第一个参数,并将 done 回调用于同步钩子函数。

¥The hook function takes the Fastify instance as a first argument, and a done callback for synchronous hook functions.

// 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

在所有正在进行的 HTTP 请求完成之前,调用 fastify.close() 停止服务器时触发。当 plugins 设置了一些附加到 HTTP 服务器的状态以防止服务器关闭时,它很有用。你不太可能需要使用此钩子,在最常见的情况下使用 onClose

¥Triggered when fastify.close() is invoked to stop the server, before all in-flight HTTP requests have been completed. It is useful when plugins have set up some state attached to the HTTP server that would prevent the server to close. It is unlikely you will need to use this hook, use the onClose for the most common case.

// callback style
fastify.addHook('preClose', (done) => {
// Some code
done()
})

// or async/await style
fastify.addHook('preClose', async () => {
// Some async code
await removeSomeServerState()
})

onRoute

注册新路由时触发。监听器传递一个 routeOptions 对象作为唯一参数。该接口是同步的,因此不会向监听器传递回调。这个钩子是封装的。

¥Triggered when a new route is registered. Listeners are passed a routeOptions object as the sole parameter. The interface is synchronous, and, as such, the listeners are not passed a callback. This hook is encapsulated.

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

如果你正在编写插件并且需要自定义应用路由,例如修改选项或添加新的路由钩子,那么这里就是正确的地方。

¥If you are authoring a plugin and you need to customize application routes, like modifying the options or adding new route hooks, this is the right place.

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 中添加更多路由,必须正确标记路由。如果没有标记,钩子将陷入无限循环。推荐的方法如下所示。

¥To add more routes within an onRoute hook, the routes must be tagged correctly. The hook will run into an infinite loop if not tagged. The recommended approach is shown below.

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

¥For more details, see this issue.

onRegister

当注册新插件并创建新封装上下文时触发。该钩子将在注册代码之前执行。

¥Triggered when a new plugin is registered and a new encapsulation context is created. The hook will be executed before the registered code.

如果你正在开发一个需要知道何时形成插件上下文的插件,并且你希望在该特定上下文中进行操作,则此钩子会很有用,因此封装了此钩子。

¥This hook can be useful if you are developing a plugin that needs to know when a plugin context is formed, and you want to operate in that specific context, thus this hook is encapsulated.

注意:如果插件封装在 fastify-plugin 内,则不会调用此钩子。

¥Note: This hook will not be called if a plugin is wrapped inside 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)
})

作用域

¥Scope

onClose 外,所有钩子都已封装。这意味着你可以使用 register 来决定钩子应该在哪里运行,如 插件指南 中所述。如果你传递一个函数,该函数将绑定到正确的 Fastify 上下文,并且从那里你可以完全访问 Fastify API。

¥Except for onClose, all hooks are encapsulated. This means that you can decide where your hooks should run by using register as explained in the plugins guide. If you pass a function, that function is bound to the right Fastify context and from there you have full access to the Fastify API.

fastify.addHook('onRequest', function (request, reply, done) {
const self = this // Fastify context
done()
})

请注意,每个钩子中的 Fastify 上下文与注册路由的插件相同,例如:

¥Note that the Fastify context in each hook is the same as the plugin where the route was registered, for example:

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,而是当前范围的函数。

¥Warn: if you declare the function with an arrow function, the this will not be Fastify, but the one of the current scope.

路由级别钩子

¥Route level hooks

你可以声明一个或多个自定义生命周期钩子(onRequestonResponsepreParsingpreValidationpreHandlerpreSerializationonSendonTimeoutonError)钩子,这些钩子对于路由是唯一的。如果这样做,这些钩子将始终作为其类别中的最后一个钩子执行。

¥You can declare one or more custom lifecycle hooks (onRequest, onResponse, preParsing, preValidation, preHandler, preSerialization, onSend, onTimeout, and onError) hook(s) that will be unique for the route. If you do so, those hooks are always executed as the last hook in their category.

如果你需要实现身份验证,这非常有用,其中 preParsingpreValidation 钩子正是你所需要的。多个路由级别的钩子也可以指定为一个数组。

¥This can be useful if you need to implement authentication, where the preParsing or preValidation hooks are exactly what you need. Multiple route-level hooks can also be specified as an array.

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

注意:这两个选项都接受函数数组。

¥Note: both options also accept an array of functions.

使用 Hooks 注入自定义属性

¥Using Hooks to Inject Custom Properties

你可以使用钩子将自定义属性注入传入请求中。这对于重用控制器中的钩子处理后的数据很有用。

¥You can use a hook to inject custom properties into incoming requests. This is useful for reusing processed data from hooks in controllers.

一个非常常见的用例是,例如,根据用户的令牌检查用户身份验证,然后将恢复的数据存储到 请求 实例中。这样,你的控制器可以使用 request.authenticatedUser 或任何你想要调用的名称轻松读取它。它可能是这样的:

¥A very common use case is, for example, checking user authentication based on their token and then storing their recovered data into the Request instance. This way, your controllers can read it easily with request.authenticatedUser or whatever you want to call it. That's how it might look like:

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 实际上可以是你选择的任何属性名称。使用你自己的自定义属性可以防止你更改现有属性,这将是危险且具有破坏性的操作。因此,请小心并确保你的属性是全新的,并且仅针对非常具体和小型的情况(例如本示例)使用此方法。

¥Note that .authenticatedUser could actually be any property name chosen by yourself. Using your own custom property prevents you from mutating existing properties, which would be a dangerous and destructive operation. So be careful and make sure your property is entirely new, also using this approach only for very specific and small cases like this example.

关于此示例中的 TypeScript,你需要更新 FastifyRequest 核心接口以包含你的新属性类型(有关更多信息,请参阅 TypeScript 页面),例如:

¥Regarding TypeScript in this example, you'd need to update the FastifyRequest core interface to include your new property typing (for more about it, see TypeScript page), like:

interface AuthenticatedUser { /* ... */ }

declare module 'fastify' {
export interface FastifyRequest {
authenticatedUser?: AuthenticatedUser;
}
}

虽然这是一种非常务实的方法,但如果你尝试执行更复杂的事情来更改这些核心对象,那么请考虑创建自定义 Plugin

¥Although this is a very pragmatic approach, if you're trying to do something more complex that changes these core objects, then consider creating a custom Plugin instead.

诊断通道钩子

¥Diagnostics Channel Hooks

一个 diagnostics_channel 发布事件 'fastify.initialization' 在初始化时发生。Fastify 实例作为传入对象的属性传递到钩子中。此时,可以与实例交互以添加钩子、插件、路由或任何其他类型的修改。

¥One diagnostics_channel publish event, 'fastify.initialization', happens at initialization time. The Fastify instance is passed into the hook as a property of the object passed in. At this point, the instance can be interacted with to add hooks, plugins, routes, or any other sort of modification.

例如,跟踪包可能会执行如下操作(当然,这是一种简化)。这将以典型的 "首先需要检测工具" 方式加载到跟踪包初始化中的文件中。

¥For example, a tracing package might do something like the following (which is, of course, a simplification). This would be in a file loaded in the initialization of the tracking package, in the typical "require instrumentation tools first" fashion.

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-patch 版本中也可能会进行重大更改。

¥Note: The TracingChannel class API is currently experimental and may undergo breaking changes even in semver-patch releases of Node.js.

根据 追踪通道 命名法,每个请求都会发布另外五个事件。通道名称及其接收的事件列表如下:

¥Five other events are published on a per-request basis following the Tracing Channel nomenclature. The list of the channel names and the event they receive is:

  • tracing:fastify.request.handler:start:总是触发

    ¥tracing:fastify.request.handler:start: Always fires

    • { request: Request, reply: Reply, route: { url, method } }
  • tracing:fastify.request.handler:end:总是触发

    ¥tracing:fastify.request.handler:end: Always fires

    • { request: Request, reply: Reply, route: { url, method }, async: Bool }
  • tracing:fastify.request.handler:asyncStart:为 promise/async 处理程序触发

    ¥tracing:fastify.request.handler:asyncStart: Fires for promise/async handlers

    • { request: Request, reply: Reply, route: { url, method } }
  • tracing:fastify.request.handler:asyncEnd:为 promise/async 处理程序触发

    ¥tracing:fastify.request.handler:asyncEnd: Fires for promise/async handlers

    • { request: Request, reply: Reply, route: { url, method } }
  • tracing:fastify.request.handler:error:发生错误时触发

    ¥tracing:fastify.request.handler:error: Fires when an error occurs

    • { 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 属性。

¥The object instance remains the same for all events associated with a given request. All payloads include a request and reply property which are an instance of Fastify's Request and Reply instances. They also include a route property which is an object with the matched url pattern (e.g. /collection/:id) and the method HTTP method (e.g. GET). The :start and :end events always fire for requests. If a request handler is an async function or one that returns a Promise then the :asyncStart and :asyncEnd events also fire. Finally, the :error event contains an error property associated with the request's failure.

这些事件可以这样接收:

¥These events can be received like so:

const dc = require('node:diagnostics_channel')
const channel = dc.channel('tracing:fastify.request.handler:start')
channel.subscribe((msg) => {
console.log(msg.request, msg.reply)
})