7.7 KiB
Fastify 提供了多种钩子(hooks),允许您在不同的生命周期阶段介入服务器的行为。它们在处理请求和响应时非常有用,例如验证请求、日志记录、执行清理或统计任务等。
下面是 Fastify 所有的生命周期钩子列表,按照它们在请求生命周期中的执行顺序排列:
应用层面的钩子
-
onRequest(req, reply, done)
- 在路由解析之前执行(但在请求被解析之后,例如 HTTP 方法、URL 和头部)。
- 可以用来执行验证、解密等任务。
-
preParsing(req, reply, payload, done)
- 在解析 HTTP 请求之前执行(例如,解析请求体之前)。
- 允许您操作原始请求对象和有效载荷。
-
preValidation(req, reply, done)
- 在执行路由处理程序之前、请求校验之前执行。
- 适合执行权限校验等任务。
-
preHandler(req, reply, done)
- 在执行路由处理程序之前执行。
- 可用于修改请求或回复,或在实际处理程序运行前做准备工作。
路由层面的钩子
在路由声明时,可以指定以下钩子,它们仅在应用于该路由的请求中执行:
-
preValidation(req, reply, done)
- 类似于应用层面的 preValidation 钩子,但只作用于特定路由。
-
preHandler(req, reply, done)
- 类似于应用层面的 preHandler 钩子,但只作用于特定路由。
异常处理钩子
onError(req, reply, error, done)
- 当处理程序或钩子抛出异常时执行。
- 用于自定义错误处理逻辑。
回复生命周期钩子
-
onSend(req, reply, payload, done)
- 在将回复发送给客户端之前执行。
- 可以用来修改回复的有效载荷。
-
onResponse(req, reply, done)
- 当响应完全发送给客户端后执行。
- 适用于清理资源或记录日志。
关闭钩子
onClose(instance, done)
- 在服务器关闭时执行。
- 通常用于关闭数据库连接、清理资源等。
这些钩子都遵循 fastify
的 "错误优先回调" 约定,即 done
回调函数的第一个参数是一个错误对象,如果有错误的话。如果一切正常,您只需调用 done()
,没有任何参数或者传入 null
。
要使用这些钩子,通常您会将它们注册为 Fastify 插件,或者直接应用在 Fastify 实例上。例如:
// 注册一个 onRequest 钩子
fastify.addHook('onRequest', (req, reply, done) => {
// 钩子逻辑
done();
});
对于路由特定的钩子,您将它们添加到路由声明中,如下所示:
fastify.route({
method: 'GET',
url: '/',
preHandler: (req, reply, done) => {
// 路由特定的钩子逻辑
done();
},
handler: (req, reply) => {
// 路由处理程序
reply.send({ hello: 'world' });
}
});
钩子可以是非常强大的工具,它们提供了对 Fastify 应用程序的细粒度控制,允许您优雅地集成自定义逻辑和中间件。
在 Fastify 中,您可以通过在路由声明中直接添加钩子来将它们作用于特定的路由。当您声明路由时,Fastify 允许您指定 preValidation
、preHandler
、preSerialization
、onError
、onSend
和 onResponse
这些生命周期钩子。
这里有一个例子,展示了如何给特定路由添加钩子:
fastify.route({
method: 'GET',
url: '/special',
preHandler: (request, reply, done) => {
// ... 在这个特定路由之前执行一些逻辑
done();
},
handler: (request, reply) => {
// ... 处理路由请求
reply.send({ hello: 'world' });
},
onSend: (request, reply, payload, done) => {
// ... 在回复发送到客户端之前修改回复内容
done();
}
});
在这个例子中,preHandler
钩子函数在处理 GET 请求到 /special
路由之前运行,而 onSend
钩子在响应发送之前运行,可以用于修改响应 payload。
每个钩子函数都会接受不同的参数,根据所执行的任务,这些参数提供了对请求 (request
)、回复 (reply
)、有效载载荷 (payload
) 等的访问权。钩子函数最后需要调用 done
函数来通知 Fastify 钩子已完成执行,如果出现错误,可以将错误作为 done
函数的第一个参数传递。如果一切顺利,那么只需调用 done()
或 done(null)
即可。
如果您需要在一个特定的路由上应用多个同类型的钩子,您可以传递一个钩子函数的数组:
fastify.route({
method: 'GET',
url: '/special',
preHandler: [
(request, reply, done) => {
// 第一个 preHandler 钩子
done();
},
(request, reply, done) => {
// 第二个 preHandler 钩子
done();
}
],
handler: (request, reply) => {
// ...处理路由请求
reply.send({ hello: 'world' });
}
});
在这个例子中,当请求 /special
路由时,将按照数组中的顺序执行两个 preHandler
钩子函数。
通过将钩子绑定到特定路由上,您可以为每个路由精细地控制流程,并按需集成权限检查、请求数据修改、响应的自定义处理等逻辑。
在 Fastify 中,如果多个路由有部分共同的路径,并且你想对这些具有共同路径的路由应用特定的钩子,你可以使用两种主要方法:
-
通过插件和前缀注册钩子:你可以创建一个插件,为这个插件设置一个路径前缀(路由的共同部分),然后在这个插件内注册全局钩子(这将仅应用于插件范围内的路由)。
以下是使用插件来注册一个钩子的示例:
const fastify = require('fastify')({ logger: true }); // 插件定义 fastify.register(function (instance, options, done) { // 这里的钩子将仅作用于 '/api' 前缀路径下的路由 instance.addHook('preHandler', async (request, reply) => { // ... 在该插件下所有路由的处理器之前执行逻辑 }); instance.get('/feature1', async (request, reply) => { // ... 处理该路由的请求 return { feature: '1' }; }); instance.get('/feature2', async (request, reply) => { // ... 处理该路由的请求 return { feature: '2' }; }); done(); }, { prefix: '/api' }); // 启动服务器 fastify.listen(3000, err => { if (err) throw err; console.log(`Server listening on ${fastify.server.address().port}`); });
在这个例子中,所有前缀为
/api
的路由都将执行在addHook
中定义的preHandler
钩子。 -
条件性地在钩子中检查路由路径:你还可以在一个全局钩子中检查请求的路径,根据请求的路由路径来做出条件性的反应,这种方法没有上面那种结构化,但在某些情况下可能有用。
以下是使用条件性钩子的示例:
// 全局钩子 fastify.addHook('preHandler', async (request, reply) => { // 查看请求路径是否匹配特定模式 if (request.raw.url.startsWith('/api')) { // ... 在所有 '/api' 路径下的路由之前执行逻辑 } }); // 非`/api`前缀的路由 fastify.get('/other', async (request, reply) => { // 这个路由不会受到上述全局钩子影响 // 除非在上面的条件检查中同时包括此路径 return { hello: 'world' }; });
在这个例子中,全局
preHandler
钩子对所有路由生效,但是它内部检查请求的 URL 是不是以/api
开头,仅对符合该条件的路径执行额外的逻辑。
通常,对于维护性和可读性而言,使用插件和前缀注册钩子的方法更为推荐,因为它将相关的路由和钩子逻辑组织在一起,并避免了全局状态的潜在冲突。