maven-proxy 1.3.2 → 1.3.3
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 -10
- package/bin/maven-proxy.js +4 -4
- package/package.json +3 -3
- package/src/cache/{maven-affinity-index.js → maven-negative-index.js} +27 -143
- package/src/config/config.js +16 -8
- package/src/index.js +9 -9
- package/src/proxy/proxy-http-handler.js +56 -31
package/README.md
CHANGED
|
@@ -288,11 +288,11 @@ Environment variables:
|
|
|
288
288
|
- OUTBOUND_KEEP_ALIVE_INTERVAL: keep-alive interval (supports s/m/h/d), for example 1s.
|
|
289
289
|
- OUTBOUND_MAX_SOCKETS: max outbound sockets per origin.
|
|
290
290
|
- OUTBOUND_MAX_FREE_SOCKETS: max idle outbound sockets per origin.
|
|
291
|
-
-
|
|
292
|
-
-
|
|
293
|
-
- MAVEN_NEGATIVE_CACHE_TTL: negative cache TTL (supports s/m/h/d), for example 24h.
|
|
294
|
-
-
|
|
295
|
-
-
|
|
291
|
+
- MAVEN_NEGATIVE_ENABLED: enable Maven negative index. Default true.
|
|
292
|
+
- MAVEN_NEGATIVE_INDEX_DIR: Maven negative index directory. Default data/index.
|
|
293
|
+
- MAVEN_NEGATIVE_CACHE_TTL: negative cache TTL (supports s/m/h/d), for example 24h.
|
|
294
|
+
- MAVEN_NEGATIVE_FLUSH_INTERVAL: flush interval for negative event log (supports s/m/h/d), for example 5s.
|
|
295
|
+
- MAVEN_NEGATIVE_EVENT_MAX_MB: max size threshold for negative event log compaction in MB.
|
|
296
296
|
- MAVEN_PROXY_CONFIG_MODE: development or user.
|
|
297
297
|
- MAVEN_PROXY_CONFIG_FILE: explicit config file path.
|
|
298
298
|
- EXISTING_TRUST_STORE_PATH: optional existing truststore path. If present, truststore init prefers it as source.
|
|
@@ -343,11 +343,11 @@ Priority:
|
|
|
343
343
|
- `OUTBOUND_KEEP_ALIVE_INTERVAL`: Keep-alive interval (supports `s/m/h/d`). Default `1s`.
|
|
344
344
|
- `OUTBOUND_MAX_SOCKETS`: Max outbound sockets per origin. Default `64`.
|
|
345
345
|
- `OUTBOUND_MAX_FREE_SOCKETS`: Max idle outbound sockets per origin. Default `16`.
|
|
346
|
-
- `
|
|
347
|
-
- `
|
|
348
|
-
- `MAVEN_NEGATIVE_CACHE_TTL`: Negative cache TTL (supports `s/m/h/d`). Default `24h`.
|
|
349
|
-
- `
|
|
350
|
-
- `
|
|
346
|
+
- `MAVEN_NEGATIVE_ENABLED`: Enable Maven negative cache index. Default `true`.
|
|
347
|
+
- `MAVEN_NEGATIVE_INDEX_DIR`: Maven negative index directory. Default `data/index`.
|
|
348
|
+
- `MAVEN_NEGATIVE_CACHE_TTL`: Negative cache TTL (supports `s/m/h/d`). Default `24h`.
|
|
349
|
+
- `MAVEN_NEGATIVE_FLUSH_INTERVAL`: Flush interval for negative event log (supports `s/m/h/d`). Default `5s`.
|
|
350
|
+
- `MAVEN_NEGATIVE_EVENT_MAX_MB`: Max size threshold for negative event log compaction in MB. Default `8`.
|
|
351
351
|
- `UPSTREAM_PROXY_URL`: Generic upstream proxy URL (fallback for HTTP/HTTPS).
|
|
352
352
|
- `UPSTREAM_HTTP_PROXY_URL`: Upstream proxy URL for HTTP requests.
|
|
353
353
|
- `UPSTREAM_HTTPS_PROXY_URL`: Upstream proxy URL for HTTPS requests.
|
package/bin/maven-proxy.js
CHANGED
|
@@ -193,11 +193,11 @@ function getDefaultConfigTemplate() {
|
|
|
193
193
|
appendEntry("OUTBOUND_KEEP_ALIVE_INTERVAL", "1s", "keep-alive 间隔(支持 s/m/h/d),默认 1s。", "Keep-alive interval (supports s/m/h/d). Default: 1s.");
|
|
194
194
|
appendEntry("OUTBOUND_MAX_SOCKETS", "64", "每个源站最大出站连接数,默认 64。", "Maximum outbound sockets per upstream host. Default: 64.");
|
|
195
195
|
appendEntry("OUTBOUND_MAX_FREE_SOCKETS", "16", "每个源站可保留空闲连接上限,默认 16。", "Maximum free outbound sockets kept per upstream host. Default: 16.");
|
|
196
|
-
appendEntry("
|
|
197
|
-
appendEntry("
|
|
196
|
+
appendEntry("MAVEN_NEGATIVE_ENABLED", "true", "是否启用 Maven negative 索引,默认 true。", "Enable Maven negative index. Default: true.");
|
|
197
|
+
appendEntry("MAVEN_NEGATIVE_INDEX_DIR", "data/index", "Maven negative 索引目录,默认 data/index。", "Maven negative index directory. Default: data/index.");
|
|
198
198
|
appendEntry("MAVEN_NEGATIVE_CACHE_TTL", "24h", "负缓存 TTL(支持 s/m/h/d),默认 24h。", "Negative cache TTL (supports s/m/h/d). Default: 24h.");
|
|
199
|
-
appendEntry("
|
|
200
|
-
appendEntry("
|
|
199
|
+
appendEntry("MAVEN_NEGATIVE_FLUSH_INTERVAL", "5s", "negative 事件日志 flush 周期(支持 s/m/h/d),默认 5s。", "Negative event log flush interval (supports s/m/h/d). Default: 5s.");
|
|
200
|
+
appendEntry("MAVEN_NEGATIVE_EVENT_MAX_MB", "8", "negative 事件日志压缩阈值(MB),默认 8。", "Negative event log compaction threshold in MB. Default: 8.");
|
|
201
201
|
appendEntry("UPSTREAM_PROXY_URL", "", "通用上级代理地址(HTTP/HTTPS 兜底)。", "Generic upstream proxy URL fallback for HTTP/HTTPS.");
|
|
202
202
|
appendEntry("UPSTREAM_HTTP_PROXY_URL", "", "HTTP 请求使用的上级代理地址。", "Upstream proxy URL for HTTP requests.");
|
|
203
203
|
appendEntry("UPSTREAM_HTTPS_PROXY_URL", "", "HTTPS 请求使用的上级代理地址。", "Upstream proxy URL for HTTPS requests.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maven-proxy",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
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": {
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"npm:version:patch": "npm version patch",
|
|
36
36
|
"npm:view:version": "npm view maven-proxy version",
|
|
37
37
|
"npm:view:dist-tags": "npm view maven-proxy dist-tags",
|
|
38
|
-
"replay:
|
|
39
|
-
"test:replay": "node --test test/replay-
|
|
38
|
+
"replay:negative": "node --test test/replay-negative.test.js",
|
|
39
|
+
"test:replay": "node --test test/replay-negative.test.js",
|
|
40
40
|
"test": "node --test test/*.test.js"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [],
|
|
@@ -31,16 +31,6 @@ function readJsonFile(filePath, fallback) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function serializeSnapshot(positiveMap, negativeMap, conflictMap) {
|
|
35
|
-
return {
|
|
36
|
-
version: 1,
|
|
37
|
-
generatedAt: new Date().toISOString(),
|
|
38
|
-
positive: [...positiveMap.entries()],
|
|
39
|
-
negative: [...negativeMap.entries()],
|
|
40
|
-
conflicts: [...conflictMap.entries()],
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
34
|
function normalizeRequestPath(pathname) {
|
|
45
35
|
return String(pathname || "")
|
|
46
36
|
.replace(/\\/g, "/")
|
|
@@ -64,22 +54,26 @@ function buildNegativeKey(scope, canonicalKey) {
|
|
|
64
54
|
return `${String(scope || "").toLowerCase()}|${canonicalKey}`;
|
|
65
55
|
}
|
|
66
56
|
|
|
67
|
-
|
|
57
|
+
function serializeSnapshot(negativeMap) {
|
|
58
|
+
return {
|
|
59
|
+
version: 1,
|
|
60
|
+
generatedAt: new Date().toISOString(),
|
|
61
|
+
negative: [...negativeMap.entries()],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class MavenNegativeIndex {
|
|
68
66
|
constructor(config) {
|
|
69
|
-
this.enabled = toBool(config.
|
|
70
|
-
this.indexDir = config.
|
|
67
|
+
this.enabled = toBool(config.mavenNegativeEnabled, true);
|
|
68
|
+
this.indexDir = config.mavenNegativeIndexDir;
|
|
71
69
|
this.negativeTtlMs = toPositiveInt(config.mavenNegativeCacheTtlMs, 24 * 60 * 60 * 1000);
|
|
72
|
-
this.flushIntervalMs = toPositiveInt(config.
|
|
73
|
-
this.maxEventBytes = toPositiveInt(config.
|
|
70
|
+
this.flushIntervalMs = toPositiveInt(config.mavenNegativeFlushIntervalMs, 5000);
|
|
71
|
+
this.maxEventBytes = toPositiveInt(config.mavenNegativeEventMaxBytes, 8 * 1024 * 1024);
|
|
74
72
|
|
|
75
|
-
this.snapshotPath = path.join(this.indexDir, "maven-
|
|
76
|
-
this.eventLogPath = path.join(this.indexDir, "maven-
|
|
73
|
+
this.snapshotPath = path.join(this.indexDir, "maven-negative.snapshot.json");
|
|
74
|
+
this.eventLogPath = path.join(this.indexDir, "maven-negative.events.log");
|
|
77
75
|
|
|
78
|
-
// Positive entries are persistent and have no TTL. They are removed only
|
|
79
|
-
// when the cache file disappears or a conflict is detected.
|
|
80
|
-
this.positive = new Map();
|
|
81
76
|
this.negative = new Map();
|
|
82
|
-
this.conflicts = new Map();
|
|
83
77
|
|
|
84
78
|
this.pendingEvents = [];
|
|
85
79
|
this.flushTimer = null;
|
|
@@ -98,7 +92,7 @@ export class MavenAffinityIndex {
|
|
|
98
92
|
|
|
99
93
|
this.flushTimer = setInterval(() => {
|
|
100
94
|
this.flush().catch((error) => {
|
|
101
|
-
console.error(`[
|
|
95
|
+
console.error(`[maven-negative] flush failed: ${error.message}`);
|
|
102
96
|
});
|
|
103
97
|
}, this.flushIntervalMs);
|
|
104
98
|
|
|
@@ -113,20 +107,12 @@ export class MavenAffinityIndex {
|
|
|
113
107
|
return;
|
|
114
108
|
}
|
|
115
109
|
|
|
116
|
-
for (const [key, value] of snapshot.positive || []) {
|
|
117
|
-
this.positive.set(key, value);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
110
|
const currentTime = nowMs();
|
|
121
111
|
for (const [key, value] of snapshot.negative || []) {
|
|
122
112
|
if (value?.expireAt && value.expireAt > currentTime) {
|
|
123
113
|
this.negative.set(key, value);
|
|
124
114
|
}
|
|
125
115
|
}
|
|
126
|
-
|
|
127
|
-
for (const [key, value] of snapshot.conflicts || []) {
|
|
128
|
-
this.conflicts.set(key, value);
|
|
129
|
-
}
|
|
130
116
|
}
|
|
131
117
|
|
|
132
118
|
#replayEventLog() {
|
|
@@ -167,22 +153,6 @@ export class MavenAffinityIndex {
|
|
|
167
153
|
return;
|
|
168
154
|
}
|
|
169
155
|
|
|
170
|
-
if (type === "positive_upsert") {
|
|
171
|
-
this.positive.set(payload.key, payload.value);
|
|
172
|
-
if (append) {
|
|
173
|
-
this.#enqueueEvent(type, payload);
|
|
174
|
-
}
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (type === "positive_remove") {
|
|
179
|
-
this.positive.delete(payload.key);
|
|
180
|
-
if (append) {
|
|
181
|
-
this.#enqueueEvent(type, payload);
|
|
182
|
-
}
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
156
|
if (type === "negative_upsert") {
|
|
187
157
|
if (payload.value?.expireAt > nowMs()) {
|
|
188
158
|
this.negative.set(payload.key, payload.value);
|
|
@@ -202,50 +172,6 @@ export class MavenAffinityIndex {
|
|
|
202
172
|
}
|
|
203
173
|
return;
|
|
204
174
|
}
|
|
205
|
-
|
|
206
|
-
if (type === "conflict_set") {
|
|
207
|
-
this.conflicts.set(payload.key, payload.value);
|
|
208
|
-
if (append) {
|
|
209
|
-
this.#enqueueEvent(type, payload);
|
|
210
|
-
}
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (type === "conflict_clear") {
|
|
215
|
-
this.conflicts.delete(payload.key);
|
|
216
|
-
if (append) {
|
|
217
|
-
this.#enqueueEvent(type, payload);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
async resolvePreferredCachePath(canonicalKey) {
|
|
223
|
-
if (!this.enabled || !canonicalKey) {
|
|
224
|
-
return "";
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (this.conflicts.has(canonicalKey)) {
|
|
228
|
-
return "";
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const existing = this.positive.get(canonicalKey);
|
|
232
|
-
if (!existing?.cachePath) {
|
|
233
|
-
return "";
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
const stats = await fs.promises.stat(existing.cachePath);
|
|
238
|
-
if (!stats.isFile()) {
|
|
239
|
-
throw new Error("not-file");
|
|
240
|
-
}
|
|
241
|
-
return existing.cachePath;
|
|
242
|
-
} catch {
|
|
243
|
-
this.#applyEvent({
|
|
244
|
-
type: "positive_remove",
|
|
245
|
-
payload: { key: canonicalKey },
|
|
246
|
-
});
|
|
247
|
-
return "";
|
|
248
|
-
}
|
|
249
175
|
}
|
|
250
176
|
|
|
251
177
|
shouldSkipRequest(canonicalKey, urlObj) {
|
|
@@ -271,58 +197,6 @@ export class MavenAffinityIndex {
|
|
|
271
197
|
return true;
|
|
272
198
|
}
|
|
273
199
|
|
|
274
|
-
recordSuccess({ canonicalKey, host, cachePath, fileName, urlObj = null }) {
|
|
275
|
-
if (!this.enabled || !canonicalKey || !cachePath || !fileName) {
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const previous = this.positive.get(canonicalKey);
|
|
280
|
-
if (previous && previous.fileName !== fileName) {
|
|
281
|
-
this.#applyEvent({
|
|
282
|
-
type: "conflict_set",
|
|
283
|
-
payload: {
|
|
284
|
-
key: canonicalKey,
|
|
285
|
-
value: {
|
|
286
|
-
reason: "file-name-mismatch",
|
|
287
|
-
updatedAt: nowMs(),
|
|
288
|
-
previousFileName: previous.fileName,
|
|
289
|
-
currentFileName: fileName,
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
this.#applyEvent({
|
|
295
|
-
type: "positive_remove",
|
|
296
|
-
payload: { key: canonicalKey },
|
|
297
|
-
});
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
this.#applyEvent({
|
|
302
|
-
type: "positive_upsert",
|
|
303
|
-
payload: {
|
|
304
|
-
key: canonicalKey,
|
|
305
|
-
value: {
|
|
306
|
-
cachePath,
|
|
307
|
-
fileName,
|
|
308
|
-
host: String(host || "").toLowerCase(),
|
|
309
|
-
updatedAt: nowMs(),
|
|
310
|
-
},
|
|
311
|
-
},
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
const successScope = buildNegativeScope(urlObj);
|
|
315
|
-
if (successScope) {
|
|
316
|
-
const negativeKey = buildNegativeKey(successScope, canonicalKey);
|
|
317
|
-
if (this.negative.has(negativeKey)) {
|
|
318
|
-
this.#applyEvent({
|
|
319
|
-
type: "negative_remove",
|
|
320
|
-
payload: { key: negativeKey },
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
200
|
recordNegative({ canonicalKey, urlObj, statusCode = 404, ttlMs = this.negativeTtlMs }) {
|
|
327
201
|
const scope = buildNegativeScope(urlObj);
|
|
328
202
|
if (!this.enabled || !canonicalKey || !scope) {
|
|
@@ -345,6 +219,16 @@ export class MavenAffinityIndex {
|
|
|
345
219
|
});
|
|
346
220
|
}
|
|
347
221
|
|
|
222
|
+
clearNegative({ canonicalKey, urlObj }) {
|
|
223
|
+
const scope = buildNegativeScope(urlObj);
|
|
224
|
+
if (!this.enabled || !canonicalKey || !scope) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const key = buildNegativeKey(scope, canonicalKey);
|
|
229
|
+
this.#applyEvent({ type: "negative_remove", payload: { key } });
|
|
230
|
+
}
|
|
231
|
+
|
|
348
232
|
async flush() {
|
|
349
233
|
if (!this.enabled || this.flushing) {
|
|
350
234
|
return;
|
|
@@ -370,7 +254,7 @@ export class MavenAffinityIndex {
|
|
|
370
254
|
}
|
|
371
255
|
|
|
372
256
|
async #writeSnapshotAndResetEventLog() {
|
|
373
|
-
const snapshot = serializeSnapshot(this.
|
|
257
|
+
const snapshot = serializeSnapshot(this.negative);
|
|
374
258
|
const tempPath = `${this.snapshotPath}.tmp`;
|
|
375
259
|
await fs.promises.writeFile(tempPath, `${JSON.stringify(snapshot, null, 2)}\n`, "utf8");
|
|
376
260
|
await fs.promises.rename(tempPath, this.snapshotPath);
|
package/src/config/config.js
CHANGED
|
@@ -230,15 +230,16 @@ const multiThreadMinSizeBytes = Math.max(0, toInt(process.env.MULTI_THREAD_MIN_S
|
|
|
230
230
|
const downloadTimeout = process.env.DOWNLOAD_TIMEOUT || "60s";
|
|
231
231
|
const outboundKeepAliveInterval = process.env.OUTBOUND_KEEP_ALIVE_INTERVAL || "1s";
|
|
232
232
|
const mavenNegativeCacheTtl = process.env.MAVEN_NEGATIVE_CACHE_TTL || "24h";
|
|
233
|
-
|
|
233
|
+
// Prefer new MAVEN_NEGATIVE_* names but fall back to legacy MAVEN_AFFINITY_* for compatibility
|
|
234
|
+
const mavenNegativeFlushInterval = process.env.MAVEN_NEGATIVE_FLUSH_INTERVAL || process.env.MAVEN_AFFINITY_FLUSH_INTERVAL || "5s";
|
|
234
235
|
const logRetention = process.env.LOG_RETENTION || "7d";
|
|
235
236
|
|
|
236
237
|
const downloadTimeoutMs = Math.max(1, parseDurationToMs(downloadTimeout, 60 * 1000));
|
|
237
238
|
const outboundKeepAliveMsecs = Math.max(1, parseDurationToMs(outboundKeepAliveInterval, 1000));
|
|
238
239
|
const mavenNegativeCacheTtlMs = Math.max(1, parseDurationToMs(mavenNegativeCacheTtl, 24 * 60 * 60 * 1000));
|
|
239
|
-
const
|
|
240
|
+
const mavenNegativeFlushIntervalMs = Math.max(1, parseDurationToMs(mavenNegativeFlushInterval, 5 * 1000));
|
|
240
241
|
const logRetentionDays = Math.max(1, Math.ceil(parseDurationToMs(logRetention, 7 * 24 * 60 * 60 * 1000) / (24 * 60 * 60 * 1000)));
|
|
241
|
-
const
|
|
242
|
+
const mavenNegativeEventMaxBytes = Math.max(1, toInt(process.env.MAVEN_NEGATIVE_EVENT_MAX_MB ?? process.env.MAVEN_AFFINITY_EVENT_MAX_MB, 8)) * 1024 * 1024;
|
|
242
243
|
|
|
243
244
|
export const config = {
|
|
244
245
|
configMode,
|
|
@@ -268,13 +269,20 @@ export const config = {
|
|
|
268
269
|
outboundKeepAliveMsecs,
|
|
269
270
|
outboundMaxSockets: Math.max(1, toInt(process.env.OUTBOUND_MAX_SOCKETS, 64)),
|
|
270
271
|
outboundMaxFreeSockets: Math.max(1, toInt(process.env.OUTBOUND_MAX_FREE_SOCKETS, 16)),
|
|
271
|
-
|
|
272
|
-
|
|
272
|
+
// Negative-only index configuration (new names)
|
|
273
|
+
mavenNegativeEnabled: toBool(process.env.MAVEN_NEGATIVE_ENABLED ?? process.env.MAVEN_AFFINITY_ENABLED, true),
|
|
274
|
+
mavenNegativeIndexDir: path.resolve(configBaseDir, process.env.MAVEN_NEGATIVE_INDEX_DIR || process.env.MAVEN_AFFINITY_INDEX_DIR || "data/index"),
|
|
273
275
|
mavenNegativeCacheTtl,
|
|
274
276
|
mavenNegativeCacheTtlMs,
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
277
|
+
mavenNegativeFlushInterval,
|
|
278
|
+
mavenNegativeFlushIntervalMs,
|
|
279
|
+
mavenNegativeEventMaxBytes,
|
|
280
|
+
// Backwards-compatible aliases for older code that still references affinity names
|
|
281
|
+
mavenAffinityEnabled: toBool(process.env.MAVEN_NEGATIVE_ENABLED ?? process.env.MAVEN_AFFINITY_ENABLED, true),
|
|
282
|
+
mavenAffinityIndexDir: path.resolve(configBaseDir, process.env.MAVEN_NEGATIVE_INDEX_DIR || process.env.MAVEN_AFFINITY_INDEX_DIR || "data/index"),
|
|
283
|
+
mavenAffinityFlushInterval: mavenNegativeFlushInterval,
|
|
284
|
+
mavenAffinityFlushIntervalMs: mavenNegativeFlushIntervalMs,
|
|
285
|
+
mavenAffinityEventMaxBytes: mavenNegativeEventMaxBytes,
|
|
278
286
|
cacheCleanupEnabled: toBool(process.env.CACHE_CLEANUP_ENABLED, true),
|
|
279
287
|
cacheCleanupDailyAt: process.env.CACHE_CLEANUP_DAILY_AT || "03:00",
|
|
280
288
|
cacheCleanupCheckMinInterval: process.env.CACHE_CLEANUP_CHECK_MIN_INTERVAL || "10m",
|
package/src/index.js
CHANGED
|
@@ -7,7 +7,7 @@ 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 {
|
|
10
|
+
import { MavenNegativeIndex } from "./cache/maven-negative-index.js";
|
|
11
11
|
import { CacheCleanupManager } from "./cache/cache-cleanup-manager.js";
|
|
12
12
|
import { installConsoleLogFileMirror, installGlobalErrorLogging } from "./common/console-log-file.js";
|
|
13
13
|
|
|
@@ -84,8 +84,8 @@ async function main() {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
const upstreamProxyManager = new UpstreamProxyManager(config, matchesDomain);
|
|
87
|
-
const
|
|
88
|
-
await
|
|
87
|
+
const mavenNegativeIndex = new MavenNegativeIndex(config);
|
|
88
|
+
await mavenNegativeIndex.init();
|
|
89
89
|
const cacheCleanupManager = new CacheCleanupManager(config);
|
|
90
90
|
await cacheCleanupManager.init();
|
|
91
91
|
|
|
@@ -97,7 +97,7 @@ async function main() {
|
|
|
97
97
|
downloader,
|
|
98
98
|
matchesDomain,
|
|
99
99
|
upstreamProxyManager,
|
|
100
|
-
|
|
100
|
+
mavenNegativeIndex,
|
|
101
101
|
cacheCleanupManager,
|
|
102
102
|
);
|
|
103
103
|
const repoServer = startRepoServer(config, downloader, cacheCleanupManager);
|
|
@@ -132,12 +132,12 @@ async function main() {
|
|
|
132
132
|
startupInfo(`[maven-proxy] outbound keepAlive interval: ${config.outboundKeepAliveInterval}`);
|
|
133
133
|
startupInfo(`[maven-proxy] outbound maxSockets: ${config.outboundMaxSockets}`);
|
|
134
134
|
startupInfo(`[maven-proxy] outbound maxFreeSockets: ${config.outboundMaxFreeSockets}`);
|
|
135
|
-
startupInfo(`[maven-proxy] maven
|
|
136
|
-
startupInfo(`[maven-proxy] maven
|
|
135
|
+
startupInfo(`[maven-proxy] maven negative index enabled: ${config.mavenNegativeEnabled}`);
|
|
136
|
+
startupInfo(`[maven-proxy] maven negative index dir: ${config.mavenNegativeIndexDir}`);
|
|
137
137
|
startupInfo(`[maven-proxy] maven negative cache ttl: ${config.mavenNegativeCacheTtl}`);
|
|
138
|
-
startupInfo(`[maven-proxy] maven
|
|
138
|
+
startupInfo(`[maven-proxy] maven negative flush interval: ${config.mavenNegativeFlushInterval}`);
|
|
139
139
|
startupInfo(`[maven-proxy] download timeout: ${config.downloadTimeout}`);
|
|
140
|
-
startupInfo(`[maven-proxy] maven
|
|
140
|
+
startupInfo(`[maven-proxy] maven negative event max(MB): ${config.mavenNegativeEventMaxBytes / (1024 * 1024)}`);
|
|
141
141
|
startupInfo(`[maven-proxy] cache cleanup enabled: ${config.cacheCleanupEnabled}`);
|
|
142
142
|
startupInfo(`[maven-proxy] cache cleanup daily at: ${config.cacheCleanupDailyAt}`);
|
|
143
143
|
startupInfo(`[maven-proxy] cache touch on hit: ${config.cacheTouchOnHit}`);
|
|
@@ -169,7 +169,7 @@ async function main() {
|
|
|
169
169
|
repoServer.close();
|
|
170
170
|
upstreamProxyManager.destroy();
|
|
171
171
|
void cacheCleanupManager.destroy();
|
|
172
|
-
void
|
|
172
|
+
void mavenNegativeIndex.destroy();
|
|
173
173
|
};
|
|
174
174
|
|
|
175
175
|
process.on("SIGINT", shutdown);
|
|
@@ -36,6 +36,44 @@ function pickClient(protocol) {
|
|
|
36
36
|
return protocol === "https:" ? https : http;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function hasFileExtension(urlObj) {
|
|
40
|
+
try {
|
|
41
|
+
const pathname = String(urlObj?.pathname || "");
|
|
42
|
+
const base = path.basename(pathname || "").toLowerCase();
|
|
43
|
+
if (!base) return false;
|
|
44
|
+
|
|
45
|
+
const knownSuffixes = [
|
|
46
|
+
".pom",
|
|
47
|
+
".jar",
|
|
48
|
+
".aar",
|
|
49
|
+
".war",
|
|
50
|
+
".zip",
|
|
51
|
+
".module",
|
|
52
|
+
".xml",
|
|
53
|
+
".sha1",
|
|
54
|
+
".md5",
|
|
55
|
+
".sha256",
|
|
56
|
+
".sha512",
|
|
57
|
+
".asc",
|
|
58
|
+
".json",
|
|
59
|
+
".toml",
|
|
60
|
+
".klib",
|
|
61
|
+
".tgz",
|
|
62
|
+
".tar.gz",
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
if (knownSuffixes.some((s) => base.endsWith(s))) return true;
|
|
66
|
+
|
|
67
|
+
const ext = path.extname(base);
|
|
68
|
+
if (!ext) return false;
|
|
69
|
+
|
|
70
|
+
// Treat numeric-only extensions (like version segments) as NOT an extension.
|
|
71
|
+
return /[a-zA-Z]/.test(ext);
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
39
77
|
function sanitizeHeaders(headers = {}) {
|
|
40
78
|
const result = { ...headers };
|
|
41
79
|
const blocked = [
|
|
@@ -80,11 +118,7 @@ function sendErrorText(res, statusCode, message, context = "proxy") {
|
|
|
80
118
|
sendText(res, statusCode, message);
|
|
81
119
|
}
|
|
82
120
|
|
|
83
|
-
|
|
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
|
-
}
|
|
121
|
+
// Positive affinity removed: only negative index (404/410) is retained.
|
|
88
122
|
|
|
89
123
|
function buildUrl(req, forcedProtocol = null) {
|
|
90
124
|
const raw = req.url || "/";
|
|
@@ -204,7 +238,7 @@ export function createHttpRequestHandler({
|
|
|
204
238
|
mavenCacheIgnorePathPrefixRules: config.mavenCacheIgnorePathPrefixRules,
|
|
205
239
|
});
|
|
206
240
|
|
|
207
|
-
if (ecosystem === "maven" && mavenAffinityIndex
|
|
241
|
+
if (ecosystem === "maven" && mavenAffinityIndex) {
|
|
208
242
|
canonical = parseMavenReleaseCanonical(urlObj);
|
|
209
243
|
}
|
|
210
244
|
} catch (error) {
|
|
@@ -221,24 +255,21 @@ export function createHttpRequestHandler({
|
|
|
221
255
|
}
|
|
222
256
|
|
|
223
257
|
if (canonical && mavenAffinityIndex) {
|
|
224
|
-
const canUsePositiveAffinity = !config.mavenCacheUseDomainDir && isPositiveAffinityEligible(canonical.fileName);
|
|
225
|
-
|
|
226
|
-
if (canUsePositiveAffinity) {
|
|
227
|
-
const preferredPath = await mavenAffinityIndex.resolvePreferredCachePath(canonical.canonicalKey);
|
|
228
|
-
if (preferredPath) {
|
|
229
|
-
console.log(`[proxy] affinity hit canonical=${canonical.canonicalKey} host=${urlObj.hostname}`);
|
|
230
|
-
await serveFile(res, req, preferredPath, cacheCleanupManager);
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
258
|
if (mavenAffinityIndex.shouldSkipRequest(canonical.canonicalKey, urlObj)) {
|
|
236
|
-
console.log(`[proxy]
|
|
259
|
+
console.log(`[proxy] negative skip canonical=${canonical.canonicalKey} host=${urlObj.hostname}`);
|
|
237
260
|
sendText(res, 404, "Not Found");
|
|
238
261
|
return;
|
|
239
262
|
}
|
|
240
263
|
}
|
|
241
264
|
|
|
265
|
+
// If the requested resource has no file extension, do not cache it.
|
|
266
|
+
// If a file without extension already exists in cache, it was handled above.
|
|
267
|
+
if (!hasFileExtension(urlObj)) {
|
|
268
|
+
console.log(`[proxy] skip caching for extensionless path host=${urlObj.hostname} path=${urlObj.pathname}`);
|
|
269
|
+
forwardDirectRequest(req, res, urlObj, config.downloadTimeoutMs, upstreamProxyManager);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
242
273
|
try {
|
|
243
274
|
console.log(`[proxy] local cache miss host=${urlObj.hostname} path=${urlObj.pathname}`);
|
|
244
275
|
if (cacheCleanupManager) {
|
|
@@ -247,19 +278,13 @@ export function createHttpRequestHandler({
|
|
|
247
278
|
await fs.promises.mkdir(path.dirname(cachePath), { recursive: true });
|
|
248
279
|
await downloader.ensureCached(urlObj, cachePath, req.headers);
|
|
249
280
|
|
|
250
|
-
if (
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
canonicalKey: canonical.canonicalKey,
|
|
258
|
-
host: urlObj.hostname,
|
|
259
|
-
cachePath,
|
|
260
|
-
fileName: canonical.fileName,
|
|
261
|
-
urlObj,
|
|
262
|
-
});
|
|
281
|
+
if (canonical && mavenAffinityIndex) {
|
|
282
|
+
try {
|
|
283
|
+
// Clear any negative entry for this request scope on successful fetch.
|
|
284
|
+
mavenAffinityIndex.clearNegative({ canonicalKey: canonical.canonicalKey, urlObj });
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.error(`[proxy] clearing negative index failed: ${err?.message || err}`);
|
|
287
|
+
}
|
|
263
288
|
}
|
|
264
289
|
|
|
265
290
|
res.setHeader("x-cache", "MISS");
|