From 24b4247b7665800a0b80d578f037558d7f578d98 Mon Sep 17 00:00:00 2001 From: Ranko Orlic Date: Tue, 14 Nov 2023 12:32:44 +0100 Subject: [PATCH] chore: allow retries --- Dockerfile | 6 +++++- README.md | 2 ++ src/index.ts | 43 +++++++++++++++++++++++++++++++++---------- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index ce8254b..ba359b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,7 +41,11 @@ ARG MIMETYPE= ENV MIMETYPE=${MIMETYPE} ARG RANGE= ENV RANGE=${RANGE} +ARG MAX_RETRIES=5 +ENV MAX_RETRIES=${MAX_RETRIES} +ARG RETRY_TIMEOUT=10 +ENV RETRY_TIMEOUT=${RETRY_TIMEOUT} ## set start command ENTRYPOINT ["/usr/bin/dumb-init", "--"] USER node -CMD ["sh", "-c", "node index.js --silent=${SILENT} --range=${RANGE} --mimeType=\"${MIMETYPE}\" --targetUrl=${TARGETURL} --cron=\"${CRON}\" --template=\"${TEMPLATE}\" --templateFile=${TEMPLATEFILE}"] +CMD ["sh", "-c", "node index.js --silent=${SILENT} --retryTimeout=${RETRY_TIMEOUT} --maxRFetries=${MAX_RETRIES} --range=${RANGE} --mimeType=\"${MIMETYPE}\" --targetUrl=${TARGETURL} --cron=\"${CRON}\" --template=\"${TEMPLATE}\" --templateFile=${TEMPLATEFILE}"] diff --git a/README.md b/README.md index 1795696..759f58f 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ The generator takes the following command line arguments: * `--template=''` allows to provide the template on the command line, no default (if not provided, you MUST provide `--templateFile`) * `--templateFile=` allows to provide the template in a file, no default (if not provided, you MUST provide `--template`) * `--range=` allows to create a range of values (1 to ``) on every tick instead of an increasing index +* `--maxRetries=` allows to retry some (possibly retryable) HTTP status codes (429, 500 & 503) a maximum number of times +* `--retryTimeout=` the amount of time (in milliseconds) to wait between retrying to send the message again The template or template file should simply contain a message with mustache variables (between `{{` and `}}`). E.g.: ```json diff --git a/src/index.ts b/src/index.ts index 7644c42..588ba8a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,28 +10,51 @@ if (!silent) console.debug("Arguments: ", args); const cron = args['cron'] || '* * * * * *'; const mimeType = args['mimeType']; +const maxRetries = Number.parseInt(args['maxRetries'] || '5'); +const retryTimeout = Number.parseInt(args['retryTimeout'] || '10'); let template: string = args['template'] || (args['templateFile'] && readFileSync(args['templateFile'], {encoding: 'utf8'})); if (!template) throw new Error('Missing template or templateFile'); const generator: Generator = new Generator(template); const range = Number.parseInt(args['range'] || '0'); +const nonFatalHttpCodes : number[] = [429, 500, 503]; +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); +let messageCount = 0; const job = new CronJob(cron, async () => { const timestamp = new Date().toISOString(); const messages = range ? generator.createRange(range) : [generator.createNext()]; + const targetUrl = args['targetUrl'] || (existsSync('./TARGETURL') && readFileSync('./TARGETURL', 'utf-8').trimEnd()); for await (const body of messages) { - const targetUrl = args['targetUrl'] || (existsSync('./TARGETURL') && readFileSync('./TARGETURL', 'utf-8').trimEnd()); + ++messageCount; + if (targetUrl) { if (!mimeType) throw new Error('Missing mimeType'); - - if (!silent) console.debug(`Sending to '${targetUrl}':`, body); - const response = await fetch(targetUrl, { - method: 'post', - body: body, - headers: {'Content-Type': mimeType} - }); - if (!silent) console.debug(`Response: ${response.statusText}`); - if (silent) console.info(`[${timestamp}] POST ${targetUrl} ${response.status}`); + let retry = false; + let retries = 0; + do { + try { + if (!silent) { + const msg = retry ? `Retrying (${retries} of ${maxRetries}) send` : "Sending"; + console.debug(`${msg} message ${messageCount} to ${targetUrl}`, body); + } + const response = await fetch(targetUrl, { + method: 'post', + body: body, + headers: {'Content-Type': mimeType} + }); + if (!silent) console.debug(`Response: ${response.statusText}`); + if (silent) { + const msg = retry ? ` (retry ${retries} of ${maxRetries})` : ' '; + console.info(`[${timestamp}] POST ${targetUrl} (msg: ${messageCount}) ${response.status}${msg}`); + } + retry = nonFatalHttpCodes.includes(response.status); + } catch (error) { + const msg = error instanceof Error ? `${error.name}: ${error.message}, reason: ${error.cause}` : String(error); + console.error(`ERROR: cannot POST to ${targetUrl} because: ${msg}`); + retry = true; + } + } while (retry && (++retries <= maxRetries) && (await sleep(retryTimeout), true)); } else { // if no targetUrl specified, send to console console.info(body); }