检测客户端何时中止
¥Detecting When Clients Abort
介绍
¥Introduction
Fastify 提供了在请求生命周期中的某些点触发的请求事件。但是,没有内置机制来检测客户端意外断开连接的情况,例如客户端的 Internet 连接中断时。本指南介绍了检测客户端是否以及何时故意中止请求的方法。
¥Fastify provides request events to trigger at certain points in a request's lifecycle. However, there isn't a built-in mechanism to detect unintentional client disconnection scenarios such as when the client's internet connection is interrupted. This guide covers methods to detect if and when a client intentionally aborts a request.
请记住,Fastify 的 clientErrorHandler
并非设计用于检测客户端何时中止请求。它的工作方式与标准 Node HTTP 模块相同,当出现错误请求或标头数据过大时,它会触发 clientError
事件。当客户端中止请求时,套接字上没有错误,clientErrorHandler
不会被触发。
¥Keep in mind, Fastify's clientErrorHandler
is not designed to detect when a
client aborts a request. This works in the same way as the standard Node HTTP
module, which triggers the clientError
event when there is a bad request or
exceedingly large header data. When a client aborts a request, there is no
error on the socket and the clientErrorHandler
will not be triggered.
解决方案
¥Solution
概述
¥Overview
所提出的解决方案是一种检测客户端何时故意中止请求的可能方法,例如何时关闭浏览器或何时从客户端应用中止 HTTP 请求。如果你的应用代码中存在导致服务器崩溃的错误,你可能需要额外的逻辑来避免错误的中止检测。
¥The proposed solution is a possible way of detecting when a client intentionally aborts a request, such as when a browser is closed or the HTTP request is aborted from your client application. If there is an error in your application code that results in the server crashing, you may require additional logic to avoid a false abort detection.
这里的目标是检测客户端何时故意中止连接,以便你的应用逻辑可以相应地继续进行。这对于日志记录或停止业务逻辑很有用。
¥The goal here is to detect when a client intentionally aborts a connection so your application logic can proceed accordingly. This can be useful for logging purposes or halting business logic.
实际操作
¥Hands-on
假设我们设置了以下基础服务器:
¥Say we have the following base server set up:
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('request closed')
}
})
})
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 服务器,其中包括以下功能:
¥Our code is setting up a Fastify server which includes the following functionality:
在 http://localhost:3000 接受请求,
{ ok: true }
的响应延迟 3 秒。¥Accepting requests at http://localhost:3000, with a 3 second delayed response of
{ ok: true }
.收到每个请求时触发的 onRequest 钩子。
¥An onRequest hook that triggers when every request is received.
请求关闭时在钩子中触发的逻辑。
¥Logic that triggers in the hook when the request is closed.
当关闭的请求属性
aborted
为真时发生的日志记录。¥Logging that occurs when the closed request property
aborted
is true.
虽然 aborted
属性已被弃用,但 destroyed
并不是 Node.js 文档建议 的合适替代品。请求可以由于各种原因成为 destroyed
,例如当服务器关闭连接时。aborted
属性仍然是检测客户端何时故意中止请求的最可靠方法。
¥Whilst the aborted
property has been deprecated, destroyed
is not a
suitable replacement as the
Node.js documentation suggests.
A request can be destroyed
for various reasons, such as when the server closes
the connection. The aborted
property is still the most reliable way to detect
when a client intentionally aborts a request.
你还可以在钩子之外直接在特定路由中执行此逻辑。
¥You can also perform this logic outside of a hook, directly in a specific route.
app.get('/', async (request, reply) => {
request.raw.on('close', () => {
if (request.raw.aborted) {
app.log.info('request closed')
}
})
await sleep(3000)
reply.code(200).send({ ok: true })
})
在业务逻辑中的任何时刻,你都可以检查请求是否已中止并执行替代操作。
¥At any point in your business logic, you can check if the request has been aborted and perform alternative actions.
app.get('/', async (request, reply) => {
await sleep(3000)
if (request.raw.aborted) {
// do something here
}
await sleep(3000)
reply.code(200).send({ ok: true })
})
在应用代码中添加此内容的好处是,你可以记录 Fastify 详细信息,例如 reqId,这在只能访问原始请求信息的更底层代码中可能不可用。
¥A benefit to adding this in your application code is that you can log Fastify details such as the reqId, which may be unavailable in lower-level code that only has access to the raw request information.
测试
¥Testing
要测试此功能,你可以使用 Postman 等应用并在 3 秒内取消你的请求。或者,你可以使用 Node 发送 HTTP 请求,并带有在 3 秒之前中止请求的逻辑。示例:
¥To test this functionality you can use an app like Postman and cancel your request within 3 seconds. Alternatively, you can use Node to send an HTTP request with logic to abort the request before 3 seconds. Example:
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 日志在请求中止时出现。
¥With either approach, you should see the Fastify log appear at the moment the request is aborted.
结论
¥Conclusion
每个问题的实现细节都会有所不同,但本指南的主要目标是展示可以在 Fastify 生态系统中解决的问题的一个非常具体的用例。
¥Specifics of the implementation will vary from one problem to another, but the main goal of this guide was to show a very specific use case of an issue that could be solved within Fastify's ecosystem.
你可以监听请求关闭事件并确定请求是否被中止或是否已成功传递。你可以在 onRequest 钩子中或直接在单独的路由中实现此解决方案。
¥You can listen to the request close event and determine if the request was aborted or if it was successfully delivered. You can implement this solution in an onRequest hook or directly in an individual route.
如果互联网中断,这种方法不会触发,并且这种检测需要额外的业务逻辑。如果你的后端应用逻辑存在缺陷,导致服务器崩溃,则可能会触发错误检测。clientErrorHandler
(无论是默认还是使用自定义逻辑)不适用于处理这种情况,并且在客户端中止请求时不会触发。
¥This approach will not trigger in the event of internet disruption, and such
detection would require additional business logic. If you have flawed backend
application logic that results in a server crash, then you could trigger a
false detection. The clientErrorHandler
, either by default or with custom
logic, is not intended to handle this scenario and will not trigger when the
client aborts a request.