com.jimuwd.xian.registry-proxy 1.1.17 → 1.1.19
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}`);
|
|
@@ -194,28 +199,23 @@ async function fetchFromRegistry(registry, targetUrl, reqFromDownstreamClient, l
|
|
|
194
199
|
}
|
|
195
200
|
catch (e) {
|
|
196
201
|
// Fetch form one of the configured upstream registries failed, this is expected behavior, not error.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// Other net error code, print log with stacktrace
|
|
207
|
-
logger.warn(`Failed to fetch from ${targetUrl}, ${e.message}`, e);
|
|
208
|
-
}
|
|
202
|
+
const errCode = e?.code;
|
|
203
|
+
if (errCode === 'ECONNREFUSED') {
|
|
204
|
+
logger.info(`Upstream ${targetUrl} refused connection [ECONNREFUSED], skip fetching from registry ${registry.normalizedRegistryUrl}`);
|
|
205
|
+
}
|
|
206
|
+
else if (errCode === 'ENOTFOUND') {
|
|
207
|
+
logger.info(`Unknown hostname in upstream url ${targetUrl} [ENOTFOUND], skip fetching from registry ${registry.normalizedRegistryUrl}.`);
|
|
208
|
+
}
|
|
209
|
+
else if (errCode === 'EAI_AGAIN') {
|
|
210
|
+
logger.info(`Could not resolve hostname in upstream url ${targetUrl} [EAI_AGAIN], skip fetching from registry ${registry.normalizedRegistryUrl}.`);
|
|
209
211
|
}
|
|
210
212
|
else {
|
|
211
|
-
|
|
213
|
+
// Other net error code, print log with stacktrace
|
|
214
|
+
logger.warn(`Failed to fetch from ${targetUrl}`, e);
|
|
212
215
|
}
|
|
213
216
|
// Return null means skipping current upstream registry.
|
|
214
217
|
return null;
|
|
215
218
|
}
|
|
216
|
-
finally {
|
|
217
|
-
limiter.release();
|
|
218
|
-
}
|
|
219
219
|
}
|
|
220
220
|
async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDownstreamClient, upstreamResponse, reqFromDownstreamClient, proxyInfo, _proxyPort, registryInfos) {
|
|
221
221
|
logger.debug(() => `Writing upstream registry server ${registryInfo.normalizedRegistryUrl}'s ${upstreamResponse.status}${upstreamResponse.statusText ? (' "' + upstreamResponse.statusText + '"') : ''} response to downstream client.`);
|
|
@@ -366,14 +366,14 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
366
366
|
logger.info('Active registries:', registryInfos.map(r => r.normalizedRegistryUrl));
|
|
367
367
|
logger.info('Proxy base path:', basePathPrefixedWithSlash);
|
|
368
368
|
logger.info('HTTPS:', !!proxyInfo.https);
|
|
369
|
+
logger.info(`Proxy server request handler rate limit is ${LIMITER.maxConcurrency}`);
|
|
369
370
|
const requestHandler = async (reqFromDownstreamClient, resToDownstreamClient) => {
|
|
370
371
|
const downstreamUserAgent = reqFromDownstreamClient.headers["user-agent"]; // "curl/x.x.x"
|
|
371
372
|
const downstreamIp = getDownstreamClientIp(reqFromDownstreamClient);
|
|
372
373
|
const downstreamRequestedHttpMethod = reqFromDownstreamClient.method; // "GET", "POST", etc.
|
|
373
374
|
const downstreamRequestedHost = reqFromDownstreamClient.headers.host; // "example.com:8080"
|
|
374
375
|
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}`);
|
|
376
|
+
logger.info(`Received downstream request from '${downstreamUserAgent}' ${downstreamIp} ${downstreamRequestedHttpMethod} ${downstreamRequestedHost} ${downstreamRequestedFullPath}`);
|
|
377
377
|
if (!downstreamRequestedFullPath || !downstreamRequestedHost) {
|
|
378
378
|
logger.warn(`400 Invalid Request, downstream ${downstreamUserAgent} req.url is absent or downstream.headers.host is absent.`);
|
|
379
379
|
resToDownstreamClient.writeHead(400).end('Invalid Request');
|
|
@@ -403,7 +403,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
403
403
|
logger.warn(`Downstream ${reqFromDownstreamClient.headers["user-agent"]} request is destroyed, no need to proxy request to upstream ${targetUrl} any more.`);
|
|
404
404
|
return;
|
|
405
405
|
}
|
|
406
|
-
const okResponseOrNull = await fetchFromRegistry(targetRegistry, targetUrl, reqFromDownstreamClient,
|
|
406
|
+
const okResponseOrNull = await fetchFromRegistry(targetRegistry, targetUrl, reqFromDownstreamClient, LIMITER);
|
|
407
407
|
if (okResponseOrNull) {
|
|
408
408
|
successfulResponseFromUpstream = okResponseOrNull;
|
|
409
409
|
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) {
|