Fastify 提供了多种钩子(hooks),允许您在不同的生命周期阶段介入服务器的行为。它们在处理请求和响应时非常有用,例如验证请求、日志记录、执行清理或统计任务等。 下面是 Fastify 所有的生命周期钩子列表,按照它们在请求生命周期中的执行顺序排列: ### 应用层面的钩子 1. `onRequest(req, reply, done)` - 在路由解析之前执行(但在请求被解析之后,例如 HTTP 方法、URL 和头部)。 - 可以用来执行验证、解密等任务。 2. `preParsing(req, reply, payload, done)` - 在解析 HTTP 请求之前执行(例如,解析请求体之前)。 - 允许您操作原始请求对象和有效载荷。 3. `preValidation(req, reply, done)` - 在执行路由处理程序之前、请求校验之前执行。 - 适合执行权限校验等任务。 4. `preHandler(req, reply, done)` - 在执行路由处理程序之前执行。 - 可用于修改请求或回复,或在实际处理程序运行前做准备工作。 ### 路由层面的钩子 在路由声明时,可以指定以下钩子,它们仅在应用于该路由的请求中执行: 1. `preValidation(req, reply, done)` - 类似于应用层面的 preValidation 钩子,但只作用于特定路由。 2. `preHandler(req, reply, done)` - 类似于应用层面的 preHandler 钩子,但只作用于特定路由。 ### 异常处理钩子 1. `onError(req, reply, error, done)` - 当处理程序或钩子抛出异常时执行。 - 用于自定义错误处理逻辑。 ### 回复生命周期钩子 1. `onSend(req, reply, payload, done)` - 在将回复发送给客户端之前执行。 - 可以用来修改回复的有效载荷。 2. `onResponse(req, reply, done)` - 当响应完全发送给客户端后执行。 - 适用于清理资源或记录日志。 ### 关闭钩子 1. `onClose(instance, done)` - 在服务器关闭时执行。 - 通常用于关闭数据库连接、清理资源等。 这些钩子都遵循 `fastify` 的 "错误优先回调" 约定,即 `done` 回调函数的第一个参数是一个错误对象,如果有错误的话。如果一切正常,您只需调用 `done()`,没有任何参数或者传入 `null`。 要使用这些钩子,通常您会将它们注册为 Fastify 插件,或者直接应用在 Fastify 实例上。例如: ```javascript // 注册一个 onRequest 钩子 fastify.addHook('onRequest', (req, reply, done) => { // 钩子逻辑 done(); }); ``` 对于路由特定的钩子,您将它们添加到路由声明中,如下所示: ```javascript 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` 这些生命周期钩子。 这里有一个例子,展示了如何给特定路由添加钩子: ```javascript 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)` 即可。 如果您需要在一个特定的路由上应用多个同类型的钩子,您可以传递一个钩子函数的数组: ```javascript 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 中,如果多个路由有部分共同的路径,并且你想对这些具有共同路径的路由应用特定的钩子,你可以使用两种主要方法: 1. **通过插件和前缀注册钩子**:你可以创建一个插件,为这个插件设置一个路径前缀(路由的共同部分),然后在这个插件内注册全局钩子(这将仅应用于插件范围内的路由)。 以下是使用插件来注册一个钩子的示例: ```javascript 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` 钩子。 2. **条件性地在钩子中检查路由路径**:你还可以在一个全局钩子中检查请求的路径,根据请求的路由路径来做出条件性的反应,这种方法没有上面那种结构化,但在某些情况下可能有用。 以下是使用条件性钩子的示例: ```javascript // 全局钩子 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` 开头,仅对符合该条件的路径执行额外的逻辑。 通常,对于维护性和可读性而言,使用插件和前缀注册钩子的方法更为推荐,因为它将相关的路由和钩子逻辑组织在一起,并避免了全局状态的潜在冲突。