Skip to content

Commit e37a3cc

Browse files
fix(webhook): adicionar timeout configurável
Implementa timeout configurável nas requisições de webhook: - Aplica configuração em todas as instâncias axios - Usa valor padrão de 30 segundos se não configurado - Evita requisições penduradas indefinidamente Issue: #1325
1 parent 5156ea5 commit e37a3cc

File tree

1 file changed

+54
-7
lines changed

1 file changed

+54
-7
lines changed

src/api/integrations/event/webhook/webhook.controller.ts

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export class WebhookController extends EventController implements EventControlle
115115
const httpService = axios.create({
116116
baseURL,
117117
headers: webhookHeaders as Record<string, string> | undefined,
118+
timeout: webhookConfig.REQUEST?.TIMEOUT_MS ?? 30000,
118119
});
119120

120121
await this.retryWebhookRequest(httpService, webhookData, `${origin}.sendData-Webhook`, baseURL, serverUrl);
@@ -156,7 +157,10 @@ export class WebhookController extends EventController implements EventControlle
156157

157158
try {
158159
if (isURL(globalURL)) {
159-
const httpService = axios.create({ baseURL: globalURL });
160+
const httpService = axios.create({
161+
baseURL: globalURL,
162+
timeout: webhookConfig.REQUEST?.TIMEOUT_MS ?? 30000,
163+
});
160164

161165
await this.retryWebhookRequest(
162166
httpService,
@@ -190,12 +194,21 @@ export class WebhookController extends EventController implements EventControlle
190194
origin: string,
191195
baseURL: string,
192196
serverUrl: string,
193-
maxRetries = 10,
194-
delaySeconds = 30,
197+
maxRetries?: number,
198+
delaySeconds?: number,
195199
): Promise<void> {
200+
// Obter configurações de retry das variáveis de ambiente
201+
const webhookConfig = configService.get<Webhook>('WEBHOOK');
202+
const maxRetryAttempts = maxRetries ?? webhookConfig.RETRY?.MAX_ATTEMPTS ?? 10;
203+
const initialDelay = delaySeconds ?? webhookConfig.RETRY?.INITIAL_DELAY_SECONDS ?? 5;
204+
const useExponentialBackoff = webhookConfig.RETRY?.USE_EXPONENTIAL_BACKOFF ?? true;
205+
const maxDelay = webhookConfig.RETRY?.MAX_DELAY_SECONDS ?? 300;
206+
const jitterFactor = webhookConfig.RETRY?.JITTER_FACTOR ?? 0.2;
207+
const nonRetryableStatusCodes = webhookConfig.RETRY?.NON_RETRYABLE_STATUS_CODES ?? [400, 401, 403, 404, 422];
208+
196209
let attempts = 0;
197210

198-
while (attempts < maxRetries) {
211+
while (attempts < maxRetryAttempts) {
199212
try {
200213
await httpService.post('', webhookData);
201214
if (attempts > 0) {
@@ -208,25 +221,59 @@ export class WebhookController extends EventController implements EventControlle
208221
return;
209222
} catch (error) {
210223
attempts++;
224+
225+
// Verificar se é um erro de timeout
226+
const isTimeout = error.code === 'ECONNABORTED';
227+
228+
// Verificar se o erro não deve gerar retry com base no status code
229+
if (error?.response?.status && nonRetryableStatusCodes.includes(error.response.status)) {
230+
this.logger.error({
231+
local: `${origin}`,
232+
message: `Erro não recuperável (${error.response.status}): ${error?.message}. Cancelando retentativas.`,
233+
statusCode: error?.response?.status,
234+
url: baseURL,
235+
server_url: serverUrl,
236+
});
237+
throw error;
238+
}
211239

212240
this.logger.error({
213241
local: `${origin}`,
214-
message: `Tentativa ${attempts}/${maxRetries} falhou: ${error?.message}`,
242+
message: `Tentativa ${attempts}/${maxRetryAttempts} falhou: ${isTimeout ? 'Timeout da requisição' : error?.message}`,
215243
hostName: error?.hostname,
216244
syscall: error?.syscall,
217245
code: error?.code,
246+
isTimeout,
247+
statusCode: error?.response?.status,
218248
error: error?.errno,
219249
stack: error?.stack,
220250
name: error?.name,
221251
url: baseURL,
222252
server_url: serverUrl,
223253
});
224254

225-
if (attempts === maxRetries) {
255+
if (attempts === maxRetryAttempts) {
226256
throw error;
227257
}
228258

229-
await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1000));
259+
// Cálculo do delay com backoff exponencial e jitter
260+
let nextDelay = initialDelay;
261+
if (useExponentialBackoff) {
262+
// Fórmula: initialDelay * (2^attempts) com limite máximo
263+
nextDelay = Math.min(initialDelay * Math.pow(2, attempts - 1), maxDelay);
264+
265+
// Adicionar jitter para evitar "thundering herd"
266+
const jitter = nextDelay * jitterFactor * (Math.random() * 2 - 1);
267+
nextDelay = Math.max(initialDelay, nextDelay + jitter);
268+
}
269+
270+
this.logger.log({
271+
local: `${origin}`,
272+
message: `Aguardando ${nextDelay.toFixed(1)} segundos antes da próxima tentativa`,
273+
url: baseURL,
274+
});
275+
276+
await new Promise((resolve) => setTimeout(resolve, nextDelay * 1000));
230277
}
231278
}
232279
}

0 commit comments

Comments
 (0)