com.jimuwd.xian.registry-proxy 1.1.17 → 1.1.18
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/dist/server/index.js
CHANGED
|
@@ -13,7 +13,8 @@ import { gracefulShutdown, registerProcessShutdownHook } from "./gracefullShutdo
|
|
|
13
13
|
import { writePortFile } from "../port.js";
|
|
14
14
|
import resolveEnvValue from "../utils/resolveEnvValue.js";
|
|
15
15
|
const { readFile } = fsPromises;
|
|
16
|
-
|
|
16
|
+
// 整个registry-proxy server实例 使用的全局限流器
|
|
17
|
+
const LIMITER = new ConcurrencyLimiter(5);
|
|
17
18
|
function removeEndingSlashAndForceStartingSlash(str) {
|
|
18
19
|
if (!str)
|
|
19
20
|
return '/';
|
|
@@ -137,8 +138,12 @@ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYar
|
|
|
137
138
|
const basePath = removeEndingSlashAndForceStartingSlash(proxyConfig.basePath);
|
|
138
139
|
return { registries, https, basePath };
|
|
139
140
|
}
|
|
141
|
+
// 有并发限流控制,禁止嵌套调用,否则导致死锁
|
|
140
142
|
async function fetchFromRegistry(registry, targetUrl, reqFromDownstreamClient, limiter) {
|
|
141
|
-
|
|
143
|
+
// 并发限流控制
|
|
144
|
+
return limiter.run(() => _fetchFromRegistry(registry, targetUrl, reqFromDownstreamClient));
|
|
145
|
+
}
|
|
146
|
+
async function _fetchFromRegistry(registry, targetUrl, reqFromDownstreamClient) {
|
|
142
147
|
try {
|
|
143
148
|
logger.info(`Fetching from upstream: ${targetUrl}`);
|
|
144
149
|
const headersFromDownstreamClient = reqFromDownstreamClient.headers;
|
|
@@ -158,10 +163,10 @@ async function fetchFromRegistry(registry, targetUrl, reqFromDownstreamClient, l
|
|
|
158
163
|
content-type=${response.headers.get('content-type')} content-encoding=${response.headers.get('content-encoding')} content-length=${response.headers.get('content-length')} transfer-encoding=${response.headers.get('transfer-encoding')}`);
|
|
159
164
|
return response;
|
|
160
165
|
}
|
|
161
|
-
else if (response.status == 301) {
|
|
162
|
-
// HTTP 301 Permanently Moved
|
|
166
|
+
else if (response.status == 301 || response.status == 308) {
|
|
167
|
+
// HTTP 301 Permanently Moved / HTTP 308 Permanent Redirect
|
|
163
168
|
logger.info(`${response.status} ${response.statusText} response from upstream ${targetUrl}, moved to location=${response.headers.get('location')}`);
|
|
164
|
-
// 对于301永久转义响应,registry-proxy 的行为:透传给下游客户端,让客户端自行跳转(提示:这个跳转后的请求将不再走registry-proxy代理了)
|
|
169
|
+
// 对于301/308永久转义响应,registry-proxy 的行为:透传给下游客户端,让客户端自行跳转(提示:这个跳转后的请求将不再走registry-proxy代理了)
|
|
165
170
|
return response;
|
|
166
171
|
}
|
|
167
172
|
else if (response.status == 302 || response.status == 303 || response.status == 307) {
|
|
@@ -174,7 +179,7 @@ async function fetchFromRegistry(registry, targetUrl, reqFromDownstreamClient, l
|
|
|
174
179
|
if (redirectedLocation) {
|
|
175
180
|
logger.info(`${response.status} ${response.statusText} response from upstream ${targetUrl}
|
|
176
181
|
Fetching from redirected location=${redirectedLocation}`);
|
|
177
|
-
return await
|
|
182
|
+
return await _fetchFromRegistry(registry, redirectedLocation, reqFromDownstreamClient);
|
|
178
183
|
}
|
|
179
184
|
else {
|
|
180
185
|
logger.warn(`${response.status} ${response.statusText} response from upstream ${targetUrl}, but redirect location is empty, skip fetching from ${targetUrl}`);
|
|
@@ -213,9 +218,6 @@ async function fetchFromRegistry(registry, targetUrl, reqFromDownstreamClient, l
|
|
|
213
218
|
// Return null means skipping current upstream registry.
|
|
214
219
|
return null;
|
|
215
220
|
}
|
|
216
|
-
finally {
|
|
217
|
-
limiter.release();
|
|
218
|
-
}
|
|
219
221
|
}
|
|
220
222
|
async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDownstreamClient, upstreamResponse, reqFromDownstreamClient, proxyInfo, _proxyPort, registryInfos) {
|
|
221
223
|
logger.debug(() => `Writing upstream registry server ${registryInfo.normalizedRegistryUrl}'s ${upstreamResponse.status}${upstreamResponse.statusText ? (' "' + upstreamResponse.statusText + '"') : ''} response to downstream client.`);
|
|
@@ -366,14 +368,14 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
366
368
|
logger.info('Active registries:', registryInfos.map(r => r.normalizedRegistryUrl));
|
|
367
369
|
logger.info('Proxy base path:', basePathPrefixedWithSlash);
|
|
368
370
|
logger.info('HTTPS:', !!proxyInfo.https);
|
|
371
|
+
logger.info(`Proxy server request handler rate limit is ${LIMITER.maxConcurrency}`);
|
|
369
372
|
const requestHandler = async (reqFromDownstreamClient, resToDownstreamClient) => {
|
|
370
373
|
const downstreamUserAgent = reqFromDownstreamClient.headers["user-agent"]; // "curl/x.x.x"
|
|
371
374
|
const downstreamIp = getDownstreamClientIp(reqFromDownstreamClient);
|
|
372
375
|
const downstreamRequestedHttpMethod = reqFromDownstreamClient.method; // "GET", "POST", etc.
|
|
373
376
|
const downstreamRequestedHost = reqFromDownstreamClient.headers.host; // "example.com:8080"
|
|
374
377
|
const downstreamRequestedFullPath = reqFromDownstreamClient.url; // "/some/path?param=1¶m=2"
|
|
375
|
-
logger.info(`Received downstream request from '${downstreamUserAgent}' ${downstreamIp} ${downstreamRequestedHttpMethod} ${downstreamRequestedHost} ${downstreamRequestedFullPath}
|
|
376
|
-
Proxy server request handler rate limit is ${limiter.maxConcurrency}`);
|
|
378
|
+
logger.info(`Received downstream request from '${downstreamUserAgent}' ${downstreamIp} ${downstreamRequestedHttpMethod} ${downstreamRequestedHost} ${downstreamRequestedFullPath}`);
|
|
377
379
|
if (!downstreamRequestedFullPath || !downstreamRequestedHost) {
|
|
378
380
|
logger.warn(`400 Invalid Request, downstream ${downstreamUserAgent} req.url is absent or downstream.headers.host is absent.`);
|
|
379
381
|
resToDownstreamClient.writeHead(400).end('Invalid Request');
|
|
@@ -403,7 +405,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
403
405
|
logger.warn(`Downstream ${reqFromDownstreamClient.headers["user-agent"]} request is destroyed, no need to proxy request to upstream ${targetUrl} any more.`);
|
|
404
406
|
return;
|
|
405
407
|
}
|
|
406
|
-
const okResponseOrNull = await fetchFromRegistry(targetRegistry, targetUrl, reqFromDownstreamClient,
|
|
408
|
+
const okResponseOrNull = await fetchFromRegistry(targetRegistry, targetUrl, reqFromDownstreamClient, LIMITER);
|
|
407
409
|
if (okResponseOrNull) {
|
|
408
410
|
successfulResponseFromUpstream = okResponseOrNull;
|
|
409
411
|
break;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 并发控制器
|
|
3
|
+
* @note 不支持重入,重入可能导致死锁
|
|
4
|
+
*/
|
|
5
|
+
export default class ConcurrencyLimiter {
|
|
2
6
|
readonly maxConcurrency: number;
|
|
3
7
|
private current;
|
|
4
8
|
private queue;
|
|
5
|
-
private executionStack;
|
|
6
9
|
constructor(maxConcurrency: number);
|
|
7
|
-
private getContextId;
|
|
8
10
|
acquire(): Promise<void>;
|
|
9
11
|
release(): void;
|
|
10
12
|
run<T>(task: () => Promise<T>): Promise<T>;
|
|
@@ -1,51 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 并发控制器
|
|
3
|
+
* @note 不支持重入,重入可能导致死锁
|
|
4
|
+
*/
|
|
5
|
+
export default class ConcurrencyLimiter {
|
|
2
6
|
maxConcurrency;
|
|
3
7
|
current = 0;
|
|
4
8
|
queue = [];
|
|
5
|
-
executionStack = [];
|
|
6
9
|
constructor(maxConcurrency) {
|
|
7
10
|
if (maxConcurrency <= 0) {
|
|
8
11
|
throw new Error("maxConcurrency must be positive");
|
|
9
12
|
}
|
|
10
13
|
this.maxConcurrency = maxConcurrency;
|
|
11
14
|
}
|
|
12
|
-
getContextId() {
|
|
13
|
-
return Symbol('context');
|
|
14
|
-
}
|
|
15
15
|
async acquire() {
|
|
16
|
-
const contextId = this.getContextId();
|
|
17
|
-
const existingContext = this.executionStack.find(c => c.contextId === contextId);
|
|
18
|
-
if (existingContext) {
|
|
19
|
-
existingContext.depth++;
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
16
|
if (this.current < this.maxConcurrency) {
|
|
23
17
|
this.current++;
|
|
24
|
-
this.executionStack.push({ contextId, depth: 1 });
|
|
25
18
|
return;
|
|
26
19
|
}
|
|
27
20
|
return new Promise((resolve) => {
|
|
28
|
-
this.queue.push(
|
|
29
|
-
this.current++;
|
|
30
|
-
this.executionStack.push({ contextId, depth: 1 });
|
|
31
|
-
resolve();
|
|
32
|
-
});
|
|
21
|
+
this.queue.push(resolve);
|
|
33
22
|
});
|
|
34
23
|
}
|
|
35
24
|
release() {
|
|
36
|
-
if (this.
|
|
25
|
+
if (this.current <= 0) {
|
|
37
26
|
throw new Error("release() called without acquire()");
|
|
38
27
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
next();
|
|
48
|
-
}
|
|
28
|
+
this.current--;
|
|
29
|
+
const next = this.queue.shift();
|
|
30
|
+
if (next) {
|
|
31
|
+
// 异步执行,避免递归调用栈溢出
|
|
32
|
+
Promise.resolve().then(() => {
|
|
33
|
+
this.current++;
|
|
34
|
+
next();
|
|
35
|
+
});
|
|
49
36
|
}
|
|
50
37
|
}
|
|
51
38
|
async run(task) {
|