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