From 5c148fdd3c32a392de23181f90cf8a8bf2039a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= Date: Wed, 25 Oct 2023 18:48:52 +0100 Subject: [PATCH] feat: support for handling of socket hangup Tries to solve the problem reported in https://github.com/node-fetch/node-fetch/issues/1735. This can be considered a persistent issue that is only minimized by this solution. --- CHANGELOG.md | 2 +- js/api/api.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d2d576..05c9648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -* +* Made changes to the way connections are handled so that connections that are kept-alive are do not generate ECONNRESET - related to [node-fetch/node-fetch#1735](https://github.com/node-fetch/node-fetch/issues/1735) ### Fixed diff --git a/js/api/api.js b/js/api/api.js index 0f1afc7..603df66 100644 --- a/js/api/api.js +++ b/js/api/api.js @@ -76,7 +76,7 @@ export class API extends Observable { async _methodBasic(method, url, options = {}) { const params = options.params !== undefined ? options.params : {}; - const headers = options.headers !== undefined ? options.headers : {}; + let headers = options.headers !== undefined ? options.headers : {}; const kwargs = options.kwargs !== undefined ? options.kwargs : {}; const handle = options.handle !== undefined ? options.handle : true; const getAgent = options.getAgent !== undefined ? options.getAgent : undefined; @@ -87,7 +87,22 @@ export class API extends Observable { }); const query = urlEncode(params || {}); if (query) url += url.includes("?") ? "&" + query : "?" + query; - const response = await fetch(url, { + + // runs a dummy agent retrieval to check if the current agent + // is set to keep connections alive + const _getAgent = getAgent || globals.getAgent || undefined; + const agent = _getAgent ? _getAgent(new URL(url)) : undefined; + const keepAlive = (agent && agent.keepAlive) || false; + + headers = Object.assign({}, headers); + if (keepAlive) headers.Connection = "keep-alive"; + + // adds a new forced loop, tick to the event loop, this should + // help solve closed connection handling problems in node-fetch + // while not adding extra issues + await new Promise(resolve => setTimeout(resolve, 0)); + + const response = await fetchRetry(url, { method: method, headers: headers || {}, agent: getAgent || globals.getAgent || undefined @@ -133,10 +148,22 @@ export class API extends Observable { mime = mime || "application/x-www-form-urlencoded"; } + // runs a dummy agent retrieval to check if the current agent + // is set to keep connections alive + const _getAgent = getAgent || globals.getAgent || undefined; + const agent = _getAgent ? _getAgent(new URL(url)) : undefined; + const keepAlive = (agent && agent.keepAlive) || false; + headers = Object.assign({}, headers); if (mime) headers["Content-Type"] = mime; + if (keepAlive) headers.Connection = "keep-alive"; - const response = await fetch(url, { + // adds a new forced loop, tick to the event loop, this should + // help solve closed connection handling problems in node-fetch + // while not adding extra issues + await new Promise(resolve => setTimeout(resolve, 0)); + + const response = await fetchRetry(url, { method: method, headers: headers || {}, body: data, @@ -255,6 +282,28 @@ export class API extends Observable { } } +export const fetchRetry = async (url, options = {}, retries = 5, delay = 0, timeout = 100) => { + let response = null; + while (!response && retries > 0) { + const start = Date.now(); + try { + response = await fetch(url, options); + } catch (error) { + const isHangup = + error.name === "FetchError" && + error.code === "ECONNRESET" && + error.message.includes("socket hang up"); + const elapsed = Date.now() - start; + if (retries === 0 || elapsed > timeout || !isHangup) { + throw error; + } + await new Promise(resolve => setTimeout(resolve, delay)); + retries--; + } + } + return response; +}; + export const buildGetAgent = (AgentHttp, AgentHttps, set = true, options = {}) => { const httpAgent = new AgentHttp({ keepAlive: options.keepAlive === undefined ? true : options.keepAlive,