fix: disable HTTP keepAlive (Node 18 behavior) with a config toggle (#5519) * implement disable keepalive, handle request-proxy and config logic * Invert keep-alive boolean setting * fix: clean-up server.js diff * fix: boolean flag type * feat: disable keep-alive by default --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

b1ef254f78b69bdf20d64421cc77d7ff68fff643

DeathStalker471 <thomasnealson85@gmail.com>

Signed
4 files changed, +22 -3Showing whitespace changes
default/config.yaml+3 -0
@@ -41,6 +41,9 @@ port: 8000
41# Interval in seconds to write a heartbeat file. Set to 0 to disable.41# Interval in seconds to write a heartbeat file. Set to 0 to disable.
42# This is used primarily for Docker healthchecks.42# This is used primarily for Docker healthchecks.
43heartbeatInterval: 043heartbeatInterval: 0
44# Enable HTTP/HTTPS keep-alive globally.
45# Disabling restores old Node 18 behavior, can help if ECONNRESET and other network errors occur.
46enableKeepAlive: false
44# -- SSL options --47# -- SSL options --
45ssl:48ssl:
46 # Enable SSL/TLS encryption49 # Enable SSL/TLS encryption
src/command-line.js+8 -0
@@ -31,6 +31,7 @@ import { initConfig } from './config-init.js';
31 * @property {string} keyPassphrase SSL private key passphrase31 * @property {string} keyPassphrase SSL private key passphrase
32 * @property {boolean} whitelistMode If enable whitelist mode32 * @property {boolean} whitelistMode If enable whitelist mode
33 * @property {boolean} basicAuthMode If enable basic authentication33 * @property {boolean} basicAuthMode If enable basic authentication
34 * @property {boolean} enableKeepAlive Enable HTTP/HTTPS keep-alive globally
34 * @property {boolean} requestProxyEnabled If enable outgoing request proxy35 * @property {boolean} requestProxyEnabled If enable outgoing request proxy
35 * @property {string} requestProxyUrl Request proxy URL36 * @property {string} requestProxyUrl Request proxy URL
36 * @property {string[]} requestProxyBypass Request proxy bypass list37 * @property {string[]} requestProxyBypass Request proxy bypass list
@@ -76,6 +77,7 @@ export class CommandLineParser {
76 keyPassphrase: '',77 keyPassphrase: '',
77 whitelistMode: true,78 whitelistMode: true,
78 basicAuthMode: false,79 basicAuthMode: false,
80 enableKeepAlive: false,
79 requestProxyEnabled: false,81 requestProxyEnabled: false,
80 requestProxyUrl: '',82 requestProxyUrl: '',
81 requestProxyBypass: [],83 requestProxyBypass: [],
@@ -217,6 +219,11 @@ export class CommandLineParser {
217 default: null,219 default: null,
218 describe: 'Enables basic authentication',220 describe: 'Enables basic authentication',
219 })221 })
222 .option('enableKeepAlive', {
223 type: 'boolean',
224 default: null,
225 describe: 'Enable HTTP/HTTPS keep-alive globally',
226 })
220 .option('requestProxyEnabled', {227 .option('requestProxyEnabled', {
221 type: 'boolean',228 type: 'boolean',
222 default: null,229 default: null,
@@ -313,6 +320,7 @@ export class CommandLineParser {
313 keyPassphrase: cliArguments.keyPassphrase ?? getConfigValue('ssl.keyPassphrase', defaultConfig.keyPassphrase),320 keyPassphrase: cliArguments.keyPassphrase ?? getConfigValue('ssl.keyPassphrase', defaultConfig.keyPassphrase),
314 whitelistMode: cliArguments.whitelist ?? getConfigValue('whitelistMode', defaultConfig.whitelistMode, 'boolean'),321 whitelistMode: cliArguments.whitelist ?? getConfigValue('whitelistMode', defaultConfig.whitelistMode, 'boolean'),
315 basicAuthMode: cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', defaultConfig.basicAuthMode, 'boolean'),322 basicAuthMode: cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', defaultConfig.basicAuthMode, 'boolean'),
323 enableKeepAlive: cliArguments.enableKeepAlive ?? getConfigValue('enableKeepAlive', defaultConfig.enableKeepAlive, 'boolean'),
316 requestProxyEnabled: cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', defaultConfig.requestProxyEnabled, 'boolean'),324 requestProxyEnabled: cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', defaultConfig.requestProxyEnabled, 'boolean'),
317 requestProxyUrl: cliArguments.requestProxyUrl ?? getConfigValue('requestProxy.url', defaultConfig.requestProxyUrl),325 requestProxyUrl: cliArguments.requestProxyUrl ?? getConfigValue('requestProxy.url', defaultConfig.requestProxyUrl),
318 requestProxyBypass: cliArguments.requestProxyBypass ?? getConfigValue('requestProxy.bypass', defaultConfig.requestProxyBypass),326 requestProxyBypass: cliArguments.requestProxyBypass ?? getConfigValue('requestProxy.bypass', defaultConfig.requestProxyBypass),
src/request-proxy.js+4 -2
@@ -13,8 +13,9 @@ const LOG_HEADER = '[Request Proxy]';
13 * @property {boolean} enabled Whether proxy is enabled.13 * @property {boolean} enabled Whether proxy is enabled.
14 * @property {string} url Proxy URL.14 * @property {string} url Proxy URL.
15 * @property {string[]} bypass List of URLs to bypass proxy.15 * @property {string[]} bypass List of URLs to bypass proxy.
16 * @property {boolean} enableKeepAlive Enable HTTP/HTTPS keep-alive.
16 */17 */
17export default function initRequestProxy({ enabled, url, bypass }) {18export default function initRequestProxy({ enabled, url, bypass, enableKeepAlive }) {
18 try {19 try {
19 // No proxy is enabled, so return20 // No proxy is enabled, so return
20 if (!enabled) {21 if (!enabled) {
@@ -39,7 +40,8 @@ export default function initRequestProxy({ enabled, url, bypass }) {
39 process.env.no_proxy = bypass.join(',');40 process.env.no_proxy = bypass.join(',');
40 }41 }
4142
42 const proxyAgent = new ProxyAgent();43 const proxyAgentOptions = enableKeepAlive ? { keepAlive: true } : { keepAlive: false };
44 const proxyAgent = new ProxyAgent(proxyAgentOptions);
43 http.globalAgent = proxyAgent;45 http.globalAgent = proxyAgent;
44 https.globalAgent = proxyAgent;46 https.globalAgent = proxyAgent;
4547
src/server-main.js+7 -1
@@ -5,6 +5,8 @@ import util from 'node:util';
5import net from 'node:net';5import net from 'node:net';
6import dns from 'node:dns';6import dns from 'node:dns';
7import process from 'node:process';7import process from 'node:process';
8import http from 'node:http';
9import https from 'node:https';
810
9import cors from 'cors';11import cors from 'cors';
10import { csrfSync } from 'csrf-sync';12import { csrfSync } from 'csrf-sync';
@@ -93,6 +95,10 @@ if (!cliArgs.enableIPv6 && !cliArgs.enableIPv4) {
93 process.exit(1);95 process.exit(1);
94}96}
9597
98// Set keep-alive preference for all HTTP/HTTPS requests.
99http.globalAgent = new http.Agent({ keepAlive: cliArgs.enableKeepAlive });
100https.globalAgent = new https.Agent({ keepAlive: cliArgs.enableKeepAlive });
101
96const app = express();102const app = express();
97app.use(helmet({103app.use(helmet({
98 contentSecurityPolicy: false,104 contentSecurityPolicy: false,
@@ -328,7 +334,7 @@ async function preSetupTasks() {
328 });334 });
329335
330 // Add request proxy.336 // Add request proxy.
331 initRequestProxy({ enabled: cliArgs.requestProxyEnabled, url: cliArgs.requestProxyUrl, bypass: cliArgs.requestProxyBypass });337 initRequestProxy({ enabled: cliArgs.requestProxyEnabled, url: cliArgs.requestProxyUrl, bypass: cliArgs.requestProxyBypass, enableKeepAlive: cliArgs.enableKeepAlive });
332338
333 // Wait for frontend libs to compile339 // Wait for frontend libs to compile
334 await webpackMiddleware.runWebpackCompiler({ pruneCache: true });340 await webpackMiddleware.runWebpackCompiler({ pruneCache: true });