2025-03-29 14:35:49 +08:00

365 lines
11 KiB
Markdown
Raw 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
* 官网: https://fastify.dev
* 中文文档: https://www.fastify.cn/docs/latest/Getting-Started/
* [提升 Node.js 服务端性能Fastify 框架](https://juejin.cn/post/7340109700767154228)
* [Fastify+TS实现基础IM服务](https://juejin.cn/post/7356535808931053606)
## 快速开始
```sh
pnpm add fastify
```
```js
// Import the framework and instantiate it
import Fastify from "fastify";
const fastify = Fastify({
logger: true,
});
// Declare a route
fastify.get("/", async function handler(request, reply) {
return { hello: "world" };
});
// Run the server!
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
```
因为使用esm模块, `package.json` 需要设置`"type": "module"`
## 使用cli快速生成项目
```sh
npm install --global fastify-cli
fastify generate myproject
```
## 插件、装饰器、中间件、钩子
### 插件
> 用于添加功能、共享代码或封装逻辑
===在 Fastify 中,一切都是插件===
* 一个插件可以是一个函数,该函数接受 Fastify 实例、选项和回调函数作为参数
* 插件可以注册路由、添加装饰器、声明新的钩子,甚至可以封装其他插件,用于构建模块化的应用
* 在应用启动时加载,按照注册的顺序执行
* `fastify.register()`
例子:
```js
async function routes (fastify, options) {
fastify.get('/', async (request, reply) => {
return { hello: 'world' }
})
}
export default routes
```
```js
import Fastify from 'fastify'
import routes from './routers/root.js'
const fastify = Fastify({
logger: true
})
// 使用register注册
fastify.register(routes)
try {
await fastify.listen({ port: 3000 })
} catch (err) {
fastify.log.error(err)
process.exit(1)
}
```
### 装饰器
> 扩展 Fastify 实例、请求Request和回复Reply对象通过添加新的方法或属性
* 例如,可以添加一个装饰器来添加一个方法,该方法在每个请求中都可用,用于访问共享的配置数据或服务
### 中间件
> 支持使用 Express/Connect 风格的中间件,主要用于兼容性或特定功能的集成
* 应谨慎使用,因为不当使用可能会绕过 Fastify 的一些优化,影响性能
### 钩子
> 允许开发者在请求生命周期的不同阶段介入执行逻辑
* 例如,在请求接收之后、路由解析之前、发送响应之前等
* 可以用于执行一些预处理或后处理逻辑,如权限检查、请求日志记录、修改响应等
* `fastify.addHook`
常见的钩子:
* `onRequest`:在请求被接收后立即执行,但在任何其他处理之前。
* `preParsing`:在请求体解析之前执行。
* `preValidation`:在路由级别的验证之前执行。
* `preHandler`:在路由处理函数之前执行。
* `preSerialization`:在响应被序列化之前执行,发送给客户端之前。
* `onSend`:在响应发送给客户端之前,但在序列化之后执行。
* `onResponse`:在响应完全发送给客户端后执行。
钩子的使用
```js
fastify.get(
'/example',
{
preHandler: (request, reply, done) => {
// 在路由处理器之前执行某些代码
done()
},
},
(request, reply) => {
reply.send({ hello: 'world' })
}
)
```
#### 生命周期
```
Incoming Request
└─▶ Routing
└─▶ Instance Logger
4**/5** ◀─┴─▶ onRequest Hook 在请求完全被接收之后,任何解析之前触发.用来执行一些认证或者其他检查
4**/5** ◀─┴─▶ preParsing Hook 解析请求之前触发,但在它被路由处理之后。用来修改原始请求对象,例如解密数据。
4**/5** ◀─┴─▶ Parsing
4**/5** ◀─┴─▶ preValidation Hook 在执行路由级别的验证之前触发.用来在标准验证之前预处理一些参数
400 ◀─┴─▶ Validation
4**/5** ◀─┴─▶ preHandler Hook 在执行路由的处理函数之前触发。这是处理请求前最后的步骤,可以做一些预处理工作,如权限校验。
4**/5** ◀─┴─▶ User Handler
└─▶ Reply
4**/5** ◀─┴─▶ preSerialization Hook 在发送响应之前,序列化之前触发。可以用来修改响应数据,或者在序列化复杂类型之前进行转换。
└─▶ onSend Hook 可以修改响应体或者添加响应头
4**/5** ◀─┴─▶ Outgoing Response
└─▶ onResponse Hook 主要用于记录日志或者执行清理任务
```
#### 响应的数据流
```js
schema validation Error
└─▶ schemaErrorFormatter
reply sent ◀── JSON ─┴─ Error instance
throw an Error
send or return
reply sent ◀── JSON ─┴─ Error instance ──▶ setErrorHandler ◀─────┘
reply sent ◀── JSON ─┴─ Error instance ──▶ onError Hook
└─▶ reply sent
```
#### 添加钩子
```js
fastify.addHook('preHandler', (request, reply, done) => {
// some code
done()
})
```
## JSON Schema (验证数据)
> 验证客户端请求的数据和格式化响应数据
* 验证路由参数、查询字符串、请求体和响应体
在 schema 的选项中设置 `response` 的值,能够加快 JSON 的序列化, 因为 Fastify 仅对 schema 里出现的数据进行序列化。
```js
const opts = {
schema: {
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
}
}
fastify.get('/', opts, async (request, reply) => {
return { hello: 'world' }
})
```
还可以验证 `body``querystring``params` 以及 `header`
## 路由
> 实际项目中, 路由分散在不同的文件和模块中, 使用插件注册
### 自定义路由处理器
```js
const customHandler = (request, reply) => {
// 自定义处理逻辑
};
fastify.route({
method: 'GET',
url: '/custom',
handler: customHandler
});
```
### 内置的快速方法
```ts
fastify.get(path, [options], handler)
fastify.head(path, [options], handler)
fastify.post(path, [options], handler)
fastify.put(path, [options], handler)
fastify.delete(path, [options], handler)
fastify.options(path, [options], handler)
fastify.patch(path, [options], handler)
// 你还可以链式调用它们
fastify.get('/path', handler).post('/path', handler);
```
### 静态路由
```js
// 参数
fastify.get('/example/:userId', function (request, reply) {
// curl ${app-url}/example/12345
// userId === '12345'
const { userId } = request.params;
// your code here
})
fastify.get('/example/:userId/:secretToken', function (request, reply) {
// curl ${app-url}/example/12345/abc.zHi
// userId === '12345'
// secretToken === 'abc.zHi'
const { userId, secretToken } = request.params;
// your code here
})
// 通配符
fastify.get('/example/*', function (request, reply) {})
```
### 动态路由参数
```js
fastify.get('/user/:id', (request, reply) => {
const userId = request.params.id;
// 根据 userId 获取用户信息...
});
```
### 路由前缀
```js
fastify.register(require('./user-routes'), { prefix: '/api/v1/users' });
```
### 404处理
```js
fastify.setNotFoundHandler((request, reply) => {
reply.status(404).send({ error: 'Not Found' });
});
```
### 路由级别的插件
为特定的路由添加特定的功能,比如认证、日志记录等
```js
fastify.get('/private', { preHandler: fastify.auth }, (request, reply) => {
// 只有通过认证的请求才能到达这里
});
```
### websocket
```js
fastify.get('/ws', { websocket: true }, (connection /* SocketStream */, req /* FastifyRequest */) => {
connection.socket.on('message', message => {
// 处理 WebSocket 消息
});
});
```
### 高级查询解析
自定义查询字符串的解析器
```js
const qs = require('qs');
fastify.setQuerystringParser((str) => {
return qs.parse(str);
});
```
## 请求和响应对象
### 请求对象
* `request.raw`: 原生的 Node.js HTTP 请求对象。
* `request.body`: 请求体的内容Fastify 会根据你设置的内容类型自动解析它。
* `request.query`: 解析后的查询字符串参数。
* `request.params`: 路由参数值,例如 `/user/:id` 中的 `id`
* `request.headers`: 请求头对象。
* `request.id`: 每个请求的唯一标识符,可以用于日志记录或跟踪。
* `request.log`: 日志记录器实例,可以用来记录请求相关的日志。
* `request.ip`: 客户端的 IP 地址。
* `request.method`: HTTP 请求方法(例如,`GET`, `POST` 等)。
* `request.url`: 请求的 URL 字符串。
### 响应对象
* `reply.raw`: 原生的 Node.js HTTP 响应对象。
* `reply.code(statusCode)`: 设置响应的状态码。
* `reply.header(name, value)`: 设置响应头。
* `reply.headers(headers)`: 一次性设置多个响应头。
* `reply.type(contentType)`: 设置 `Content-Type` 响应头。
* `reply.redirect(url)`: 重定向到指定的 URL。
* `reply.send(payload)`: 发送响应数据。payload 可以是一个字符串、Buffer、对象或者 Stream。
* `reply.serializer(customSerializer)`: 设置自定义的序列化函数来转换响应数据。
* `reply.sent`: 一个布尔值,如果响应已经发送则为 `true`