Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 44 additions & 10 deletions lib/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,29 @@ const fetchH1 = h1NoCache({
rejectUnauthorized: false // By default skip auth check for all.
}).fetch;

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_SESSION_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.
Expand All @@ -73,7 +87,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);
Expand All @@ -91,6 +105,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);
}
})
Expand Down Expand Up @@ -143,9 +161,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.
Expand All @@ -160,26 +186,34 @@ 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);
});
// 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);
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 => {
Expand Down
4 changes: 2 additions & 2 deletions lib/plugins/system/htmlparser/htmlparser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading