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 | # 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. |
| 43 | heartbeatInterval: 0 | 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 | # -- SSL options -- | 47 | # -- SSL options -- |
| 45 | ssl: | 48 | ssl: |
| 46 | # Enable SSL/TLS encryption | 49 | # Enable SSL/TLS encryption |
| @@ -31,6 +31,7 @@ import { initConfig } from './config-init.js'; | |||
| 31 | * @property {string} keyPassphrase SSL private key passphrase | 31 | * @property {string} keyPassphrase SSL private key passphrase |
| 32 | * @property {boolean} whitelistMode If enable whitelist mode | 32 | * @property {boolean} whitelistMode If enable whitelist mode |
| 33 | * @property {boolean} basicAuthMode If enable basic authentication | 33 | * @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 proxy | 35 | * @property {boolean} requestProxyEnabled If enable outgoing request proxy |
| 35 | * @property {string} requestProxyUrl Request proxy URL | 36 | * @property {string} requestProxyUrl Request proxy URL |
| 36 | * @property {string[]} requestProxyBypass Request proxy bypass list | 37 | * @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), |
| @@ -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 | */ |
| 17 | export default function initRequestProxy({ enabled, url, bypass }) { | 18 | export default function initRequestProxy({ enabled, url, bypass, enableKeepAlive }) { |
| 18 | try { | 19 | try { |
| 19 | // No proxy is enabled, so return | 20 | // 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 | } |
| 41 | 42 | ||
| 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; |
| 45 | 47 | ||
| @@ -5,6 +5,8 @@ import util from 'node:util'; | |||
| 5 | import net from 'node:net'; | 5 | import net from 'node:net'; |
| 6 | import dns from 'node:dns'; | 6 | import dns from 'node:dns'; |
| 7 | import process from 'node:process'; | 7 | import process from 'node:process'; |
| 8 | import http from 'node:http'; | ||
| 9 | import https from 'node:https'; | ||
| 8 | 10 | ||
| 9 | import cors from 'cors'; | 11 | import cors from 'cors'; |
| 10 | import { csrfSync } from 'csrf-sync'; | 12 | import { csrfSync } from 'csrf-sync'; |
| @@ -93,6 +95,10 @@ if (!cliArgs.enableIPv6 && !cliArgs.enableIPv4) { | |||
| 93 | process.exit(1); | 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 | const app = express(); | 102 | const app = express(); |
| 97 | app.use(helmet({ | 103 | app.use(helmet({ |
| 98 | contentSecurityPolicy: false, | 104 | contentSecurityPolicy: false, |
| @@ -328,7 +334,7 @@ async function preSetupTasks() { | |||
| 328 | }); | 334 | }); |
| 329 | 335 | ||
| 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 }); |
| 332 | 338 | ||
| 333 | // Wait for frontend libs to compile | 339 | // Wait for frontend libs to compile |
| 334 | await webpackMiddleware.runWebpackCompiler({ pruneCache: true }); | 340 | await webpackMiddleware.runWebpackCompiler({ pruneCache: true }); |