客户端中止请求的检测
引言
Fastify 提供了在请求生命周期中的某些点触发的请求事件。然而,没有内置机制来检测客户端意外断开连接的情况,例如当客户端的互联网连接中断时。本指南介绍了如何检测客户端何时有意终止请求。
请注意,Fastify 的 clientErrorHandler
不用于检测客户端中止请求的情况。这与标准 Node HTTP 模块的工作方式相同,在出现错误请求或超大头数据时触发 clientError
事件。当客户端中止请求时,套接字上没有错误,并且 clientErrorHandler
将不会被触发。
解决方案
概述
所提出的解决方案是一种可能的方法,用于检测客户端故意终止请求的情况,例如当浏览器关闭或从您的客户端应用程序中终止 HTTP 请求时。如果您的应用代码存在错误导致服务器崩溃,则您可能需要额外的逻辑以避免误判为故意终止。
目标是检测到客户端故意断开连接,以便您的应用程序逻辑可以相应地继续执行。这在日志记录或停止业务逻辑方面可能会很有用。
实战操作
假设我们有以下基础服务器设置:
import Fastify from 'fastify';
const sleep = async (time) => {
return await new Promise(resolve => setTimeout(resolve, time || 1000));
}
const app = Fastify({
logger: {
transport: {
target: 'pino-pretty',
options: {
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
},
},
})
app.addHook('onRequest', async (request, reply) => {
request.raw.on('close', () => {
if (request.raw.aborted) {
app.log.info('请求关闭')
}
})
})
app.get('/', async (request, reply) => {
await sleep(3000)
reply.code(200).send({ ok: true })
})
const start = async () => {
try {
await app.listen({ port: 3000 })
} catch (err) {
app.log.error(err)
process.exit(1)
}
}
start()
我们的代码设置了一个Fastify服务器,包括以下功能:
- 在 http://localhost:3000 接受请求,并返回延迟3秒的
{ ok: true }
响应。 - 每当收到请求时触发
onRequest
钩子。 - 当请求关闭时,在钩子中执行逻辑。
- 当已关闭请求属性
aborted
为真时进行日志记录。
虽然 aborted
属性已被弃用,但 destroyed
并不是一个合适的替代方案,因为 Node.js 文档建议 。请求可以因多种原因被销毁,例如当服务器关闭连接时。因此,aborted
属性仍然是检测客户端故意中止请求的最可靠方式。
您也可以在钩子之外,在特定路由中直接执行此逻辑。
app.get('/', async (request, reply) => {
request.raw.on('close', () => {
if (request.raw.aborted) {
app.log.info('请求关闭')
}
})
await sleep(3000)
reply.code(200).send({ ok: true })
})
在业务逻辑的任何阶段,您可以检查请求是否已被中止,并执行替代操作。
app.get('/', async (request, reply) => {
await sleep(3000)
if (request.raw.aborted) {
// 在这里执行某些操作
}
await sleep(3000)
reply.code(200).send({ ok: true })
})
在应用程序代码中添加此功能的一个好处是,您可以记录Fastify的详细信息(如reqId),这些信息可能在仅具有原始请求信息访问权限的较低级别代码中不可用。
测试
要测试此功能,您可以使用类似 Postman 的应用程序并在 3 秒内取消您的请求。或者,您也可以使用 Node 发送一个带有逻辑在 3 秒之前中止请求的 HTTP 请求。示例:
const controller = new AbortController();
const signal = controller.signal;
(async () => {
try {
const response = await fetch('http://localhost:3000', { signal });
const body = await response.text();
console.log(body);
} catch (error) {
console.error(error);
}
})();
setTimeout(() => {
controller.abort()
}, 1000);
无论采用哪种方法,您都应在请求被中止的时刻看到 Fastify 日志。
结论
实现的具体细节会因问题而异,但本指南的主要目的是展示一个非常具体的用例,在Fastify生态系统中可以解决的问题。
你可以监听请求关闭事件,并确定请求是被中断还是成功送达。你可以在onRequest钩子或单独的路由中直接实现此解决方案。
这种方法在遇到互联网中断时不会触发,此类检测需要额外的业务逻辑。如果你的后端应用程序逻辑有缺陷导致服务器崩溃,则可能会引发误报。clientErrorHandler
(无论是默认配置还是自定义逻辑)不打算处理这种情况,并且当客户端中断请求时也不会触发。