You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
hutao/docs/book/hooks.md

213 lines
7.7 KiB

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