From 30f9e358b7cacb118e0dd78a8e6de9cb98ea255d Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Wed, 27 Mar 2024 22:02:26 -0700 Subject: [PATCH] cameras: fix fetch timeout bugs --- plugins/amcrest/package-lock.json | 4 +- plugins/amcrest/package.json | 2 +- plugins/hikvision/package-lock.json | 4 +- plugins/hikvision/package.json | 2 +- plugins/onvif/package-lock.json | 4 +- plugins/onvif/package.json | 2 +- plugins/reolink/package-lock.json | 4 +- plugins/reolink/package.json | 2 +- plugins/snapshot/package-lock.json | 4 +- plugins/snapshot/package.json | 2 +- server/src/fetch/http-fetch.ts | 57 +++++++++++++++++++---------- server/src/fetch/index.ts | 54 ++++++++++++++++----------- 12 files changed, 85 insertions(+), 56 deletions(-) diff --git a/plugins/amcrest/package-lock.json b/plugins/amcrest/package-lock.json index b204efb1b0..0afa2f63d9 100644 --- a/plugins/amcrest/package-lock.json +++ b/plugins/amcrest/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/amcrest", - "version": "0.0.142", + "version": "0.0.143", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@scrypted/amcrest", - "version": "0.0.142", + "version": "0.0.143", "license": "Apache", "dependencies": { "@scrypted/common": "file:../../common", diff --git a/plugins/amcrest/package.json b/plugins/amcrest/package.json index 50d2714b8c..2cf865ae34 100644 --- a/plugins/amcrest/package.json +++ b/plugins/amcrest/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/amcrest", - "version": "0.0.142", + "version": "0.0.143", "description": "Amcrest Plugin for Scrypted", "author": "Scrypted", "license": "Apache", diff --git a/plugins/hikvision/package-lock.json b/plugins/hikvision/package-lock.json index 388b9e52d8..59f1e1bea8 100644 --- a/plugins/hikvision/package-lock.json +++ b/plugins/hikvision/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/hikvision", - "version": "0.0.144", + "version": "0.0.145", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/hikvision", - "version": "0.0.144", + "version": "0.0.145", "license": "Apache", "dependencies": { "@scrypted/common": "file:../../common", diff --git a/plugins/hikvision/package.json b/plugins/hikvision/package.json index adc014ed18..8bd252dd30 100644 --- a/plugins/hikvision/package.json +++ b/plugins/hikvision/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/hikvision", - "version": "0.0.144", + "version": "0.0.145", "description": "Hikvision Plugin for Scrypted", "author": "Scrypted", "license": "Apache", diff --git a/plugins/onvif/package-lock.json b/plugins/onvif/package-lock.json index 3784c08439..9267ba227d 100644 --- a/plugins/onvif/package-lock.json +++ b/plugins/onvif/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/onvif", - "version": "0.1.11", + "version": "0.1.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@scrypted/onvif", - "version": "0.1.11", + "version": "0.1.12", "license": "Apache", "dependencies": { "@scrypted/common": "file:../../common", diff --git a/plugins/onvif/package.json b/plugins/onvif/package.json index 87379d5c82..7a5d51f2bc 100644 --- a/plugins/onvif/package.json +++ b/plugins/onvif/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/onvif", - "version": "0.1.11", + "version": "0.1.12", "description": "ONVIF Camera Plugin for Scrypted", "author": "Scrypted", "license": "Apache", diff --git a/plugins/reolink/package-lock.json b/plugins/reolink/package-lock.json index 31684873f8..1f60bc4f9a 100644 --- a/plugins/reolink/package-lock.json +++ b/plugins/reolink/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/reolink", - "version": "0.0.63", + "version": "0.0.64", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@scrypted/reolink", - "version": "0.0.63", + "version": "0.0.64", "license": "Apache", "dependencies": { "@scrypted/common": "file:../../common", diff --git a/plugins/reolink/package.json b/plugins/reolink/package.json index 1ea07b54fe..f1e7f95471 100644 --- a/plugins/reolink/package.json +++ b/plugins/reolink/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/reolink", - "version": "0.0.63", + "version": "0.0.64", "description": "Reolink Plugin for Scrypted", "author": "Scrypted", "license": "Apache", diff --git a/plugins/snapshot/package-lock.json b/plugins/snapshot/package-lock.json index 0df319be89..c1c8b5040a 100644 --- a/plugins/snapshot/package-lock.json +++ b/plugins/snapshot/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/snapshot", - "version": "0.2.40", + "version": "0.2.41", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/snapshot", - "version": "0.2.40", + "version": "0.2.41", "dependencies": { "@types/node": "^20.10.6", "sharp": "^0.33.1", diff --git a/plugins/snapshot/package.json b/plugins/snapshot/package.json index 637476ccc5..0d468b377b 100644 --- a/plugins/snapshot/package.json +++ b/plugins/snapshot/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/snapshot", - "version": "0.2.40", + "version": "0.2.41", "description": "Snapshot Plugin for Scrypted", "scripts": { "scrypted-setup-project": "scrypted-setup-project", diff --git a/server/src/fetch/http-fetch.ts b/server/src/fetch/http-fetch.ts index b1fe1064ff..71307a301c 100644 --- a/server/src/fetch/http-fetch.ts +++ b/server/src/fetch/http-fetch.ts @@ -93,44 +93,61 @@ export async function httpFetch>(options: T } } + let controller: AbortController; + let timeout: NodeJS.Timeout; + if (options.timeout) { + controller = new AbortController(); + timeout = setTimeout(() => controller.abort(), options.timeout); + } + const request = proto.request(url, { method: getFetchMethod(options), rejectUnauthorized: options.rejectUnauthorized, family: options.family, headers: nodeHeaders, + signal: controller?.signal || options.signal, timeout: options.timeout, }); - options.signal?.addEventListener('abort', () => request.destroy(new Error('abort'))); + if (controller) + options.signal?.addEventListener('abort', () => controller.abort('abort')); + else + options.signal?.addEventListener('abort', () => request.destroy(new Error('abort'))); if (body) body.pipe(request); else request.end(); - const [response] = await once(request, 'response') as [IncomingMessage]; - if (!options?.ignoreStatusCode) { - try { - checkStatus(response.statusCode); - } - catch (e) { - readMessageBuffer(response).catch(() => { }); - throw e; + try { + const [response] = await once(request, 'response') as [IncomingMessage]; + + if (!options?.ignoreStatusCode) { + try { + checkStatus(response.statusCode); + } + catch (e) { + readMessageBuffer(response).catch(() => { }); + throw e; + } } - } - const incomingHeaders = new Headers(); - for (const [k, v] of Object.entries(response.headers)) { - for (const vv of (typeof v === 'string' ? [v] : v)) { - incomingHeaders.append(k, vv) + const incomingHeaders = new Headers(); + for (const [k, v] of Object.entries(response.headers)) { + for (const vv of (typeof v === 'string' ? [v] : v)) { + incomingHeaders.append(k, vv) + } } - } - return { - statusCode: response.statusCode, - headers: incomingHeaders, - body: await httpFetchParseIncomingMessage(response, options.responseType), - }; + return { + statusCode: response.statusCode, + headers: incomingHeaders, + body: await httpFetchParseIncomingMessage(response, options.responseType), + }; + } + finally { + clearTimeout(timeout); + } } function ensureType(v: T) { diff --git a/server/src/fetch/index.ts b/server/src/fetch/index.ts index 0f28bc9c33..b28fc7d34d 100644 --- a/server/src/fetch/index.ts +++ b/server/src/fetch/index.ts @@ -124,30 +124,42 @@ export async function domFetch>(options: T) body = createStringOrBufferBody(headers, body); } - const { url } = options; - const response = await fetch(url, { - method: getFetchMethod(options), - credentials: options.withCredentials ? 'include' : undefined, - headers, - signal: options.signal || options.timeout ? AbortSignal.timeout(options.timeout) : undefined, - body, - }); + let controller: AbortController; + let timeout: NodeJS.Timeout; + if (options.timeout) { + controller = new AbortController(); + timeout = setTimeout(() => controller.abort(), options.timeout); + } - if (!options?.ignoreStatusCode) { - try { - checkStatus(response.status); - } - catch (e) { - response.arrayBuffer().catch(() => { }); - throw e; + try { + const { url } = options; + const response = await fetch(url, { + method: getFetchMethod(options), + credentials: options.withCredentials ? 'include' : undefined, + headers, + signal: controller?.signal || options.signal, + body, + }); + + if (!options?.ignoreStatusCode) { + try { + checkStatus(response.status); + } + catch (e) { + response.arrayBuffer().catch(() => { }); + throw e; + } } - } - return { - statusCode: response.status, - headers: response.headers, - body: await domFetchParseIncomingMessage(response, options.responseType), - }; + return { + statusCode: response.status, + headers: response.headers, + body: await domFetchParseIncomingMessage(response, options.responseType), + }; + } + finally { + clearTimeout(timeout); + } } function ensureType(v: T) {