掘金 后端 ( ) • 2024-04-12 16:34

Fastify的插件系统

Fastify 是一个高性能的 Node.js web 框架,它提供了一个强大的插件系统,允许开发者扩展框架的核心功能,添加新的特性或者重用一些逻辑。Fastify 的插件系统是其核心特性之一,它为开发者提供了强大的模块化能力。

特点

  • 封装: Fastify 的插件可以创建封装的上下文,这意味着注册在插件内的路由、装饰器、钩子等都不会影响到其他的插件或者根应用。这种封装性保证了代码的隔离和复用性。
  • 继承: 封装的上下文可以继承父上下文的属性和方法,但是父上下文不能访问子上下文的属性和方法。这允许插件之间建立层次关系。
  • 生命周期钩子: Fastify 提供了多个生命周期钩子,例如 onRequest, preHandler, onSend, 和 onResponse。插件可以注册这些钩子,以在请求处理的不同阶段执行代码。
  • 装饰器: 插件可以通过装饰器向 Fastify 实例、请求(Request)、回复(Reply)对象添加新的方法或属性,这些装饰器可以在插件的封装作用域内或全局使用。

创建 Fastify 插件

一个基础的 Fastify 插件是一个函数,这个函数接受 Fastify 实例、一个选项对象和一个回调函数作为参数。这个函数通常是异步的或者返回一个 Promise。下面是一个简单的插件示例:

// my-plugin.js
module.exports = async function (fastify, options) {
  fastify.decorate('utility', () => {
    console.log('Something useful');
  });

  fastify.get('/plugin-route', (request, reply) => {
    reply.send({ hello: 'world from plugin' });
  });
};

然后,你可以使用 fastify.register() 方法来注册这个插件:

// index.js
const fastify = require('fastify')();
const myPlugin = require('./my-plugin');

fastify.register(myPlugin, { /* 你可以在这里传递选项 */ });

fastify.listen(3000, err => {
  if (err) throw err;
  console.log(`Server is running at ${fastify.server.address().port}`);
});

使用第三方插件

要使用第三方插件,你只需要安装它们并在你的 Fastify 实例中注册它们。例如,使用 fastify-sensible 插件可以为 Fastify 实例添加一些有用的工具和实用函数:

const fastify = require('fastify')();
const sensible = require('fastify-sensible');

fastify.register(sensible);

fastify.listen(3000, err => {
  if (err) throw err;
  console.log(`Server is running at ${fastify.server.address().port}`);
});

路由管理

Fastify 的路由管理是其核心特性之一,它提供了一个高性能的路由系统,这个系统允许开发者以极简的方式定义和管理路由。在实践中,Fastify 的路由系统支持许多高级特性,包括参数验证、序列化、生命周期钩子和插件封装等。

路由定义

在 Fastify 中,定义路由是通过向实例添加一个路由方法来完成的,这些方法对应 HTTP 的方法,例如 GET, POST, PUT, DELETE 等。下面是一个简单的路由定义示例:

fastify.get('/example', (request, reply) => {
  reply.send({ hello: 'world' });
});

路由钩子

Fastify 允许在路由级别添加钩子(hook),这些钩子可以在请求的不同阶段执行代码。常见的钩子包括 onRequest, preHandler, preValidation, onSend 等。

fastify.get('/example', {
  preHandler: (request, reply, done) => {
    // 在路由处理器之前执行某些代码
    done();
  }
}, (request, reply) => {
  reply.send({ hello: 'world' });
});

路由参数验证

Fastify 使用 JSON Schema 来验证路由参数、查询字符串、请求体和响应体。这提供了一个强大且快速的验证机制。

const schema = {
  querystring: {
    type: 'object',
    properties: {
      name: { type: 'string' },
      excitement: { type: 'integer' }
    },
    required: ['name']
  }
};

fastify.get('/example', { schema }, (request, reply) => {
  // 如果验证失败,Fastify 会自动发送一个 400 错误回客户端
  reply.send({ hello: `Excited ${request.query.name}!` });
});

路由序列化

你可以为响应定义一个序列化器,以确保发送给客户端的数据符合特定的格式。

const schema = {
  response: {
    200: {
      type: 'object',
      properties: {
        pong: { type: 'string' }
      }
    }
  }
};

fastify.get('/ping', { schema }, (request, reply) => {
  reply.send({ pong: 'it worked!' });
});

路由组织和封装

Fastify 支持通过插件封装来组织路由,这样可以创建具有特定前缀的路由集合,同时保持封装性。

fastify.register((instance, opts, done) => {
  instance.get('/local', (request, reply) => {
    reply.send({ hello: 'local' });
  });

  done();
}, { prefix: '/v1' });

// 这将创建一个路由 '/v1/local'

实践中的路由管理

在实际项目中,你可能会将路由分散在不同的文件和模块中,以保持代码的可维护性。例如,你可以为每个功能或业务逻辑创建一个插件,并在插件中定义相关的路由。

// user-routes.js
module.exports = async function (fastify, opts) {
  fastify.get('/users', async (request, reply) => {
    // 获取用户列表的逻辑
  });

  fastify.get('/users/:id', async (request, reply) => {
    // 获取特定用户的逻辑
  });

  // 更多用户相关的路由...
};

// 在你的主应用文件中
fastify.register(require('./user-routes'));

通过这种方式,你可以构建一个清晰、模块化且易于扩展的路由系统。Fastify 的路由管理系统是设计用来支持大型和复杂应用程序的,同时保持高性能和开发效率。

