From c52df1e3bbae65179b48af6662e08e827f879bac Mon Sep 17 00:00:00 2001 From: Nazar Leush Date: Thu, 25 Jun 2026 16:54:42 +0300 Subject: [PATCH 1/6] reuse function `skipLoggingFetchError` to check not logged errors --- lib/fetch.js | 8 ++++++-- lib/plugins/system/htmlparser/htmlparser.js | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/fetch.js b/lib/fetch.js index 3ae061c32..32f3c8c9e 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -38,6 +38,10 @@ const fetchH1 = h1NoCache({ rejectUnauthorized: false // By default skip auth check for all. }).fetch; +export function skipLoggingFetchError(error) { + return error?.name === 'AbortError' || error?.code === 'ECONNRESET'; +} + function doFetch(fetch_func, h1_fetch_func, options) { const fetch_options = Object.assign({}, options); @@ -73,7 +77,7 @@ function doFetch(fetch_func, h1_fetch_func, options) { // Catch async stream errors (e.g. brotli/gzip decode on truncated responses) to avoid unhandled 'error' crashes. stream.on('error', (err) => { clearTimeout(timeoutTimerId); - if (err?.name === 'AbortError') return; + if (skipLoggingFetchError(err)) return; log(' -- doFetch stream error', uri, err?.code, err?.name, err?.message); }); abortController.onResponse(stream); @@ -167,7 +171,7 @@ export function fetchData(options) { // Catch async stream errors (e.g. brotli/gzip decode on truncated responses) to avoid unhandled 'error' crashes. stream.on('error', (err) => { clearTimeout(timeoutTimerId); - if (err?.name === 'AbortError') return; + if (skipLoggingFetchError(err)) return; log(' -- fetchData stream error', uri, err?.code, err?.name, err?.message); }); abortController.onResponse(stream); diff --git a/lib/plugins/system/htmlparser/htmlparser.js b/lib/plugins/system/htmlparser/htmlparser.js index 00e765ca7..ffee38009 100644 --- a/lib/plugins/system/htmlparser/htmlparser.js +++ b/lib/plugins/system/htmlparser/htmlparser.js @@ -4,7 +4,7 @@ import { cache } from '../../../cache.js'; import * as utils from '../../../utils.js'; import * as libUtils from '../../../utils.js'; import * as metaUtils from '../meta/utils.js'; -import { extendCookiesJar } from '../../../fetch.js'; +import { extendCookiesJar, skipLoggingFetchError } from '../../../fetch.js'; var getUrlFunctional = utils.getUrlFunctional; import { CollectingHandlerForMutliTarget } from './CollectingHandlerForMutliTarget.js'; import { HtmlHandler } from './HtmlHandler.js'; @@ -225,7 +225,7 @@ export default { resp.on('end', parser.end.bind(parser)); // Handle stream error. resp.on('error', function(err) { - if (err?.name !== 'AbortError' && options.handleRuntimeError) { + if (!skipLoggingFetchError(err) && options.handleRuntimeError) { options.handleRuntimeError({ url: url, code: err?.code, From 29e9e527eda9c65ad11c9b68b187260753fe1ac1 Mon Sep 17 00:00:00 2001 From: Nazar Leush Date: Thu, 25 Jun 2026 18:23:17 +0300 Subject: [PATCH 2/6] request: cleanup options.allowCache --- lib/request.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/request.js b/lib/request.js index 536c6c598..2a13ee4fd 100644 --- a/lib/request.js +++ b/lib/request.js @@ -62,6 +62,7 @@ export default function(options, iframely_options, callback) { } delete options.prepareResult; + delete options.allowCache; delete options.useCacheOnly; delete options.cache_key; delete options.new_cache_key; From 2d0b966bee4c75268d7d5c423ff40ad2a9ff7ab5 Mon Sep 17 00:00:00 2001 From: Nazar Leush Date: Thu, 25 Jun 2026 18:31:06 +0300 Subject: [PATCH 3/6] fetch: safe processing HEAD requests --- lib/fetch.js | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/lib/fetch.js b/lib/fetch.js index 32f3c8c9e..101c08393 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -45,12 +45,18 @@ export function skipLoggingFetchError(error) { function doFetch(fetch_func, h1_fetch_func, options) { const fetch_options = Object.assign({}, options); + const is_head_request = fetch_options.method === 'HEAD'; // Implement `qs` (get params). var uri = options.qs ? createUrl(options.uri, options.qs) : options.uri; // Remove hash part of url. uri = uri.replace(/#.*/gi, ''); + // Prevent decode body for head request. + if (is_head_request) { + fetch_options.decode = false; + } + const abortController = new HostAbortController(uri); // Allow request abort before finish. @@ -95,6 +101,10 @@ function doFetch(fetch_func, h1_fetch_func, options) { stream.headers = headers; stream.abortController = abortController; stream.h2 = response.httpVersion === '2.0'; + // HEAD body is empty and no caller reads it; drain so 'end' fires and the socket is released. + if (is_head_request) { + stream.resume(); + } resolve(stream); } }) @@ -147,9 +157,17 @@ export function fetchData(options) { var json = options.json; delete options.json; var res; + const fetch_options = Object.assign({}, options); + const is_head_request = fetch_options.method === 'HEAD'; + const uri = options.qs ? createUrl(options.uri, options.qs) : options.uri; + // Prevent decode body for head request. + if (is_head_request) { + fetch_options.decode = false; + } + const abortController = new HostAbortController(uri); // Allow request abort before finish. @@ -164,7 +182,6 @@ export function fetchData(options) { a_fetch_func(uri, fetch_options) .then(response => { var stream = response.body; - // TODO: looks like HEAD request has no END event. stream.on('end', () => { clearTimeout(timeoutTimerId); }); @@ -176,14 +193,23 @@ export function fetchData(options) { }); abortController.onResponse(stream); res = response; - if (json !== false) { - // If `json` not forbidden, read `content-type`. - json = json || (response.headers.get('content-type').indexOf('application/json') > -1); - } - if (json) { - return response.json(); + + if (is_head_request) { + // Empty data for HEAD request — drain body so 'end' fires and the socket is released. + stream.resume(); + return Promise.resolve(''); } else { - return response.text(); + + if (json !== false) { + // If `json` not forbidden, read `content-type`. + json = json || (response.headers.get('content-type').indexOf('application/json') > -1); + } + + if (json) { + return response.json(); + } else { + return response.text(); + } } }) .then(data => { From 138011bb2be40d91908591298a1a6e70ed6e54a5 Mon Sep 17 00:00:00 2001 From: Nazar Leush Date: Thu, 25 Jun 2026 18:36:24 +0300 Subject: [PATCH 4/6] skip log for `ERR_STREAM_PREMATURE_CLOSE` error --- lib/fetch.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/fetch.js b/lib/fetch.js index 101c08393..fe08ca069 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -39,7 +39,9 @@ const fetchH1 = h1NoCache({ }).fetch; export function skipLoggingFetchError(error) { - return error?.name === 'AbortError' || error?.code === 'ECONNRESET'; + return error?.name === 'AbortError' + || error?.code === 'ECONNRESET' + || error?.code === 'ERR_STREAM_PREMATURE_CLOSE'; } function doFetch(fetch_func, h1_fetch_func, options) { From 87135940893ef4c305db0e08f1adae24a8da4829 Mon Sep 17 00:00:00 2001 From: Nazar Leush Date: Thu, 25 Jun 2026 19:01:28 +0300 Subject: [PATCH 5/6] do not log ERR_HTTP2_STREAM_ERROR errors --- lib/fetch.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/fetch.js b/lib/fetch.js index fe08ca069..1b6c1e56d 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -41,7 +41,8 @@ const fetchH1 = h1NoCache({ export function skipLoggingFetchError(error) { return error?.name === 'AbortError' || error?.code === 'ECONNRESET' - || error?.code === 'ERR_STREAM_PREMATURE_CLOSE'; + || error?.code === 'ERR_STREAM_PREMATURE_CLOSE' + || error?.code === 'ERR_HTTP2_STREAM_ERROR'; } function doFetch(fetch_func, h1_fetch_func, options) { From 01dc0f03588250bf90d3c022236eebc4986eb0ac Mon Sep 17 00:00:00 2001 From: Nazar Leush Date: Thu, 25 Jun 2026 19:44:57 +0300 Subject: [PATCH 6/6] do not log ERR_HTTP2_SESSION_ERROR errors --- lib/fetch.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/fetch.js b/lib/fetch.js index 1b6c1e56d..8aadf114d 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -42,7 +42,8 @@ export function skipLoggingFetchError(error) { return error?.name === 'AbortError' || error?.code === 'ECONNRESET' || error?.code === 'ERR_STREAM_PREMATURE_CLOSE' - || error?.code === 'ERR_HTTP2_STREAM_ERROR'; + || error?.code === 'ERR_HTTP2_STREAM_ERROR' + || error?.code === 'ERR_HTTP2_SESSION_ERROR'; } function doFetch(fetch_func, h1_fetch_func, options) {