网站首页 > 开源技术 正文
在这一部分,我们将深入探讨 Fastify 的一些更高级的概念。具体来说,我们将演示如何使用挂钩、中间件、装饰器和验证来构建更健壮的 Web 应用程序。我们还将简要介绍一些将现有 Express 应用程序迁移到 Fastify 的可行策略。
让我们开始吧!
Fastify 中的钩子和中间件
Fastify 中的钩子允许您监听应用程序生命周期中的特定事件,并在这些事件发生时执行特定任务。Hooks 类似于 Express 风格的中间件功能,但性能更高,允许您执行身份验证、日志记录、路由、数据解析、错误处理等任务。Fastify 中有两种类型的钩子: 请求/回复钩子 和 应用程序钩子。
Fastify 中的请求/回复钩子
请求/回复挂钩在服务器的请求/回复周期中执行,允许您在处理传入请求或传出响应之前或之后执行各种任务。这些挂钩可以应用于所有路线或选择的路线。它们通常用于实现以下功能:
- 访问控制
- 数据验证
- 请求记录
和更多。
请求/回复挂钩的示例包括preRequest、onSend、onTimeout和其他。
下面是一个使用钩子为从服务器发送的所有响应onSend添加标头的示例:Server
javascript
fastify.addHook("onSend", (request, reply, payload, done) => {
reply.headers({
Server: "fastify",
});
done();
});
和对象大家应该很熟悉了,参数 request就是响应体。您可以在此处修改响应有效负载,或者通过在挂钩函数中将其设置为来将其完全清除。最后,应该执行回调以表示挂钩结束,以便请求/回复生命周期继续。它最多可以接受两个参数:一个错误(如果有的话),或,以及更新后的有效负载。replypayloadnulldone()null
有了上面的代码,每个响应现在都将包含指定的标头:
壳
curl -I http://localhost:3000
文本
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
server: fastify
content-length: 12
Date: Sun, 12 Feb 2023 18:23:40 GMT
Connection: keep-alive
Keep-Alive: timeout=72
fastify如果你想为路由的子集实现钩子,你需要创建一个新的封装上下文,然后在插件中的实例上注册钩子。例如,您可以在本教程前面演示的插件onSend中创建另一个挂钩:health
javascript
// plugin.js
function health(fastify, options, next) {
fastify.get("/health", (request, reply) => {
reply.send({ status: "up" });
});
fastify.addHook("onSend", (request, reply, payload, done) => {
const newPlayload = payload.replace("up", "down");
reply.headers({
"Cache-Control": "no-store",
Server: "nodejs",
});
done(null, newPlayload);
});
next();
}
export default health;
这一次,onSend钩子用于修改插件上下文中所有路由的响应主体(/health在本例中只是 ),方法是更改up?为 down,并且它还Server在添加新标头时覆盖父上下文中的响应标头Cache-Control。因此,对 /health路由的请求现在将产生以下响应:
壳
curl -i http://localhost:3000/health
文本
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
cache-control: no-store
server: nodejs
content-length: 17
Date: Sun, 12 Feb 2023 18:54:21 GMT
Connection: keep-alive
Keep-Alive: timeout=72
{"status":"down"}?
请注意,上下文onSend中的挂钩health将在所有共享挂钩之后运行 onSend。如果你想在特定路由而不是插件上下文中的所有路由上注册一个钩子,你可以将它添加到路由选项中,如下所示:
javascript
function health(fastify, options, next) {
fastify.get(
"/health",
{
onSend: function (request, reply, payload, done) {
const newPlayload = payload.replace("up", "down");
reply.headers({
"Cache-Control": "no-store",
Server: "nodejs",
});
done(null, newPlayload);
},
},
(request, reply) => {
reply.send({ status: "up" });
}
);
fastify.get("/health/alwaysUp", (request, reply) => {
reply.send({ status: "up" });
});
next();
}
export default health;
该onSend钩子已移至该/health路由的路由选项,因此它仅影响该路由的请求/响应周期。/health您可以通过向和发出请求来观察差异,/health/alwaysUp如下所示。尽管它们在具有相同处理程序的相同插件上下文中,但它们响应的内容是不同的。
获取/健康:
文本
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
server: nodejs
cache-control: no-store
content-length: 17
Date: Sun, 12 Feb 2023 19:08:37 GMT
Connection: keep-alive
Keep-Alive: timeout=72
{"status":"down"}
获取/健康/始终向上:
文本
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
server: fastify
content-length: 15
Date: Sun, 12 Feb 2023 19:09:18 GMT
Connection: keep-alive
Keep-Alive: timeout=72
{"status":"up"}
Fastify 应用钩子
另一方面,应用程序挂钩是在请求/回复生命周期之外执行的。它们根据Fastify实例发出的事件执行,可用于执行一般服务器或插件任务,例如:
- 连接到数据库和其他资源
- 加载或卸载配置
- 关闭连接
- 持久化数据
- 刷新日志
和别的。
下面是一个使用 Fastify 的应用程序钩子模拟正常关闭的示例onClose 。如果您正在部署更新或您的服务器因其他原因需要重启,这样的设置是理想的。
javascript
fastify.addHook("onClose", (instance, done) => {
instance.log.info("Server is shutting down...");
// Perform any necessary cleanup tasks here
done();
});
process.on("SIGINT", () => {
fastify.close(() => {
fastify.log.info("Server has been shut down");
process.exit(0);
});
});
在此示例中,onClose挂钩在服务器的根上下文中注册,并且在服务器关闭之前执行回调。钩子函数可以访问当前Fastify实例和一个done回调函数,回调函数应该在钩子完成时调用。
↓文章在下面继续
您的应用程序损坏或运行缓慢吗?AppSignal 让您知道。
通过 AppSignal 监控 →
此外,该process.on()函数还监听在您按下或系统关闭SIGINT 时发送给进程的信号。CTRL+C收到信号后,fastify.close()将调用该函数关闭服务器,并将服务器关闭的记录记录到控制台。
将上述代码添加到您的程序后,启动服务器并按Ctrl-C 关闭进程。您将在控制台中观察到以下日志:
JSON
{"level":30,"time":1676240333903,"pid":615734,"hostname":"fedora","msg":"Server is shutting down..."}
{"level":30,"time":1676240333903,"pid":615734,"hostname":"fedora","msg":"Server has been shut down"}
Fastify 中的中间件
Fastify 也支持 Express 风格的中间件,但它需要你安装一个外部插件,比如 @fastify/express或 @fastify/middie。这简化了从 Express 到 Fastify 的迁移,但不应该在新项目中使用它来支持 hooks。请注意,在许多情况下,您可以找到提供与 Express 中间件相同功能的原生 Fastify 插件。
下面的示例演示了如何在 Fastify 中使用标准的 Express 中间件,例如 cookie-parser ,但您应该尽可能使用原生替代品——例如 @fastify/cookie——因为它们针对 Fastify 的使用进行了更好的优化。
javascript
import Fastify from "fastify";
import middie from "@fastify/middie";
import cookieParser from "cookie-parser";
const fastify = Fastify({
logger: true,
});
await fastify.register(middie);
fastify.use(cookieParser());
fastify.get("/", function (request, reply) {
console.log("Cookies: ", request.raw.cookies);
reply.send("Hello world!");
});
const port = process.env.PORT || 3000;
fastify.listen({ port }, function (err, address) {
if (err) {
fastify.log.error(err);
process.exit(1);
}
fastify.log.info(`Fastify is listening on port: ${address}`);
});
导入并注册插件后,您可以通过实例上提供的方法@fastify/middie开始使用 Express 中间件 ,它应该如上所示工作。请注意,以这种方式应用的每个中间件都将在挂钩阶段执行。use()FastifyonRequest
Fastify 中的装饰器
装饰器是一种允许您使用任何类型的 JavaScript 属性(例如函数、对象或任何原始类型)自定义核心对象的功能。例如,您可以使用装饰器来存储自定义数据或分别通过Fastify、Request、和方法在、或Reply对象中注册一个新方法。decorate()decorateRequest()decorateReply()
这个例子演示了如何使用 Fastify 的装饰器来为 Web 应用程序添加功能:
javascript
import Fastify from 'fastify';
import fastifyCookie from '@fastify/cookie';
const fastify = Fastify({
logger: true,
});
await fastify.register(fastifyCookie, {
secret: 'secret',
});
fastify.decorate('authorize', authorize);
fastify.decorate('getUser', getUser);
fastify.decorateRequest('user', null);
async function getUser(token) {
// imagine the token is used to retrieve a user
return {
id: 1234,
name: 'John Doe',
email: 'john@example.com',
};
}
async function authorize(request, reply) {
const { user_token } = request.cookies;
if (!user_token) {
throw new Error('unauthorized: missing session cookie');
}
const cookie = request.unsignCookie(user_token);
if (!cookie.valid) {
throw new Error('unauthorized: invalid cookie signature');
}
let user;
try {
user = await fastify.getUser(cookie.value);
} catch (err) {
request.log.warn(err);
throw err;
}
request.user = user;
}
fastify.get('/', async function (request, reply) {
await this.authorize(request, reply);
reply.send(`Hello ${request.user.name}!`);
});
. . .
上面的代码片段装饰了fastify实例上的两个函数。第一个是getUser()——它接受一个令牌作为参数并返回一个用户对象(在这个例子中是硬编码的)。
authorize()接下来定义函数。它检查user_token 请求中是否存在 cookie 并对其进行验证。如果 cookie 无效或丢失,则会抛出错误。否则,cookie 值用于通过函数检索相应的用户getUser(),并将结果存储在 user请求对象的属性中。如果在检索用户时发生错误,则会记录并重新抛出错误。
虽然您可以向 、 或 对象添加任何属性Fastify,Request但Reply您需要提前使用装饰器声明它们。这有助于底层 JavaScript 引擎优化对这些对象的处理。
壳
curl --cookie "user_token=yes.Y7pzW5FUVuoPD5yXLV8joDdR35gNiZJzITWeURHF5Tg" http://127.0.0.1:3000/
文本
Hello John Doe!
Fastify 中的数据验证
数据验证是任何依赖客户端数据的 Web 应用程序的基本功能,因为它有助于防止恶意负载引起的安全漏洞并提高应用程序的可靠性和健壮性。
Fastify 使用JSON schema为每个路由的输入负载定义验证规则,包括请求主体、查询字符串、参数和标头。JSON schema 是定义 JSON 数据结构和约束的标准格式,Fastify 使用 Ajv,这是可用的最快、最高效的 JSON schema 验证器之一。
要在 Fastify 中使用 JSON 验证,您需要为每个需要负载的路由定义一个模式。您可以使用标准的 JSON 模式格式或 Fastify JSON 模式格式指定模式,这是表达模式的更简洁和表达方式。
下面是一个如何在 Fastify 中为路由定义 JSON schema 的例子:
javascript
const bodySchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
email: { type: "string", format: "email" },
},
required: ["name", "email"],
};
fastify.post(
"/users",
{
schema: {
body: bodySchema,
},
},
async (request, reply) => {
const { name, age, email } = request.body;
reply.send({
name,
age,
email,
});
}
);
在此示例中,我们定义了一个模式,该模式需要一个具有三个属性的对象:name、age和email。该name属性应为字符串,age属性应为数字,email属性应为电子邮件格式的字符串。我们还指定 和name是email必需的属性。
当客户端发送/users带有无效负载的 POST 请求时,Fastify 会自动返回一个“400 Bad Request”响应,其中包含一条错误消息,指示哪个属性未通过验证。但是,如果有效载荷符合模式,则路由处理函数将使用解析后的有效载荷作为request.body.
这是一个带有无效负载的请求示例(email密钥格式错误):
壳
curl --header "Content-Type: application/json" \
--request POST \
--data '{"name":"John","age": 44,"email":"john@example"}' \
http://localhost:3000/users
这是 Fastify 产生的响应:
JSON
{"statusCode":400,"error":"Bad Request","message":"body/email must match format \"email\""}?
请参阅文档以了解有关 Fastify 中的验证以及如何自定义其行为以满足您的需求的更多信息。
计划从 Express 到 Fastify 的增量迁移
对于那些想要切换到新框架但又无力一次性进行所有更改的人来说,增量迁移是一种极好的策略。通过采用增量方法,您可以在仍然使用 Express 的同时逐步将 Fastify 引入您的项目,从而让您有时间进行任何必要的更改并确保平稳过渡。
第一步是确定项目中最能从 Fastify 的功能中获益的部分,例如它对验证和日志记录的内置支持,以及比 Express 更高的性能。一旦你确定了这些区域,你就可以在现有的 Express 代码中引入 Fastify。
这可能涉及设置一个单独的服务器实例,该实例使用 Fastify 处理某些路由或端点,同时仍然为应用程序的其余部分使用 Express。但是你可能会发现使用 @fastify/express插件为 Fastify 添加完整的 Express 兼容性更容易,这样你就可以像使用 Fastify 插件一样使用 Express 中间件和应用程序。
要使用该@fastify/express插件,您可以通过以下方式安装它npm并在您的 Fastify 实例中注册它:
javascript
import Fastify from "fastify";
import expressPlugin from "@fastify/express";
const fastify = Fastify({
logger: true,
});
await fastify.register(expressPlugin);
然后,您可以像在 Express 应用程序中一样使用 Express 中间件或应用程序。例如:
javascript
import express from "express";
const expressApp = express();
expressApp.use((req, res, next) => {
console.log("This is an Express middleware");
next();
});
expressApp.get("/express", (req, res) => {
res.json({ body: "hello from express" });
});
fastify.use(expressApp);
壳
curl http://localhost:3000/express
JSON
{ "body": "hello from express" }
随着您对 Fastify 越来越熟悉,您可以开始迁移越来越多的代码,最终用 Fastify 替换所有特定于 Express 的代码。通过花时间计划和执行深思熟虑的迁移策略,您可以确保顺利过渡并最终获得更高效、高性能的应用程序。
下一步:从 Express 迁移到 Fastify
我们在本教程中介绍了很多内容。我们希望您已经更深入地了解 Fastify 的新颖功能(如钩子和装饰器)如何比 Express 更能帮助您定制和扩展您的应用程序。
在本系列的下一部分和最后一部分中,我们将提供从 Express 迁移到 Fastify 的实用指南。我们将涵盖常见的迁移场景,提供优化性能和提高安全性的技巧,并在此过程中分享一些最佳实践。
感谢阅读,我们下次再见!
作者:Damilola Olatunji
Damilola is a freelance technical writer and software developer based in Lagos, Nigeria. He specializes in JavaScript and Node.js, and aims to deliver concise and practical articles for developers. When not writing or coding, he enjoys reading, playing games, and traveling.
出处:https://blog.appsignal.com/2023/05/24/advanced-fastify-hooks-middleware-and-decorators.html
猜你喜欢
- 2024-09-08 vue2组件系列第四十二节:NavBar 导航栏
- 2024-09-08 从零开始学Python——使用Selenium抓取动态网页数据
- 2024-09-08 黑客突破macOS的安全防御,新型恶意软件正在偷偷的窃取你的文件
- 2024-09-08 vue2组件系列第三十六节:Lazyload 图片懒加载
- 2024-09-08 Visa|实习面试|2022 暑假(visa issues)
- 2024-09-08 消息中间件RabbitMQ入门详解(消息中间件mq作用)
- 2024-09-08 vue2组件系列第二十二节:SwitchCell 开关单元格
- 2024-09-08 vue2组件系列第二十节:按钮式单选组件
- 2024-09-08 vue2组件系列第四十节:NoticeBar 通告栏
- 2024-09-08 vue2组件系列第二十六节:PasswordInput 密码输入框
你 发表评论:
欢迎- 07-10公司网站建站选择:人工建站和源码建站分析
- 07-10多用途游戏娱乐新闻网站HTML5模板
- 07-10站长教你搭建属于自己的网站(搭建网站的步骤)
- 07-10php宝塔搭建部署实战响应式塑料封条制品企业网站模板源码
- 07-10自适应响应式汽车配件类网站源码 html5高端大气汽车网站织梦模板
- 07-10网站标签怎么设置?(网站标签怎么设置比较好)
- 07-10PageAdmin企业网站制作中踩过的坑
- 07-10豆包给我输出的html在线象棋源码(有点简单)
- 最近发表
- 标签列表
-
- jdk (81)
- putty (66)
- rufus (78)
- 内网穿透 (89)
- okhttp (70)
- powertoys (74)
- windowsterminal (81)
- netcat (65)
- ghostscript (65)
- veracrypt (65)
- asp.netcore (70)
- wrk (67)
- aspose.words (80)
- itk (80)
- ajaxfileupload.js (66)
- sqlhelper (67)
- express.js (67)
- phpmailer (67)
- xjar (70)
- redisclient (78)
- wakeonlan (66)
- tinygo (85)
- startbbs (72)
- webftp (82)
- vsvim (79)
本文暂时没有评论,来添加一个吧(●'◡'●)