com.jimuwd.xian.registry-proxy 1.0.32 → 1.0.34
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/index.js +45 -19
- package/package.json +1 -1
- package/src/index.ts +49 -18
package/dist/index.js
CHANGED
|
@@ -7,6 +7,8 @@ import fetch from 'node-fetch';
|
|
|
7
7
|
import { homedir } from 'os';
|
|
8
8
|
import { join, resolve } from 'path';
|
|
9
9
|
import { URL } from 'url';
|
|
10
|
+
import { Readable } from "node:stream";
|
|
11
|
+
import { pipeline } from "node:stream/promises";
|
|
10
12
|
const { readFile, writeFile } = fsPromises;
|
|
11
13
|
class ConcurrencyLimiter {
|
|
12
14
|
maxConcurrency;
|
|
@@ -162,12 +164,15 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
162
164
|
console.log(`Proxying: ${relativePathPrefixedWithSlash}`);
|
|
163
165
|
// 修改为按顺序尝试注册表,找到第一个成功响应即返回
|
|
164
166
|
for (const { normalizedRegistryUrl, token } of registryInfos) {
|
|
167
|
+
if (req.destroyed)
|
|
168
|
+
break;
|
|
165
169
|
await limiter.acquire();
|
|
170
|
+
let response = null;
|
|
166
171
|
try {
|
|
167
172
|
const targetUrl = `${normalizedRegistryUrl}${relativePathPrefixedWithSlash}${fullUrl.search || ''}`;
|
|
168
173
|
console.log(`Fetching from: ${targetUrl}`);
|
|
169
174
|
const headers = token ? { Authorization: `Bearer ${token}` } : undefined;
|
|
170
|
-
|
|
175
|
+
response = await fetch(targetUrl, { headers });
|
|
171
176
|
console.log(`Response from ${targetUrl}: ${response.status} ${response.statusText}`);
|
|
172
177
|
if (response.ok) {
|
|
173
178
|
const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
|
|
@@ -202,37 +207,58 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
202
207
|
}
|
|
203
208
|
}
|
|
204
209
|
else {
|
|
205
|
-
//
|
|
210
|
+
// 二进制流处理
|
|
206
211
|
if (!response.body) {
|
|
207
|
-
console.error(`Empty response body from ${response.url}
|
|
208
|
-
// 继续尝试下一个注册表
|
|
212
|
+
console.error(`Empty response body from ${response.url}`);
|
|
209
213
|
continue;
|
|
210
214
|
}
|
|
211
|
-
|
|
212
|
-
const safeHeaders = {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
215
|
+
// 头信息处理
|
|
216
|
+
const safeHeaders = {
|
|
217
|
+
'content-type': contentType,
|
|
218
|
+
'connection': 'keep-alive',
|
|
219
|
+
};
|
|
220
|
+
// 复制关键头信息
|
|
221
|
+
['cache-control', 'etag', 'last-modified', 'content-encoding'].forEach(header => {
|
|
222
|
+
const value = response?.headers.get(header);
|
|
223
|
+
if (value)
|
|
224
|
+
safeHeaders[header] = value;
|
|
220
225
|
});
|
|
221
|
-
|
|
226
|
+
res.writeHead(response.status, safeHeaders);
|
|
227
|
+
// 流转换与传输
|
|
228
|
+
const nodeStream = Readable.fromWeb(response.body);
|
|
229
|
+
let isComplete = false;
|
|
230
|
+
const cleanUp = () => {
|
|
231
|
+
if (!isComplete)
|
|
232
|
+
nodeStream.destroy();
|
|
233
|
+
};
|
|
234
|
+
try {
|
|
235
|
+
req.on('close', cleanUp);
|
|
236
|
+
res.on('close', cleanUp);
|
|
237
|
+
await pipeline(nodeStream, res);
|
|
238
|
+
isComplete = true;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
req.off('close', cleanUp);
|
|
243
|
+
res.off('close', cleanUp);
|
|
244
|
+
}
|
|
222
245
|
}
|
|
223
246
|
}
|
|
224
247
|
}
|
|
225
248
|
catch (e) {
|
|
226
|
-
|
|
227
|
-
|
|
249
|
+
// 增强错误日志
|
|
250
|
+
if (e instanceof Error) {
|
|
251
|
+
console.error(e.code === 'ECONNREFUSED'
|
|
252
|
+
? `Registry ${normalizedRegistryUrl} unreachable [ECONNREFUSED]`
|
|
253
|
+
: `Error from ${normalizedRegistryUrl}: ${e.message}`);
|
|
254
|
+
}
|
|
228
255
|
}
|
|
229
256
|
finally {
|
|
230
257
|
limiter.release();
|
|
231
258
|
}
|
|
232
259
|
}
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
res.writeHead(404).end('Not Found - All upstream registries failed');
|
|
260
|
+
// 所有注册表尝试失败
|
|
261
|
+
res.writeHead(404).end('All upstream registries failed');
|
|
236
262
|
};
|
|
237
263
|
let server;
|
|
238
264
|
if (proxyInfo.https) {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -8,6 +8,8 @@ import fetch, {Response} from 'node-fetch';
|
|
|
8
8
|
import {homedir} from 'os';
|
|
9
9
|
import {join, resolve} from 'path';
|
|
10
10
|
import {URL} from 'url';
|
|
11
|
+
import {Readable} from "node:stream";
|
|
12
|
+
import {pipeline} from "node:stream/promises";
|
|
11
13
|
|
|
12
14
|
const {readFile, writeFile} = fsPromises;
|
|
13
15
|
|
|
@@ -221,12 +223,14 @@ export async function startProxyServer(
|
|
|
221
223
|
|
|
222
224
|
// 修改为按顺序尝试注册表,找到第一个成功响应即返回
|
|
223
225
|
for (const {normalizedRegistryUrl, token} of registryInfos) {
|
|
226
|
+
if (req.destroyed) break;
|
|
224
227
|
await limiter.acquire();
|
|
228
|
+
let response: Response | null = null;
|
|
225
229
|
try {
|
|
226
230
|
const targetUrl = `${normalizedRegistryUrl}${relativePathPrefixedWithSlash}${fullUrl.search || ''}`;
|
|
227
231
|
console.log(`Fetching from: ${targetUrl}`);
|
|
228
232
|
const headers = token ? {Authorization: `Bearer ${token}`} : undefined;
|
|
229
|
-
|
|
233
|
+
response = await fetch(targetUrl, {headers});
|
|
230
234
|
console.log(`Response from ${targetUrl}: ${response.status} ${response.statusText}`);
|
|
231
235
|
|
|
232
236
|
if (response.ok) {
|
|
@@ -260,35 +264,62 @@ export async function startProxyServer(
|
|
|
260
264
|
// 继续尝试下一个注册表
|
|
261
265
|
}
|
|
262
266
|
} else {
|
|
263
|
-
//
|
|
267
|
+
// 二进制流处理
|
|
264
268
|
if (!response.body) {
|
|
265
|
-
console.error(`Empty response body from ${response.url}
|
|
266
|
-
// 继续尝试下一个注册表
|
|
269
|
+
console.error(`Empty response body from ${response.url}`);
|
|
267
270
|
continue;
|
|
268
271
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
safeHeaders
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
272
|
+
|
|
273
|
+
// 头信息处理
|
|
274
|
+
const safeHeaders: OutgoingHttpHeaders = {
|
|
275
|
+
'content-type': contentType,
|
|
276
|
+
'connection': 'keep-alive',
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// 复制关键头信息
|
|
280
|
+
['cache-control', 'etag', 'last-modified', 'content-encoding'].forEach(header => {
|
|
281
|
+
const value = response?.headers.get(header);
|
|
282
|
+
if (value) safeHeaders[header] = value;
|
|
277
283
|
});
|
|
278
|
-
|
|
284
|
+
|
|
285
|
+
res.writeHead(response.status, safeHeaders);
|
|
286
|
+
|
|
287
|
+
// 流转换与传输
|
|
288
|
+
const nodeStream = Readable.fromWeb(response.body as any);
|
|
289
|
+
let isComplete = false;
|
|
290
|
+
const cleanUp = () => {
|
|
291
|
+
if (!isComplete) nodeStream.destroy();
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
req.on('close', cleanUp);
|
|
297
|
+
res.on('close', cleanUp);
|
|
298
|
+
await pipeline(nodeStream, res);
|
|
299
|
+
isComplete = true;
|
|
300
|
+
return;
|
|
301
|
+
} finally {
|
|
302
|
+
req.off('close', cleanUp);
|
|
303
|
+
res.off('close', cleanUp);
|
|
304
|
+
}
|
|
279
305
|
}
|
|
280
306
|
}
|
|
281
307
|
} catch (e) {
|
|
282
|
-
|
|
283
|
-
|
|
308
|
+
// 增强错误日志
|
|
309
|
+
if (e instanceof Error) {
|
|
310
|
+
console.error(
|
|
311
|
+
(e as any).code === 'ECONNREFUSED'
|
|
312
|
+
? `Registry ${normalizedRegistryUrl} unreachable [ECONNREFUSED]`
|
|
313
|
+
: `Error from ${normalizedRegistryUrl}: ${e.message}`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
284
316
|
} finally {
|
|
285
317
|
limiter.release();
|
|
286
318
|
}
|
|
287
319
|
}
|
|
288
320
|
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
res.writeHead(404).end('Not Found - All upstream registries failed');
|
|
321
|
+
// 所有注册表尝试失败
|
|
322
|
+
res.writeHead(404).end('All upstream registries failed');
|
|
292
323
|
};
|
|
293
324
|
|
|
294
325
|
let server: HttpServer | HttpsServer;
|