maven-proxy 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/bin/maven-proxy.js +158 -3
- package/package.json +2 -1
- package/src/cache/maven-affinity-index.js +6 -3
- package/src/config/config.js +2 -1
- package/src/index.js +89 -35
- package/src/proxy/proxy-connect-handler.js +15 -5
- package/src/proxy/proxy-http-handler.js +17 -6
package/README.md
CHANGED
|
@@ -267,7 +267,8 @@ Environment variables:
|
|
|
267
267
|
- HTTPS_MITM_DOMAINS: MITM domain list (includes registry.npmjs.org by default, wildcards supported).
|
|
268
268
|
- DOWNLOAD_LOG_DIR: log directory.
|
|
269
269
|
- LOG_RETENTION_DAYS: number of days to retain logs.
|
|
270
|
-
- LOG_TO_STDOUT: whether to also print logs to stdout/stderr.
|
|
270
|
+
- LOG_TO_STDOUT: whether to also print runtime logs to stdout/stderr; startup logs are always printed.
|
|
271
|
+
- LOG_CONNECT_EVENTS: whether to print verbose CONNECT/MITM handshake logs. Default false.
|
|
271
272
|
- OUTBOUND_KEEP_ALIVE: enable outbound keep-alive connection pooling.
|
|
272
273
|
- OUTBOUND_KEEP_ALIVE_SECONDS: keep-alive interval in seconds.
|
|
273
274
|
- OUTBOUND_MAX_SOCKETS: max outbound sockets per origin.
|
|
@@ -294,7 +295,7 @@ Priority:
|
|
|
294
295
|
- `REPO_FALLBACK_REPOS`: Comma-separated fallback repository URLs for cache misses.
|
|
295
296
|
- `ENABLE_HTTPS_PROXY`: Enable HTTPS proxy handling (`true/false`).
|
|
296
297
|
- `HTTPS_MITM_DOMAINS`: Comma-separated domains to apply MITM certificate issuance (wildcards supported).
|
|
297
|
-
- `HTTPS_PASSTHROUGH_FOR_UNMATCHED`: Whether unmatched HTTPS domains are tunneled directly.
|
|
298
|
+
- `HTTPS_PASSTHROUGH_FOR_UNMATCHED`: Whether unmatched HTTPS domains are tunneled directly. Default `false`.
|
|
298
299
|
- `NPM_REGISTRY_DOMAINS`: Domains treated as npm ecosystem for cache routing (wildcards supported).
|
|
299
300
|
- `MAVEN_REPO_DOMAINS`: Domains treated as Maven ecosystem for cache routing (wildcards supported).
|
|
300
301
|
- `MULTI_THREAD_DOMAINS`: Domains allowed to use multi-thread download (wildcards supported).
|
|
@@ -303,7 +304,8 @@ Priority:
|
|
|
303
304
|
- `DOWNLOAD_TIMEOUT_SECONDS`: Upstream request timeout in seconds.
|
|
304
305
|
- `DOWNLOAD_LOG_DIR`: Directory for unified app/error logs.
|
|
305
306
|
- `LOG_RETENTION_DAYS`: Number of days to keep log files.
|
|
306
|
-
- `LOG_TO_STDOUT`: Whether to also print logs to stdout/stderr. Default `true`.
|
|
307
|
+
- `LOG_TO_STDOUT`: Whether to also print runtime logs to stdout/stderr. Startup logs are always printed. Default `true`.
|
|
308
|
+
- `LOG_CONNECT_EVENTS`: Whether to print verbose CONNECT/MITM handshake logs. Default `false`.
|
|
307
309
|
- `OUTBOUND_KEEP_ALIVE`: Enable outbound keep-alive connection pooling. Default `true`.
|
|
308
310
|
- `OUTBOUND_KEEP_ALIVE_SECONDS`: Keep-alive interval in seconds. Default `1`.
|
|
309
311
|
- `OUTBOUND_MAX_SOCKETS`: Max outbound sockets per origin. Default `64`.
|
|
@@ -404,12 +406,17 @@ Common commands:
|
|
|
404
406
|
- maven-proxy --config /path/to/config
|
|
405
407
|
- maven-proxy start --mode development
|
|
406
408
|
- maven-proxy start --mode user
|
|
409
|
+
- maven-proxy stop
|
|
407
410
|
- maven-proxy init-config --force
|
|
408
411
|
- maven-proxy truststore print
|
|
409
412
|
- maven-proxy truststore init
|
|
410
413
|
- maven-proxy truststore merge --source /path/source.jks --target /path/target.jks
|
|
411
414
|
- maven-proxy doctor
|
|
412
415
|
|
|
416
|
+
Start/stop behavior:
|
|
417
|
+
- `maven-proxy start` runs in background and returns immediately.
|
|
418
|
+
- `maven-proxy stop` stops the background process using PID file `~/maven-proxy/maven-proxy.pid`.
|
|
419
|
+
|
|
413
420
|
Doctor command:
|
|
414
421
|
- Checks config loading, port availability, keytool, JAVA_HOME, cert/truststore paths, and writable log/cache directories.
|
|
415
422
|
- Reports PASS/WARN/FAIL.
|
package/bin/maven-proxy.js
CHANGED
|
@@ -3,10 +3,14 @@ import fs from "node:fs";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import net from "node:net";
|
|
6
|
-
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
8
9
|
const defaultConfigDir = path.resolve(os.homedir(), "maven-proxy");
|
|
9
10
|
const defaultConfigFile = path.join(defaultConfigDir, "config.properties");
|
|
11
|
+
const daemonPidFile = path.join(defaultConfigDir, "maven-proxy.pid");
|
|
12
|
+
const internalRunCommand = "__run-server";
|
|
13
|
+
const cliFilePath = fileURLToPath(import.meta.url);
|
|
10
14
|
|
|
11
15
|
function normalizeMode(value) {
|
|
12
16
|
const normalized = String(value || "").trim().toLowerCase();
|
|
@@ -54,6 +58,7 @@ function printHelp() {
|
|
|
54
58
|
console.log("Usage:");
|
|
55
59
|
console.log(" maven-proxy");
|
|
56
60
|
console.log(" maven-proxy start [--mode <development|user>] [--config <file>]");
|
|
61
|
+
console.log(" maven-proxy stop");
|
|
57
62
|
console.log(" maven-proxy init-config [--force] [--config <file>]");
|
|
58
63
|
console.log(" maven-proxy truststore <print|init|merge> [options]");
|
|
59
64
|
console.log(" maven-proxy doctor [--mode <development|user>] [--config <file>]");
|
|
@@ -62,6 +67,7 @@ function printHelp() {
|
|
|
62
67
|
console.log(" npx maven-proxy");
|
|
63
68
|
console.log(" maven-proxy init-config");
|
|
64
69
|
console.log(" maven-proxy start --mode development");
|
|
70
|
+
console.log(" maven-proxy stop");
|
|
65
71
|
console.log(" maven-proxy --config ~/maven-proxy/config.properties");
|
|
66
72
|
console.log(" maven-proxy truststore print");
|
|
67
73
|
console.log(" maven-proxy truststore merge --source ./a.jks --target ./b.jks");
|
|
@@ -144,7 +150,7 @@ function getDefaultConfigTemplate() {
|
|
|
144
150
|
"REPO_FALLBACK_REPOS=https://repo1.maven.org/maven2,https://jitpack.io,https://plugins.gradle.org/m2,https://maven.google.com",
|
|
145
151
|
"ENABLE_HTTPS_PROXY=true",
|
|
146
152
|
"HTTPS_MITM_DOMAINS=repo1.maven.org,repo.maven.apache.org,registry.npmjs.org",
|
|
147
|
-
"HTTPS_PASSTHROUGH_FOR_UNMATCHED=
|
|
153
|
+
"HTTPS_PASSTHROUGH_FOR_UNMATCHED=false",
|
|
148
154
|
"NPM_REGISTRY_DOMAINS=registry.npmjs.org,registry.npmmirror.com,npm.pkg.github.com",
|
|
149
155
|
"MAVEN_REPO_DOMAINS=repo1.maven.org,repo.maven.apache.org,jitpack.io,plugins.gradle.org,maven.google.com",
|
|
150
156
|
"MULTI_THREAD_DOMAINS=repo1.maven.org",
|
|
@@ -217,12 +223,145 @@ async function ensureAutoConfigIfNeeded(options, command) {
|
|
|
217
223
|
await initConfigFile(defaultConfigFile, false);
|
|
218
224
|
}
|
|
219
225
|
|
|
220
|
-
async function
|
|
226
|
+
async function runServerInCurrentProcess(options) {
|
|
221
227
|
applyConfigOverrides(options);
|
|
222
228
|
|
|
223
229
|
await import("../src/index.js");
|
|
224
230
|
}
|
|
225
231
|
|
|
232
|
+
function parsePid(rawText) {
|
|
233
|
+
const pid = Number.parseInt(String(rawText || "").trim(), 10);
|
|
234
|
+
return Number.isFinite(pid) && pid > 0 ? pid : 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function readDaemonPid() {
|
|
238
|
+
if (!fs.existsSync(daemonPidFile)) {
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const text = fs.readFileSync(daemonPidFile, "utf8");
|
|
244
|
+
return parsePid(text);
|
|
245
|
+
} catch {
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function isProcessRunning(pid) {
|
|
251
|
+
if (!pid) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
process.kill(pid, 0);
|
|
257
|
+
return true;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (error && error.code === "EPERM") {
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function removeDaemonPidFile() {
|
|
267
|
+
await fs.promises.rm(daemonPidFile, { force: true });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function waitMs(ms) {
|
|
271
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function waitForProcessExit(pid, timeoutMs = 5000) {
|
|
275
|
+
const startedAt = Date.now();
|
|
276
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
277
|
+
if (!isProcessRunning(pid)) {
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
await waitMs(100);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return !isProcessRunning(pid);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function startServer(options) {
|
|
287
|
+
await fs.promises.mkdir(defaultConfigDir, { recursive: true });
|
|
288
|
+
|
|
289
|
+
const existingPid = readDaemonPid();
|
|
290
|
+
if (existingPid && isProcessRunning(existingPid)) {
|
|
291
|
+
console.log(`[maven-proxy] already running (pid=${existingPid})`);
|
|
292
|
+
console.log(`[maven-proxy] pid file: ${daemonPidFile}`);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (existingPid && !isProcessRunning(existingPid)) {
|
|
297
|
+
await removeDaemonPidFile();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const childEnv = {
|
|
301
|
+
...process.env,
|
|
302
|
+
MAVEN_PROXY_CONFIG_MODE: resolveEffectiveMode(options),
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
if (options.configPath) {
|
|
306
|
+
childEnv.MAVEN_PROXY_CONFIG_FILE = resolvePath(options.configPath);
|
|
307
|
+
} else {
|
|
308
|
+
delete childEnv.MAVEN_PROXY_CONFIG_FILE;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const child = spawn(
|
|
312
|
+
process.execPath,
|
|
313
|
+
[cliFilePath, internalRunCommand, "--mode", resolveEffectiveMode(options)],
|
|
314
|
+
{
|
|
315
|
+
cwd: process.cwd(),
|
|
316
|
+
detached: true,
|
|
317
|
+
stdio: "ignore",
|
|
318
|
+
env: childEnv,
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
child.unref();
|
|
323
|
+
await fs.promises.writeFile(daemonPidFile, `${child.pid}\n`, "utf8");
|
|
324
|
+
|
|
325
|
+
await waitMs(300);
|
|
326
|
+
if (!isProcessRunning(child.pid)) {
|
|
327
|
+
await removeDaemonPidFile();
|
|
328
|
+
throw new Error("start failed: process exited immediately, check app/error logs");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log(`[maven-proxy] started in background (pid=${child.pid})`);
|
|
332
|
+
console.log(`[maven-proxy] pid file: ${daemonPidFile}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function stopServer(options) {
|
|
336
|
+
const pid = readDaemonPid();
|
|
337
|
+
if (!pid) {
|
|
338
|
+
console.log("[maven-proxy] not running (pid file not found)");
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!isProcessRunning(pid)) {
|
|
343
|
+
await removeDaemonPidFile();
|
|
344
|
+
console.log(`[maven-proxy] stale pid removed: ${pid}`);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
process.kill(pid, "SIGTERM");
|
|
349
|
+
const stopped = await waitForProcessExit(pid, 5000);
|
|
350
|
+
if (!stopped) {
|
|
351
|
+
process.kill(pid, "SIGKILL");
|
|
352
|
+
const forceStopped = await waitForProcessExit(pid, 2000);
|
|
353
|
+
if (!forceStopped) {
|
|
354
|
+
throw new Error(`stop failed: unable to terminate pid ${pid}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
await removeDaemonPidFile();
|
|
359
|
+
console.log(`[maven-proxy] stopped (pid=${pid})`);
|
|
360
|
+
if (options.configPath) {
|
|
361
|
+
console.log(`[maven-proxy] stop requested with config: ${resolvePath(options.configPath)}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
226
365
|
function applyConfigOverrides(options) {
|
|
227
366
|
process.env.MAVEN_PROXY_CONFIG_MODE = resolveEffectiveMode(options);
|
|
228
367
|
|
|
@@ -577,6 +716,14 @@ async function main() {
|
|
|
577
716
|
return;
|
|
578
717
|
}
|
|
579
718
|
|
|
719
|
+
if (command === "stop") {
|
|
720
|
+
if (options.commandArgs.length > 0) {
|
|
721
|
+
throw new Error(`Unknown argument for stop: ${options.commandArgs[0]}`);
|
|
722
|
+
}
|
|
723
|
+
await stopServer(options);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
|
|
580
727
|
if (command === "start") {
|
|
581
728
|
if (options.commandArgs.length > 0) {
|
|
582
729
|
throw new Error(`Unknown argument for start: ${options.commandArgs[0]}`);
|
|
@@ -585,6 +732,14 @@ async function main() {
|
|
|
585
732
|
return;
|
|
586
733
|
}
|
|
587
734
|
|
|
735
|
+
if (command === internalRunCommand) {
|
|
736
|
+
if (options.commandArgs.length > 0) {
|
|
737
|
+
throw new Error(`Unknown argument for ${internalRunCommand}: ${options.commandArgs[0]}`);
|
|
738
|
+
}
|
|
739
|
+
await runServerInCurrentProcess(options);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
|
|
588
743
|
throw new Error(`Unknown command: ${command}`);
|
|
589
744
|
}
|
|
590
745
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maven-proxy",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Maven proxy with cache, HTTPS MITM for selected domains, and local repo publishing",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"start:cli": "node bin/maven-proxy.js",
|
|
18
18
|
"cli:help": "node bin/maven-proxy.js --help",
|
|
19
19
|
"cli:start": "node bin/maven-proxy.js start --mode development",
|
|
20
|
+
"cli:stop": "node bin/maven-proxy.js stop",
|
|
20
21
|
"cli:doctor": "node bin/maven-proxy.js doctor --mode development",
|
|
21
22
|
"cli:truststore:print": "node bin/maven-proxy.js truststore print --mode development",
|
|
22
23
|
"cli:truststore:init": "node bin/maven-proxy.js truststore init --mode development",
|
|
@@ -75,6 +75,8 @@ export class MavenAffinityIndex {
|
|
|
75
75
|
this.snapshotPath = path.join(this.indexDir, "maven-affinity.snapshot.json");
|
|
76
76
|
this.eventLogPath = path.join(this.indexDir, "maven-affinity.events.log");
|
|
77
77
|
|
|
78
|
+
// Positive entries are persistent and have no TTL. They are removed only
|
|
79
|
+
// when the cache file disappears or a conflict is detected.
|
|
78
80
|
this.positive = new Map();
|
|
79
81
|
this.negative = new Map();
|
|
80
82
|
this.conflicts = new Map();
|
|
@@ -269,7 +271,7 @@ export class MavenAffinityIndex {
|
|
|
269
271
|
return true;
|
|
270
272
|
}
|
|
271
273
|
|
|
272
|
-
recordSuccess({ canonicalKey, host, cachePath, fileName }) {
|
|
274
|
+
recordSuccess({ canonicalKey, host, cachePath, fileName, urlObj = null }) {
|
|
273
275
|
if (!this.enabled || !canonicalKey || !cachePath || !fileName) {
|
|
274
276
|
return;
|
|
275
277
|
}
|
|
@@ -309,8 +311,9 @@ export class MavenAffinityIndex {
|
|
|
309
311
|
},
|
|
310
312
|
});
|
|
311
313
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
+
const successScope = buildNegativeScope(urlObj);
|
|
315
|
+
if (successScope) {
|
|
316
|
+
const negativeKey = buildNegativeKey(successScope, canonicalKey);
|
|
314
317
|
if (this.negative.has(negativeKey)) {
|
|
315
318
|
this.#applyEvent({
|
|
316
319
|
type: "negative_remove",
|
package/src/config/config.js
CHANGED
|
@@ -218,6 +218,7 @@ export const config = {
|
|
|
218
218
|
downloadLogDir: path.resolve(configBaseDir, process.env.DOWNLOAD_LOG_DIR || "data/logs/downloads"),
|
|
219
219
|
logRetentionDays: Math.max(1, toInt(process.env.LOG_RETENTION_DAYS, 7)),
|
|
220
220
|
logToStdout: toBool(process.env.LOG_TO_STDOUT, true),
|
|
221
|
+
logConnectEvents: toBool(process.env.LOG_CONNECT_EVENTS, false),
|
|
221
222
|
certDir: path.resolve(configBaseDir, process.env.CERT_DIR || "data/certs"),
|
|
222
223
|
rootCertPath: path.resolve(configBaseDir, process.env.ROOT_CERT_PATH || "data/certs/root-ca.crt"),
|
|
223
224
|
rootKeyPath: path.resolve(configBaseDir, process.env.ROOT_KEY_PATH || "data/certs/root-ca.key.pem"),
|
|
@@ -230,7 +231,7 @@ export const config = {
|
|
|
230
231
|
javaHome: javaHomeResolution.javaHome,
|
|
231
232
|
javaHomeSource: javaHomeResolution.source,
|
|
232
233
|
javaHomeConfigured: javaHomeResolution.configuredJavaHome || "",
|
|
233
|
-
httpsPassthroughForUnmatched: toBool(process.env.HTTPS_PASSTHROUGH_FOR_UNMATCHED,
|
|
234
|
+
httpsPassthroughForUnmatched: toBool(process.env.HTTPS_PASSTHROUGH_FOR_UNMATCHED, false),
|
|
234
235
|
upstreamProxyUrl: normalizeProxyUrl(process.env.UPSTREAM_PROXY_URL || process.env.ALL_PROXY || process.env.all_proxy || ""),
|
|
235
236
|
upstreamHttpProxyUrl: normalizeProxyUrl(process.env.UPSTREAM_HTTP_PROXY_URL || process.env.HTTP_PROXY || process.env.http_proxy || ""),
|
|
236
237
|
upstreamHttpsProxyUrl: normalizeProxyUrl(process.env.UPSTREAM_HTTPS_PROXY_URL || process.env.HTTPS_PROXY || process.env.https_proxy || ""),
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,53 @@ installConsoleLogFileMirror({
|
|
|
17
17
|
});
|
|
18
18
|
installGlobalErrorLogging();
|
|
19
19
|
|
|
20
|
+
function startupInfo(message) {
|
|
21
|
+
if (!config.logToStdout) {
|
|
22
|
+
process.stdout.write(`${message}\n`);
|
|
23
|
+
}
|
|
24
|
+
console.log(message);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function startupError(message, error = null) {
|
|
28
|
+
if (!config.logToStdout) {
|
|
29
|
+
process.stderr.write(`${message}\n`);
|
|
30
|
+
if (error) {
|
|
31
|
+
process.stderr.write(`${error?.stack || error?.message || String(error)}\n`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (error) {
|
|
35
|
+
console.error(message, error);
|
|
36
|
+
} else {
|
|
37
|
+
console.error(message);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function waitForServerListening(server, name) {
|
|
42
|
+
if (server?.listening) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await new Promise((resolve, reject) => {
|
|
47
|
+
const onListening = () => {
|
|
48
|
+
cleanup();
|
|
49
|
+
resolve();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const onError = (error) => {
|
|
53
|
+
cleanup();
|
|
54
|
+
reject(new Error(`${name} listen failed: ${error.message}`));
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const cleanup = () => {
|
|
58
|
+
server.off("listening", onListening);
|
|
59
|
+
server.off("error", onError);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
server.once("listening", onListening);
|
|
63
|
+
server.once("error", onError);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
20
67
|
async function main() {
|
|
21
68
|
await fs.promises.mkdir(config.cacheDir, { recursive: true });
|
|
22
69
|
await fs.promises.mkdir(config.mavenCacheDir, { recursive: true });
|
|
@@ -51,46 +98,53 @@ async function main() {
|
|
|
51
98
|
);
|
|
52
99
|
const repoServer = startRepoServer(config, downloader);
|
|
53
100
|
|
|
101
|
+
await Promise.all([
|
|
102
|
+
waitForServerListening(proxyServer, "proxy server"),
|
|
103
|
+
waitForServerListening(repoServer, "repo server"),
|
|
104
|
+
]);
|
|
105
|
+
|
|
54
106
|
const trustCommands = getTrustStoreCommands(config);
|
|
55
107
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
108
|
+
startupInfo("[maven-proxy] started");
|
|
109
|
+
startupInfo(`[maven-proxy] config mode: ${config.configMode}`);
|
|
110
|
+
startupInfo(`[maven-proxy] config file: ${config.loadedConfigFile || "(none)"}`);
|
|
111
|
+
startupInfo(`[maven-proxy] config base: ${config.configBaseDir}`);
|
|
60
112
|
if (config.configMode === "user") {
|
|
61
|
-
|
|
113
|
+
startupInfo(`[maven-proxy] default user config: ${config.defaultUserConfigPath}`);
|
|
62
114
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
115
|
+
startupInfo(`[maven-proxy] proxy port: ${config.proxyPort}`);
|
|
116
|
+
startupInfo(`[maven-proxy] repo port: ${config.repoPort}`);
|
|
117
|
+
startupInfo(`[maven-proxy] cache dir : ${config.cacheDir}`);
|
|
118
|
+
startupInfo(`[maven-proxy] cache maven: ${config.mavenCacheDir}`);
|
|
119
|
+
startupInfo(`[maven-proxy] cache npm : ${config.npmCacheDir}`);
|
|
120
|
+
startupInfo(`[maven-proxy] cache other: ${config.genericCacheDir}`);
|
|
121
|
+
startupInfo(`[maven-proxy] log dir: ${config.downloadLogDir}`);
|
|
122
|
+
startupInfo(`[maven-proxy] log retention days: ${config.logRetentionDays}`);
|
|
123
|
+
startupInfo(`[maven-proxy] log to stdout: ${config.logToStdout}`);
|
|
124
|
+
startupInfo(`[maven-proxy] log connect events: ${config.logConnectEvents}`);
|
|
125
|
+
startupInfo(`[maven-proxy] outbound keep-alive: ${config.outboundKeepAlive}`);
|
|
126
|
+
startupInfo(`[maven-proxy] outbound keepAlive(seconds): ${config.outboundKeepAliveMsecs / 1000}`);
|
|
127
|
+
startupInfo(`[maven-proxy] outbound maxSockets: ${config.outboundMaxSockets}`);
|
|
128
|
+
startupInfo(`[maven-proxy] outbound maxFreeSockets: ${config.outboundMaxFreeSockets}`);
|
|
129
|
+
startupInfo(`[maven-proxy] maven affinity enabled: ${config.mavenAffinityEnabled}`);
|
|
130
|
+
startupInfo(`[maven-proxy] maven affinity index dir: ${config.mavenAffinityIndexDir}`);
|
|
131
|
+
startupInfo(`[maven-proxy] maven negative cache ttl(hours): ${config.mavenNegativeCacheTtlMs / (60 * 60 * 1000)}`);
|
|
132
|
+
startupInfo(`[maven-proxy] maven affinity flush interval(seconds): ${config.mavenAffinityFlushIntervalMs / 1000}`);
|
|
133
|
+
startupInfo(`[maven-proxy] maven affinity event max(MB): ${config.mavenAffinityEventMaxBytes / (1024 * 1024)}`);
|
|
134
|
+
startupInfo(`[maven-proxy] root cert : ${config.rootCertPath}`);
|
|
135
|
+
startupInfo(`[maven-proxy] repo fallback repos: ${(config.repoFallbackRepos || []).join(",") || "(none)"}`);
|
|
83
136
|
if (config.upstreamProxyUrl || config.upstreamHttpProxyUrl || config.upstreamHttpsProxyUrl) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
137
|
+
startupInfo(`[maven-proxy] upstream proxy (generic): ${config.upstreamProxyUrl || "(none)"}`);
|
|
138
|
+
startupInfo(`[maven-proxy] upstream proxy (http) : ${config.upstreamHttpProxyUrl || "(none)"}`);
|
|
139
|
+
startupInfo(`[maven-proxy] upstream proxy (https) : ${config.upstreamHttpsProxyUrl || "(none)"}`);
|
|
140
|
+
startupInfo(`[maven-proxy] upstream no-proxy : ${(config.upstreamNoProxyDomains || []).join(",") || "(none)"}`);
|
|
141
|
+
startupInfo(`[maven-proxy] upstream ignore-domains : ${(config.upstreamIgnoreDomains || []).join(",") || "(none)"}`);
|
|
89
142
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
143
|
+
startupInfo("[maven-proxy] trust store command (copy):");
|
|
144
|
+
startupInfo(trustCommands.copyCmd);
|
|
145
|
+
startupInfo("[maven-proxy] trust store command (import):");
|
|
146
|
+
startupInfo(trustCommands.importCmd);
|
|
147
|
+
startupInfo(`[maven-proxy] startup success: proxy=127.0.0.1:${config.proxyPort}, repo=127.0.0.1:${config.repoPort}`);
|
|
94
148
|
|
|
95
149
|
const shutdown = () => {
|
|
96
150
|
proxyServer.close();
|
|
@@ -105,6 +159,6 @@ async function main() {
|
|
|
105
159
|
}
|
|
106
160
|
|
|
107
161
|
main().catch((error) => {
|
|
108
|
-
|
|
162
|
+
startupError("[maven-proxy] fatal error:", error);
|
|
109
163
|
process.exit(1);
|
|
110
164
|
});
|
|
@@ -20,7 +20,9 @@ async function openConnectUpstreamSocket(targetHost, targetPort, timeoutMs, upst
|
|
|
20
20
|
upstreamProxyManager.hasProxyFor("https:", targetHost);
|
|
21
21
|
|
|
22
22
|
if (useUpstreamProxy) {
|
|
23
|
-
|
|
23
|
+
if (upstreamProxyManager?.config?.logConnectEvents) {
|
|
24
|
+
console.log(`[proxy] CONNECT via upstream target=${targetHost}:${targetPort}`);
|
|
25
|
+
}
|
|
24
26
|
const tunnel = await upstreamProxyManager.createConnectTunnel(targetHost, targetPort, timeoutMs);
|
|
25
27
|
return {
|
|
26
28
|
upstreamSocket: tunnel.socket,
|
|
@@ -81,9 +83,13 @@ async function handlePassThroughConnect(clientSocket, head, targetHost, targetPo
|
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
async function handleMitmConnect(clientSocket, head, targetHost, certManager, mitmHttpServer) {
|
|
84
|
-
|
|
86
|
+
if (certManager?.config?.logConnectEvents) {
|
|
87
|
+
console.log(`[proxy] MITM prepare ${targetHost}`);
|
|
88
|
+
}
|
|
85
89
|
const leaf = await certManager.getOrCreateLeaf(targetHost);
|
|
86
|
-
|
|
90
|
+
if (certManager?.config?.logConnectEvents) {
|
|
91
|
+
console.log(`[proxy] MITM cert ready ${targetHost}`);
|
|
92
|
+
}
|
|
87
93
|
|
|
88
94
|
await new Promise((resolve, reject) => {
|
|
89
95
|
writeTunnelResponse(clientSocket, "HTTP/1.1 200 Connection Established", (error) => {
|
|
@@ -94,7 +100,9 @@ async function handleMitmConnect(clientSocket, head, targetHost, certManager, mi
|
|
|
94
100
|
resolve();
|
|
95
101
|
});
|
|
96
102
|
});
|
|
97
|
-
|
|
103
|
+
if (certManager?.config?.logConnectEvents) {
|
|
104
|
+
console.log(`[proxy] MITM tunnel established ${targetHost}`);
|
|
105
|
+
}
|
|
98
106
|
|
|
99
107
|
const tlsSocket = new tls.TLSSocket(clientSocket, {
|
|
100
108
|
isServer: true,
|
|
@@ -138,7 +146,9 @@ export function attachConnectHandler(server, {
|
|
|
138
146
|
config.enableHttpsProxy &&
|
|
139
147
|
matchesDomain(host, config.httpsMitmDomains);
|
|
140
148
|
|
|
141
|
-
|
|
149
|
+
if (config.logConnectEvents) {
|
|
150
|
+
console.log(`[proxy] CONNECT ${host}:${port} mitm=${mitmEnabled}`);
|
|
151
|
+
}
|
|
142
152
|
|
|
143
153
|
if (!mitmEnabled) {
|
|
144
154
|
if (!config.httpsPassthroughForUnmatched) {
|
|
@@ -80,6 +80,12 @@ function sendErrorText(res, statusCode, message, context = "proxy") {
|
|
|
80
80
|
sendText(res, statusCode, message);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
export function isPositiveAffinityEligible(fileName) {
|
|
84
|
+
const lower = String(fileName || "").toLowerCase();
|
|
85
|
+
const base = lower.replace(/\.(sha1|sha256|sha512|md5|asc)$/i, "");
|
|
86
|
+
return /\.(jar|aar|war)$/i.test(base);
|
|
87
|
+
}
|
|
88
|
+
|
|
83
89
|
function buildUrl(req, forcedProtocol = null) {
|
|
84
90
|
const raw = req.url || "/";
|
|
85
91
|
if (/^https?:\/\//i.test(raw)) {
|
|
@@ -203,16 +209,19 @@ export function createHttpRequestHandler({
|
|
|
203
209
|
|
|
204
210
|
const existing = await statIfFile(cachePath);
|
|
205
211
|
if (existing) {
|
|
212
|
+
console.log(`[proxy] local cache hit host=${urlObj.hostname} path=${urlObj.pathname}`);
|
|
206
213
|
await serveFile(res, req, cachePath);
|
|
207
214
|
return;
|
|
208
215
|
}
|
|
209
216
|
|
|
210
217
|
if (canonical && mavenAffinityIndex) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
218
|
+
if (isPositiveAffinityEligible(canonical.fileName)) {
|
|
219
|
+
const preferredPath = await mavenAffinityIndex.resolvePreferredCachePath(canonical.canonicalKey);
|
|
220
|
+
if (preferredPath) {
|
|
221
|
+
console.log(`[proxy] affinity hit canonical=${canonical.canonicalKey} host=${urlObj.hostname}`);
|
|
222
|
+
await serveFile(res, req, preferredPath);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
216
225
|
}
|
|
217
226
|
|
|
218
227
|
if (mavenAffinityIndex.shouldSkipRequest(canonical.canonicalKey, urlObj)) {
|
|
@@ -223,15 +232,17 @@ export function createHttpRequestHandler({
|
|
|
223
232
|
}
|
|
224
233
|
|
|
225
234
|
try {
|
|
235
|
+
console.log(`[proxy] local cache miss host=${urlObj.hostname} path=${urlObj.pathname}`);
|
|
226
236
|
await fs.promises.mkdir(path.dirname(cachePath), { recursive: true });
|
|
227
237
|
await downloader.ensureCached(urlObj, cachePath, req.headers);
|
|
228
238
|
|
|
229
|
-
if (canonical && mavenAffinityIndex) {
|
|
239
|
+
if (canonical && mavenAffinityIndex && isPositiveAffinityEligible(canonical.fileName)) {
|
|
230
240
|
mavenAffinityIndex.recordSuccess({
|
|
231
241
|
canonicalKey: canonical.canonicalKey,
|
|
232
242
|
host: urlObj.hostname,
|
|
233
243
|
cachePath,
|
|
234
244
|
fileName: canonical.fileName,
|
|
245
|
+
urlObj,
|
|
235
246
|
});
|
|
236
247
|
}
|
|
237
248
|
|