alioth/before/hoto/docs/book/hooks.md
2025-05-30 09:18:01 +08:00

214 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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` 开头,仅对符合该条件的路径执行额外的逻辑。
通常,对于维护性和可读性而言,使用插件和前缀注册钩子的方法更为推荐,因为它将相关的路由和钩子逻辑组织在一起,并避免了全局状态的潜在冲突。