动态路由

Fastify 支持动态路由参数,这意味着你可以在 URL 路径中捕获变量。

fastify.get('/user/:id', (request, reply) => {
  const userId = request.params.id;
  // 根据 userId 获取用户信息...
});

路由 Shorthand 方法

Fastify 提供了一系列的 shorthand 方法,使得定义路由更加便捷。这些方法对应 HTTP 的各种请求方法。

// GET 请求的 shorthand 方法
fastify.get('/path', handler);

// POST 请求的 shorthand 方法
fastify.post('/path', handler);

// 你还可以链式调用它们
fastify.get('/path', handler).post('/path', handler);

自定义路由处理器

你可以自定义路由处理器,这对于复用逻辑或者添加特定的逻辑非常有用。

const customHandler = (request, reply) => {
  // 自定义处理逻辑
};

fastify.route({
  method: 'GET',
  url: '/custom',
  handler: customHandler
});

路由前缀

在注册插件时,可以为插件内的所有路由指定一个前缀,这有助于创建版本化的 API 或者按功能区分的路由组。

fastify.register(require('./user-routes'), { prefix: '/api/v1/users' });

404 处理

Fastify 允许你自定义 404 处理函数,以便在没有匹配到路由时提供自定义逻辑。

fastify.setNotFoundHandler((request, reply) => {
  reply.status(404).send({ error: 'Not Found' });
});

路由级别的插件

你可以在特定的路由上使用插件,这允许你为特定的路由添加特定的功能,比如认证、日志记录等。

fastify.get('/private', { preHandler: fastify.auth }, (request, reply) => {
  // 只有通过认证的请求才能到达这里
});

WebSocket 支持

Fastify 支持 WebSocket 路由,允许你在同一个 HTTP 服务器上处理 WebSocket 连接。

fastify.get('/ws', { websocket: true }, (connection /* SocketStream */, req /* FastifyRequest */) => {
  connection.socket.on('message', message => {
    // 处理 WebSocket 消息
  });
});

高级查询解析

Fastify 允许你自定义查询字符串的解析器,这可以让你处理复杂的查询字符串。

const qs = require('qs');

fastify.setQuerystringParser((str) => {
  return qs.parse(str);
});

请求与响应对象

请求对象(Request)

Fastify 的请求对象封装了原生的 Node.js HTTP 请求对象,并且附加了一些额外的功能和属性。这些是请求对象的一些关键部分:

  • 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)

Fastify 的响应对象提供了一系列的方法来设置响应状态、发送响应数据和管理响应头。以下是响应对象的一些关键特点:

  • 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

钩子(Hooks)和生命周期

在 Fastify 中,钩子(Hooks)是一种允许开发者在请求的生命周期的特定时刻运行自定义代码的功能。通过钩子,你可以执行验证、执行一些预处理或后处理任务、修改请求和响应对象、处理日志等。这些钩子按照请求的生命周期顺序执行,为开发者提供了介入请求处理流程的机会。

生命周期概念

Fastify 中的请求生命周期是指一个请求从开始到结束所经历的一系列阶段。这些阶段包括接收请求、解析请求、路由匹配、执行业务逻辑、构建响应直到发送响应给客户端。在这个过程中,Fastify 定义了多个钩子,可以在不同的阶段触发。

钩子类型

以下是 Fastify 中一些常见的钩子类型,按照它们在请求生命周期中的触发顺序排列:

  1. onRequest:在请求完全被接收之后,任何解析之前触发。适合用来执行一些认证或者其他检查。
  2. preParsing:在解析请求(如解析 JSON 体)之前触发,但在它被路由处理之后。可以用来修改原始请求对象,例如解密数据。
  3. preValidation:在执行路由级别的验证之前触发。如果你有一些参数需要在标准验证之前预处理,可以在这里做。
  4. preHandler:在执行路由的处理函数之前触发。这是处理请求前最后的步骤,可以做一些预处理工作,如权限校验。
  5. preSerialization:在发送响应之前,序列化(如将对象转换为 JSON)之前触发。这个钩子可以用来修改响应数据,或者在序列化复杂类型之前进行转换。
  6. onSend:在响应被发送给客户端之前触发,但在响应被序列化之后。这里可以修改响应体或者添加响应头。
  7. onResponse:当响应发送给客户端之后触发。这个钩子主要用于记录日志或者执行清理任务。
  8. onError:当请求处理过程中发生错误时触发。可以用来定制错误响应或记录错误日志。

使用方法

钩子可以在应用级别或者路由级别注册。以下是一个注册全局 onRequest 钩子的例子:

fastify.addHook('onRequest', async (request, reply) => {
  // 这里可以执行一些认证逻辑
  if (!request.headers.auth) {
    throw new Error('Authentication failed')
  }
});

应用场景

不同的钩子可以用于不同的场景:

  • 认证:使用 onRequest 钩子来检查请求的认证信息。
  • 数据预处理:在 preParsingpreValidation 钩子中对请求数据进行预处理,如格式转换、数据修饰等。
  • 权限校验:在 preHandler 钩子中进行权限检查,确保用户有权执行请求的操作。
  • 自定义序列化:使用 preSerialization 钩子来自定义响应数据的序列化逻辑。
  • 日志记录:在 onResponseonError 钩子中记录请求相关的信息或错误日志。

通过在适当的生命周期阶段使用钩子,你可以使你的应用更加灵活和强大。