maven-proxy 1.1.1 → 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 +24 -7
- package/bin/maven-proxy.js +174 -9
- package/package.json +5 -2
- package/src/cache/downloader.js +0 -3
- package/src/cache/maven-affinity-index.js +396 -0
- package/src/common/console-log-file.js +39 -9
- package/src/common/maven-canonical.js +151 -0
- package/src/config/config.js +23 -6
- package/src/index.js +96 -25
- package/src/proxy/proxy-connect-handler.js +15 -5
- package/src/proxy/proxy-http-handler.js +63 -2
- package/src/proxy/proxy-server.js +9 -1
- package/src/proxy/upstream-proxy.js +41 -3
- package/src/common/download-log-writer.js +0 -27
package/src/index.js
CHANGED
|
@@ -7,14 +7,63 @@ import { startProxyServer } from "./proxy/proxy-server.js";
|
|
|
7
7
|
import { startRepoServer } from "./repo/repo-server.js";
|
|
8
8
|
import { getTrustStoreCommands } from "./cert/truststore-utils.js";
|
|
9
9
|
import { UpstreamProxyManager } from "./proxy/upstream-proxy.js";
|
|
10
|
+
import { MavenAffinityIndex } from "./cache/maven-affinity-index.js";
|
|
10
11
|
import { installConsoleLogFileMirror, installGlobalErrorLogging } from "./common/console-log-file.js";
|
|
11
12
|
|
|
12
13
|
installConsoleLogFileMirror({
|
|
13
14
|
logDir: config.downloadLogDir,
|
|
14
15
|
retentionDays: config.logRetentionDays,
|
|
16
|
+
outputToConsole: config.logToStdout,
|
|
15
17
|
});
|
|
16
18
|
installGlobalErrorLogging();
|
|
17
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
|
+
|
|
18
67
|
async function main() {
|
|
19
68
|
await fs.promises.mkdir(config.cacheDir, { recursive: true });
|
|
20
69
|
await fs.promises.mkdir(config.mavenCacheDir, { recursive: true });
|
|
@@ -34,6 +83,8 @@ async function main() {
|
|
|
34
83
|
}
|
|
35
84
|
|
|
36
85
|
const upstreamProxyManager = new UpstreamProxyManager(config, matchesDomain);
|
|
86
|
+
const mavenAffinityIndex = new MavenAffinityIndex(config);
|
|
87
|
+
await mavenAffinityIndex.init();
|
|
37
88
|
|
|
38
89
|
const downloader = new Downloader(config, matchesDomain, upstreamProxyManager);
|
|
39
90
|
|
|
@@ -43,44 +94,64 @@ async function main() {
|
|
|
43
94
|
downloader,
|
|
44
95
|
matchesDomain,
|
|
45
96
|
upstreamProxyManager,
|
|
97
|
+
mavenAffinityIndex,
|
|
46
98
|
);
|
|
47
99
|
const repoServer = startRepoServer(config, downloader);
|
|
48
100
|
|
|
101
|
+
await Promise.all([
|
|
102
|
+
waitForServerListening(proxyServer, "proxy server"),
|
|
103
|
+
waitForServerListening(repoServer, "repo server"),
|
|
104
|
+
]);
|
|
105
|
+
|
|
49
106
|
const trustCommands = getTrustStoreCommands(config);
|
|
50
107
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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}`);
|
|
55
112
|
if (config.configMode === "user") {
|
|
56
|
-
|
|
113
|
+
startupInfo(`[maven-proxy] default user config: ${config.defaultUserConfigPath}`);
|
|
57
114
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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)"}`);
|
|
68
136
|
if (config.upstreamProxyUrl || config.upstreamHttpProxyUrl || config.upstreamHttpsProxyUrl) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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)"}`);
|
|
74
142
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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}`);
|
|
79
148
|
|
|
80
149
|
const shutdown = () => {
|
|
81
150
|
proxyServer.close();
|
|
82
151
|
mitmHttpServer.close();
|
|
83
152
|
repoServer.close();
|
|
153
|
+
upstreamProxyManager.destroy();
|
|
154
|
+
void mavenAffinityIndex.destroy();
|
|
84
155
|
};
|
|
85
156
|
|
|
86
157
|
process.on("SIGINT", shutdown);
|
|
@@ -88,6 +159,6 @@ async function main() {
|
|
|
88
159
|
}
|
|
89
160
|
|
|
90
161
|
main().catch((error) => {
|
|
91
|
-
|
|
162
|
+
startupError("[maven-proxy] fatal error:", error);
|
|
92
163
|
process.exit(1);
|
|
93
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) {
|
|
@@ -4,6 +4,7 @@ import https from "node:https";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { getCacheFilePath } from "../cache/cache-path.js";
|
|
6
6
|
import { detectPackageEcosystem } from "../common/ecosystem.js";
|
|
7
|
+
import { parseMavenReleaseCanonical } from "../common/maven-canonical.js";
|
|
7
8
|
|
|
8
9
|
const LOCAL_FS_ERROR_CODES = new Set([
|
|
9
10
|
"EACCES",
|
|
@@ -79,6 +80,12 @@ function sendErrorText(res, statusCode, message, context = "proxy") {
|
|
|
79
80
|
sendText(res, statusCode, message);
|
|
80
81
|
}
|
|
81
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
|
+
|
|
82
89
|
function buildUrl(req, forcedProtocol = null) {
|
|
83
90
|
const raw = req.url || "/";
|
|
84
91
|
if (/^https?:\/\//i.test(raw)) {
|
|
@@ -158,7 +165,13 @@ function forwardDirectRequest(req, res, urlObj, timeoutMs, upstreamProxyManager
|
|
|
158
165
|
req.pipe(upstreamReq);
|
|
159
166
|
}
|
|
160
167
|
|
|
161
|
-
export function createHttpRequestHandler({
|
|
168
|
+
export function createHttpRequestHandler({
|
|
169
|
+
config,
|
|
170
|
+
downloader,
|
|
171
|
+
upstreamProxyManager = null,
|
|
172
|
+
matchesDomain,
|
|
173
|
+
mavenAffinityIndex = null,
|
|
174
|
+
}) {
|
|
162
175
|
return async function handleHttpRequestPath(req, res, forcedProtocol = null) {
|
|
163
176
|
let urlObj;
|
|
164
177
|
try {
|
|
@@ -176,12 +189,18 @@ export function createHttpRequestHandler({ config, downloader, upstreamProxyMana
|
|
|
176
189
|
}
|
|
177
190
|
|
|
178
191
|
let cachePath;
|
|
192
|
+
let ecosystem;
|
|
193
|
+
let canonical = null;
|
|
179
194
|
try {
|
|
180
|
-
|
|
195
|
+
ecosystem = detectPackageEcosystem(urlObj, config, matchesDomain);
|
|
181
196
|
cachePath = getCacheFilePath(config.cacheDir, urlObj, {
|
|
182
197
|
ecosystem,
|
|
183
198
|
includeHost: ecosystem !== "maven",
|
|
184
199
|
});
|
|
200
|
+
|
|
201
|
+
if (ecosystem === "maven" && mavenAffinityIndex?.enabled) {
|
|
202
|
+
canonical = parseMavenReleaseCanonical(urlObj);
|
|
203
|
+
}
|
|
185
204
|
} catch (error) {
|
|
186
205
|
const message = `Invalid cache path: ${error.message}`;
|
|
187
206
|
sendErrorText(res, 400, message, "proxy");
|
|
@@ -190,16 +209,58 @@ export function createHttpRequestHandler({ config, downloader, upstreamProxyMana
|
|
|
190
209
|
|
|
191
210
|
const existing = await statIfFile(cachePath);
|
|
192
211
|
if (existing) {
|
|
212
|
+
console.log(`[proxy] local cache hit host=${urlObj.hostname} path=${urlObj.pathname}`);
|
|
193
213
|
await serveFile(res, req, cachePath);
|
|
194
214
|
return;
|
|
195
215
|
}
|
|
196
216
|
|
|
217
|
+
if (canonical && mavenAffinityIndex) {
|
|
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
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (mavenAffinityIndex.shouldSkipRequest(canonical.canonicalKey, urlObj)) {
|
|
228
|
+
console.log(`[proxy] affinity negative skip canonical=${canonical.canonicalKey} host=${urlObj.hostname}`);
|
|
229
|
+
sendText(res, 404, "Not Found");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
197
234
|
try {
|
|
235
|
+
console.log(`[proxy] local cache miss host=${urlObj.hostname} path=${urlObj.pathname}`);
|
|
198
236
|
await fs.promises.mkdir(path.dirname(cachePath), { recursive: true });
|
|
199
237
|
await downloader.ensureCached(urlObj, cachePath, req.headers);
|
|
238
|
+
|
|
239
|
+
if (canonical && mavenAffinityIndex && isPositiveAffinityEligible(canonical.fileName)) {
|
|
240
|
+
mavenAffinityIndex.recordSuccess({
|
|
241
|
+
canonicalKey: canonical.canonicalKey,
|
|
242
|
+
host: urlObj.hostname,
|
|
243
|
+
cachePath,
|
|
244
|
+
fileName: canonical.fileName,
|
|
245
|
+
urlObj,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
200
249
|
res.setHeader("x-cache", "MISS");
|
|
201
250
|
await serveFile(res, req, cachePath);
|
|
202
251
|
} catch (error) {
|
|
252
|
+
if (
|
|
253
|
+
canonical &&
|
|
254
|
+
mavenAffinityIndex &&
|
|
255
|
+
(error.statusCode === 404 || error.statusCode === 410)
|
|
256
|
+
) {
|
|
257
|
+
mavenAffinityIndex.recordNegative({
|
|
258
|
+
canonicalKey: canonical.canonicalKey,
|
|
259
|
+
urlObj,
|
|
260
|
+
statusCode: error.statusCode,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
203
264
|
if (isLocalFsWriteError(error)) {
|
|
204
265
|
if (!error.statusCode) {
|
|
205
266
|
error.statusCode = 500;
|
|
@@ -12,12 +12,20 @@ function sendErrorText(res, statusCode, message) {
|
|
|
12
12
|
sendText(res, statusCode, message);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function startProxyServer(
|
|
15
|
+
export function startProxyServer(
|
|
16
|
+
config,
|
|
17
|
+
certManager,
|
|
18
|
+
downloader,
|
|
19
|
+
matchesDomain,
|
|
20
|
+
upstreamProxyManager = null,
|
|
21
|
+
mavenAffinityIndex = null,
|
|
22
|
+
) {
|
|
16
23
|
const handleHttpRequestPath = createHttpRequestHandler({
|
|
17
24
|
config,
|
|
18
25
|
downloader,
|
|
19
26
|
upstreamProxyManager,
|
|
20
27
|
matchesDomain,
|
|
28
|
+
mavenAffinityIndex,
|
|
21
29
|
});
|
|
22
30
|
const mitmHttpServer = createMitmHttpServer(handleHttpRequestPath);
|
|
23
31
|
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import https from "node:https";
|
|
1
3
|
import net from "node:net";
|
|
2
4
|
import tls from "node:tls";
|
|
3
5
|
import { ProxyAgent } from "proxy-agent";
|
|
4
6
|
|
|
7
|
+
function toPositiveInt(value, fallback) {
|
|
8
|
+
const parsed = Number.parseInt(value, 10);
|
|
9
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function buildAgentOptions(config) {
|
|
13
|
+
return {
|
|
14
|
+
keepAlive: Boolean(config.outboundKeepAlive),
|
|
15
|
+
keepAliveMsecs: toPositiveInt(config.outboundKeepAliveMsecs, 1000),
|
|
16
|
+
maxSockets: toPositiveInt(config.outboundMaxSockets, 64),
|
|
17
|
+
maxFreeSockets: toPositiveInt(config.outboundMaxFreeSockets, 16),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
function normalizeHostname(hostname) {
|
|
6
22
|
return String(hostname || "")
|
|
7
23
|
.trim()
|
|
@@ -64,6 +80,12 @@ export class UpstreamProxyManager {
|
|
|
64
80
|
this.config = config;
|
|
65
81
|
this.matchesDomain = matchesDomain;
|
|
66
82
|
this.agentCache = new Map();
|
|
83
|
+
this.directHttpAgent = new http.Agent(buildAgentOptions(config));
|
|
84
|
+
this.directHttpsAgent = new https.Agent(buildAgentOptions(config));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getDirectAgentForProtocol(protocol) {
|
|
88
|
+
return protocol === "https:" ? this.directHttpsAgent : this.directHttpAgent;
|
|
67
89
|
}
|
|
68
90
|
|
|
69
91
|
shouldBypass(hostname) {
|
|
@@ -111,17 +133,21 @@ export class UpstreamProxyManager {
|
|
|
111
133
|
}
|
|
112
134
|
|
|
113
135
|
getAgentForUrl(urlObj) {
|
|
114
|
-
const
|
|
136
|
+
const protocol = urlObj?.protocol === "https:" ? "https:" : "http:";
|
|
137
|
+
const hostname = String(urlObj?.hostname || "");
|
|
138
|
+
const proxyUrl = this.getProxyUrlFor(protocol, hostname);
|
|
139
|
+
|
|
115
140
|
if (!proxyUrl) {
|
|
116
|
-
return
|
|
141
|
+
return this.getDirectAgentForProtocol(protocol);
|
|
117
142
|
}
|
|
118
143
|
|
|
119
|
-
const cacheKey =
|
|
144
|
+
const cacheKey = `proxy:${proxyUrl}`;
|
|
120
145
|
if (!this.agentCache.has(cacheKey)) {
|
|
121
146
|
// proxy-agent v6 expects resolver-style options for deterministic proxy routing.
|
|
122
147
|
this.agentCache.set(
|
|
123
148
|
cacheKey,
|
|
124
149
|
new ProxyAgent({
|
|
150
|
+
...buildAgentOptions(this.config),
|
|
125
151
|
getProxyForUrl: () => proxyUrl,
|
|
126
152
|
}),
|
|
127
153
|
);
|
|
@@ -134,6 +160,18 @@ export class UpstreamProxyManager {
|
|
|
134
160
|
return Boolean(this.getProxyUrlFor(protocol, hostname));
|
|
135
161
|
}
|
|
136
162
|
|
|
163
|
+
destroy() {
|
|
164
|
+
for (const agent of this.agentCache.values()) {
|
|
165
|
+
if (typeof agent?.destroy === "function") {
|
|
166
|
+
agent.destroy();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.agentCache.clear();
|
|
171
|
+
this.directHttpAgent.destroy();
|
|
172
|
+
this.directHttpsAgent.destroy();
|
|
173
|
+
}
|
|
174
|
+
|
|
137
175
|
async createConnectTunnel(targetHost, targetPort, timeoutMs) {
|
|
138
176
|
const proxyUrlText = this.getProxyUrlFor("https:", targetHost);
|
|
139
177
|
if (!proxyUrlText) {
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { DailyLogFile } from "./daily-log-file.js";
|
|
2
|
-
|
|
3
|
-
export class DownloadLogWriter {
|
|
4
|
-
constructor(logDir, retentionDays = 7) {
|
|
5
|
-
this.logFile = new DailyLogFile({
|
|
6
|
-
logDir,
|
|
7
|
-
filePrefix: "download",
|
|
8
|
-
retentionDays,
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async append(event, url, details = {}) {
|
|
13
|
-
const record = {
|
|
14
|
-
time: new Date().toISOString(),
|
|
15
|
-
event,
|
|
16
|
-
url,
|
|
17
|
-
...details,
|
|
18
|
-
};
|
|
19
|
-
await this.logFile.appendLine(JSON.stringify(record));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
write(event, url, details = {}) {
|
|
23
|
-
this.append(event, url, details).catch((error) => {
|
|
24
|
-
console.warn(`[downloader] write download log failed: ${error.message}`);
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
}
|