dsh-provider-proxy 0.6.0 → 0.6.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 +5 -0
- package/lib/index.js +165 -18
- package/lib/index.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,6 +77,11 @@ Host catalog 是否列出某个 provider(`live`)不影响已保存路由的
|
|
|
77
77
|
因为成功判据在响应头到达时就已成立(流式转发不能靠消费 body 来判断)。
|
|
78
78
|
「最近调用」的时间取请求**发起**时刻;「最近成功 / 失败」的结果与时间来自最近一次**结束**的请求——
|
|
79
79
|
两者分开记录,并发调用下不会把一次请求的发起时间配到另一次的结果上。
|
|
80
|
+
**这条记录只属于当前路由配置**:改代理端点、凭据、`useDefault` 的默认端点、目标域名规则或
|
|
81
|
+
`localDirect` 后,该 provider 的「最近调用」会变成「尚无代理记录」;`testUrl` / `testTarget`
|
|
82
|
+
只影响测试,不会清掉它。仅关闭再打开代理开关也保留记录。修改配置前已经启动的请求不会把旧结果
|
|
83
|
+
写回新配置;删除后重新添加同名 provider 同样视为一条新路由。`hits` 命中计数是历史累计,
|
|
84
|
+
上述配置变化都不会清空它。
|
|
80
85
|
|
|
81
86
|
页面顶部与顶层还有三项整页设置(写在 `cordis.patch.yml`,也可以直接编辑):
|
|
82
87
|
|
package/lib/index.js
CHANGED
|
@@ -1203,6 +1203,28 @@ function deadRuleEntries(raw) {
|
|
|
1203
1203
|
}
|
|
1204
1204
|
|
|
1205
1205
|
// src/proxy-table.ts
|
|
1206
|
+
function normalizedRuleSet(raw) {
|
|
1207
|
+
const rules = (raw ?? "").split(/[,\s]+/).map(normalizeHostRule).filter((entry) => entry.length > 0);
|
|
1208
|
+
return [...new Set(rules)].sort();
|
|
1209
|
+
}
|
|
1210
|
+
function routeFingerprint(input) {
|
|
1211
|
+
return JSON.stringify([
|
|
1212
|
+
input.protocol ?? "http",
|
|
1213
|
+
String(input.host ?? "").trim(),
|
|
1214
|
+
input.port ?? 0,
|
|
1215
|
+
String(input.username ?? "").trim(),
|
|
1216
|
+
input.password ?? "",
|
|
1217
|
+
normalizedRuleSet(input.onlyHosts),
|
|
1218
|
+
normalizedRuleSet(input.bypassHosts),
|
|
1219
|
+
input.localDirect !== false
|
|
1220
|
+
]);
|
|
1221
|
+
}
|
|
1222
|
+
function routeStateDecision(previous, next) {
|
|
1223
|
+
if (previous === void 0) return { token: "new", clearLastUse: false };
|
|
1224
|
+
if (previous.fingerprint !== next.fingerprint) return { token: "new", clearLastUse: true };
|
|
1225
|
+
if (previous.enabled !== next.enabled) return { token: "new", clearLastUse: false };
|
|
1226
|
+
return { token: "keep", clearLastUse: false };
|
|
1227
|
+
}
|
|
1206
1228
|
function proxyUrl(route) {
|
|
1207
1229
|
const credentials = route.username.length === 0 ? "" : route.password.length > 0 && route.protocol !== "socks5" ? `${encodeURIComponent(route.username)}:${encodeURIComponent(route.password)}@` : `${encodeURIComponent(route.username)}@`;
|
|
1208
1230
|
const host = route.host.includes(":") && !route.host.startsWith("[") ? `[${route.host}]` : route.host;
|
|
@@ -1372,12 +1394,16 @@ function apply(ctx, config) {
|
|
|
1372
1394
|
let closed = false;
|
|
1373
1395
|
let webAvailable = false;
|
|
1374
1396
|
const hits = /* @__PURE__ */ new Map();
|
|
1397
|
+
const routeStates = /* @__PURE__ */ new Map();
|
|
1398
|
+
let nextRouteToken = 1;
|
|
1375
1399
|
const lastUse = /* @__PURE__ */ new Map();
|
|
1376
|
-
const bump = (provider) => {
|
|
1400
|
+
const bump = (provider, token) => {
|
|
1377
1401
|
hits.set(provider, (hits.get(provider) ?? 0) + 1);
|
|
1402
|
+
if (routeStates.get(provider)?.token !== token) return;
|
|
1378
1403
|
lastUse.set(provider, { ...lastUse.get(provider), startedAt: Date.now() });
|
|
1379
1404
|
};
|
|
1380
|
-
const settle = (provider) => (ok, detail) => {
|
|
1405
|
+
const settle = (provider, token) => (ok, detail) => {
|
|
1406
|
+
if (routeStates.get(provider)?.token !== token) return;
|
|
1381
1407
|
const previous = lastUse.get(provider);
|
|
1382
1408
|
lastUse.set(provider, {
|
|
1383
1409
|
startedAt: previous?.startedAt ?? Date.now(),
|
|
@@ -1393,7 +1419,60 @@ function apply(ctx, config) {
|
|
|
1393
1419
|
const MAX_REQUEST_BYTES = 8 * 1024;
|
|
1394
1420
|
const selfChecks = /* @__PURE__ */ new Map();
|
|
1395
1421
|
const SELF_CHECK_COOLDOWN_MS = 3e3;
|
|
1422
|
+
const MAX_CHECK_ENTRIES_PER_PROVIDER = 8;
|
|
1423
|
+
const MAX_CHECK_PROVIDERS = 64;
|
|
1396
1424
|
const lastChecks = /* @__PURE__ */ new Map();
|
|
1425
|
+
const checkKeyOf = (provider, key) => key.slice(provider.length);
|
|
1426
|
+
const pruneCheckCache = (now = Date.now()) => {
|
|
1427
|
+
for (const [provider, entries] of lastChecks) {
|
|
1428
|
+
for (const [key, entry] of entries) {
|
|
1429
|
+
if (now - entry.at >= SELF_CHECK_COOLDOWN_MS) entries.delete(key);
|
|
1430
|
+
}
|
|
1431
|
+
if (entries.size === 0) lastChecks.delete(provider);
|
|
1432
|
+
}
|
|
1433
|
+
while (lastChecks.size > MAX_CHECK_PROVIDERS) {
|
|
1434
|
+
let oldestProvider;
|
|
1435
|
+
let oldestAt = Infinity;
|
|
1436
|
+
for (const [provider, entries] of lastChecks) {
|
|
1437
|
+
let latest = -Infinity;
|
|
1438
|
+
for (const entry of entries.values()) latest = Math.max(latest, entry.at);
|
|
1439
|
+
if (latest < oldestAt) {
|
|
1440
|
+
oldestAt = latest;
|
|
1441
|
+
oldestProvider = provider;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (oldestProvider === void 0) break;
|
|
1445
|
+
lastChecks.delete(oldestProvider);
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
const rememberCheck = (provider, key, result) => {
|
|
1449
|
+
pruneCheckCache();
|
|
1450
|
+
let entries = lastChecks.get(provider);
|
|
1451
|
+
if (entries === void 0) {
|
|
1452
|
+
entries = /* @__PURE__ */ new Map();
|
|
1453
|
+
lastChecks.set(provider, entries);
|
|
1454
|
+
}
|
|
1455
|
+
entries.set(checkKeyOf(provider, key), { at: Date.now(), result });
|
|
1456
|
+
while (entries.size > MAX_CHECK_ENTRIES_PER_PROVIDER) {
|
|
1457
|
+
let oldestKey;
|
|
1458
|
+
let oldestAt = Infinity;
|
|
1459
|
+
for (const [candidate, entry] of entries) {
|
|
1460
|
+
if (entry.at < oldestAt) {
|
|
1461
|
+
oldestAt = entry.at;
|
|
1462
|
+
oldestKey = candidate;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
if (oldestKey === void 0) break;
|
|
1466
|
+
entries.delete(oldestKey);
|
|
1467
|
+
}
|
|
1468
|
+
pruneCheckCache();
|
|
1469
|
+
};
|
|
1470
|
+
const forgetChecks = (provider) => {
|
|
1471
|
+
lastChecks.delete(provider);
|
|
1472
|
+
for (const key of [...selfChecks.keys()]) {
|
|
1473
|
+
if (key.startsWith(`${provider}\0`)) selfChecks.delete(key);
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1397
1476
|
const plainTestUrls = () => {
|
|
1398
1477
|
const raw = config?.testUrl;
|
|
1399
1478
|
const snapshot2 = raw?.get;
|
|
@@ -1651,17 +1730,56 @@ function apply(ctx, config) {
|
|
|
1651
1730
|
ctx.logger.info("dsh-provider-proxy mounted (pid %d)", process.pid);
|
|
1652
1731
|
} catch {
|
|
1653
1732
|
}
|
|
1654
|
-
const rebuild =
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
});
|
|
1733
|
+
const rebuild = () => {
|
|
1734
|
+
const providers = plainProviders(config);
|
|
1735
|
+
const defaultProxy = plainDefaultProxy(config);
|
|
1736
|
+
const localDirect = plainLocalDirect(config);
|
|
1737
|
+
const next = createProxyTable(providers, { defaultProxy, localDirect });
|
|
1659
1738
|
const previous = current;
|
|
1660
1739
|
current = next.entries;
|
|
1661
1740
|
problems = next.problems;
|
|
1741
|
+
const configured = new Set(Object.keys(providers));
|
|
1742
|
+
for (const provider of [...routeStates.keys()]) {
|
|
1743
|
+
if (configured.has(provider)) continue;
|
|
1744
|
+
routeStates.delete(provider);
|
|
1745
|
+
lastUse.delete(provider);
|
|
1746
|
+
forgetChecks(provider);
|
|
1747
|
+
}
|
|
1748
|
+
for (const [provider, raw] of Object.entries(providers)) {
|
|
1749
|
+
const effective = raw?.useDefault === true ? { ...raw, ...defaultProxy } : raw;
|
|
1750
|
+
const fingerprint = routeFingerprint({
|
|
1751
|
+
...effective,
|
|
1752
|
+
localDirect
|
|
1753
|
+
});
|
|
1754
|
+
const decision = routeStateDecision(routeStates.get(provider), {
|
|
1755
|
+
fingerprint,
|
|
1756
|
+
enabled: raw?.enabled === true
|
|
1757
|
+
});
|
|
1758
|
+
const token = decision.token === "new" ? nextRouteToken++ : routeStates.get(provider).token;
|
|
1759
|
+
if (decision.clearLastUse) {
|
|
1760
|
+
lastUse.delete(provider);
|
|
1761
|
+
forgetChecks(provider);
|
|
1762
|
+
}
|
|
1763
|
+
routeStates.set(provider, { fingerprint, enabled: raw?.enabled === true, token });
|
|
1764
|
+
}
|
|
1765
|
+
pruneCheckCache();
|
|
1662
1766
|
void closeTable(previous);
|
|
1767
|
+
return Promise.resolve();
|
|
1663
1768
|
};
|
|
1664
|
-
|
|
1769
|
+
let rebuildQueue = Promise.resolve();
|
|
1770
|
+
const enqueueRebuild = () => {
|
|
1771
|
+
if (closed) return Promise.resolve();
|
|
1772
|
+
try {
|
|
1773
|
+
const done = rebuild();
|
|
1774
|
+
rebuildQueue = rebuildQueue.then(() => done).catch(() => void 0);
|
|
1775
|
+
return done;
|
|
1776
|
+
} catch (error) {
|
|
1777
|
+
const failed = Promise.reject(error);
|
|
1778
|
+
rebuildQueue = failed.catch(() => void 0);
|
|
1779
|
+
return failed;
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
enqueueRebuild();
|
|
1665
1783
|
const catalog = () => {
|
|
1666
1784
|
const live = /* @__PURE__ */ new Set();
|
|
1667
1785
|
const out = [];
|
|
@@ -1777,16 +1895,20 @@ function apply(ctx, config) {
|
|
|
1777
1895
|
send(400, { ok: false, kind: "body", error: "\u7F3A\u5C11 provider" });
|
|
1778
1896
|
return;
|
|
1779
1897
|
}
|
|
1780
|
-
const key = selfCheckKey(provider)
|
|
1898
|
+
const key = `${provider}\0${selfCheckKey(provider)}`;
|
|
1781
1899
|
let pending = selfChecks.get(key);
|
|
1782
1900
|
if (pending === void 0) {
|
|
1783
|
-
|
|
1901
|
+
pruneCheckCache();
|
|
1902
|
+
const last = lastChecks.get(provider)?.get(checkKeyOf(provider, key));
|
|
1784
1903
|
if (last !== void 0 && Date.now() - last.at < SELF_CHECK_COOLDOWN_MS) {
|
|
1785
1904
|
send(200, { ...last.result, cached: true });
|
|
1786
1905
|
return;
|
|
1787
1906
|
}
|
|
1907
|
+
const checkToken = routeStates.get(provider)?.token;
|
|
1788
1908
|
pending = runSelfCheck(provider).then((result) => {
|
|
1789
|
-
|
|
1909
|
+
if (checkToken !== void 0 && routeStates.get(provider)?.token === checkToken) {
|
|
1910
|
+
rememberCheck(provider, key, result);
|
|
1911
|
+
}
|
|
1790
1912
|
return result;
|
|
1791
1913
|
});
|
|
1792
1914
|
selfChecks.set(key, pending);
|
|
@@ -1813,11 +1935,13 @@ function apply(ctx, config) {
|
|
|
1813
1935
|
const patched = function(request, signal) {
|
|
1814
1936
|
const route = current.get(WEB_SEARCH_KEY);
|
|
1815
1937
|
if (route === void 0) return original.call(this, request, signal);
|
|
1938
|
+
const token = routeStates.get(WEB_SEARCH_KEY)?.token;
|
|
1939
|
+
if (token === void 0) return original.call(this, request, signal);
|
|
1816
1940
|
return router.run(
|
|
1817
1941
|
{
|
|
1818
1942
|
dispatcher: route.dispatcher,
|
|
1819
|
-
onHit: () => bump(WEB_SEARCH_KEY),
|
|
1820
|
-
onSettle: settle(WEB_SEARCH_KEY),
|
|
1943
|
+
onHit: () => bump(WEB_SEARCH_KEY, token),
|
|
1944
|
+
onSettle: settle(WEB_SEARCH_KEY, token),
|
|
1821
1945
|
decide: route.decide
|
|
1822
1946
|
},
|
|
1823
1947
|
() => original.call(this, request, signal)
|
|
@@ -1837,11 +1961,13 @@ function apply(ctx, config) {
|
|
|
1837
1961
|
if (typeof provider !== "string") return next();
|
|
1838
1962
|
const route = current.get(provider);
|
|
1839
1963
|
if (route === void 0) return next();
|
|
1964
|
+
const token = routeStates.get(provider)?.token;
|
|
1965
|
+
if (token === void 0) return next();
|
|
1840
1966
|
return router.wrap(
|
|
1841
1967
|
{
|
|
1842
1968
|
dispatcher: route.dispatcher,
|
|
1843
|
-
onHit: () => bump(provider),
|
|
1844
|
-
onSettle: settle(provider),
|
|
1969
|
+
onHit: () => bump(provider, token),
|
|
1970
|
+
onSettle: settle(provider, token),
|
|
1845
1971
|
decide: route.decide
|
|
1846
1972
|
},
|
|
1847
1973
|
next
|
|
@@ -1850,14 +1976,14 @@ function apply(ctx, config) {
|
|
|
1850
1976
|
{ global: true }
|
|
1851
1977
|
);
|
|
1852
1978
|
const onConfig = () => {
|
|
1853
|
-
void
|
|
1979
|
+
void enqueueRebuild();
|
|
1854
1980
|
};
|
|
1855
1981
|
ctx.on("loader/volatile-update", onConfig, { global: true });
|
|
1856
1982
|
return async () => {
|
|
1857
1983
|
stop();
|
|
1858
1984
|
if (closed) return;
|
|
1859
1985
|
closed = true;
|
|
1860
|
-
await
|
|
1986
|
+
await rebuildQueue.catch(() => void 0);
|
|
1861
1987
|
await closeTable(current);
|
|
1862
1988
|
current = /* @__PURE__ */ new Map();
|
|
1863
1989
|
router.release();
|
|
@@ -1872,7 +1998,26 @@ function apply(ctx, config) {
|
|
|
1872
1998
|
})),
|
|
1873
1999
|
problems: Object.fromEntries(problems),
|
|
1874
2000
|
hits: Object.fromEntries(hits),
|
|
1875
|
-
lastUse: Object.fromEntries(lastUse)
|
|
2001
|
+
lastUse: Object.fromEntries(lastUse),
|
|
2002
|
+
selfCheckCache: (() => {
|
|
2003
|
+
let expiredEntries = 0;
|
|
2004
|
+
let maxPerProvider = 0;
|
|
2005
|
+
let totalEntries = 0;
|
|
2006
|
+
for (const entries of lastChecks.values()) {
|
|
2007
|
+
totalEntries += entries.size;
|
|
2008
|
+
maxPerProvider = Math.max(maxPerProvider, entries.size);
|
|
2009
|
+
for (const entry of entries.values()) {
|
|
2010
|
+
if (Date.now() - entry.at >= SELF_CHECK_COOLDOWN_MS) expiredEntries += 1;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
return {
|
|
2014
|
+
providers: lastChecks.size,
|
|
2015
|
+
pending: selfChecks.size,
|
|
2016
|
+
expiredEntries,
|
|
2017
|
+
totalEntries,
|
|
2018
|
+
maxPerProvider
|
|
2019
|
+
};
|
|
2020
|
+
})()
|
|
1876
2021
|
})
|
|
1877
2022
|
});
|
|
1878
2023
|
}
|
|
@@ -1897,6 +2042,8 @@ export {
|
|
|
1897
2042
|
parseHostList,
|
|
1898
2043
|
proxyHostProblem,
|
|
1899
2044
|
proxyUrl,
|
|
2045
|
+
routeFingerprint,
|
|
2046
|
+
routeStateDecision,
|
|
1900
2047
|
splitHostRules
|
|
1901
2048
|
};
|
|
1902
2049
|
//# sourceMappingURL=index.js.map
|