跳到主要內容
版本:最新版 (v5.0.x)

偵測客戶端中止

簡介

Fastify 提供請求事件,以在請求生命週期的特定點觸發。然而,沒有內建機制可以偵測非預期的客戶端斷線情況,例如客戶端的網路連線中斷。本指南涵蓋偵測客戶端是否以及何時主動中止請求的方法。

請記住,Fastify 的 clientErrorHandler 並非設計用來偵測客戶端何時中止請求。其運作方式與標準 Node HTTP 模組相同,當有錯誤請求或過大的標頭資料時,會觸發 clientError 事件。當客戶端中止請求時,socket 上不會有錯誤,並且不會觸發 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('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 伺服器,其中包含以下功能

  • 接受 https://127.0.0.1:3000 的請求,並以 3 秒的延遲回應 { ok: true }
  • 一個 onRequest 掛鉤,在每次收到請求時觸發。
  • 當請求關閉時,在掛鉤中觸發的邏輯。
  • 當關閉的請求屬性 aborted 為 true 時,會進行日誌記錄。

雖然 aborted 屬性已被棄用,但 destroyed 並不是合適的替代品,如同 Node.js 文件建議。請求可能會因各種原因而 destroyed,例如當伺服器關閉連線時。aborted 屬性仍然是偵測客戶端何時主動中止請求最可靠的方法。

您也可以在掛鉤之外,直接在特定路由中執行此邏輯。

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

在您的業務邏輯中的任何一點,您可以檢查請求是否已中止並執行其他操作。

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,這些資訊在只能存取原始請求資訊的較低階程式碼中可能無法使用。

測試

要測試此功能,您可以使用像 Postman 這樣的應用程式,並在 3 秒內取消您的請求。或者,您可以使用 Node 發送一個 HTTP 請求,並在 3 秒前中止該請求。範例

const controller = new AbortController();
const signal = controller.signal;

(async () => {
try {
const response = await fetch('https://127.0.0.1:3000', { signal });
const body = await response.text();
console.log(body);
} catch (error) {
console.error(error);
}
})();

setTimeout(() => {
controller.abort()
}, 1000);

無論使用哪種方法,您都應該在請求中止時看到 Fastify 日誌出現。

結論

實作的細節會因問題而異,但本指南的主要目標是展示一個非常具體的使用案例,該案例可以在 Fastify 的生態系統中解決。

您可以監聽請求關閉事件,並判斷請求是否中止或是否已成功傳遞。您可以在 onRequest 掛鉤或直接在個別路由中實作此解決方案。

此方法不會在網路中斷的情況下觸發,此類偵測將需要額外的業務邏輯。如果您的後端應用程式邏輯存在缺陷導致伺服器崩潰,則可能會觸發錯誤的偵測。clientErrorHandler,無論是預設的還是使用自訂邏輯,都無意處理這種情況,並且在客戶端中止請求時不會觸發。