com.jimuwd.xian.registry-proxy 1.0.33 → 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.
Files changed (3) hide show
  1. package/dist/index.js +33 -34
  2. package/package.json +1 -1
  3. package/src/index.ts +34 -41
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { homedir } from 'os';
8
8
  import { join, resolve } from 'path';
9
9
  import { URL } from 'url';
10
10
  import { Readable } from "node:stream";
11
+ import { pipeline } from "node:stream/promises";
11
12
  const { readFile, writeFile } = fsPromises;
12
13
  class ConcurrencyLimiter {
13
14
  maxConcurrency;
@@ -163,6 +164,8 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
163
164
  console.log(`Proxying: ${relativePathPrefixedWithSlash}`);
164
165
  // 修改为按顺序尝试注册表,找到第一个成功响应即返回
165
166
  for (const { normalizedRegistryUrl, token } of registryInfos) {
167
+ if (req.destroyed)
168
+ break;
166
169
  await limiter.acquire();
167
170
  let response = null;
168
171
  try {
@@ -204,62 +207,58 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
204
207
  }
205
208
  }
206
209
  else {
210
+ // 二进制流处理
207
211
  if (!response.body) {
208
- console.error(`Empty response body from ${response.url}, status: ${response.status}`);
212
+ console.error(`Empty response body from ${response.url}`);
209
213
  continue;
210
214
  }
211
- // 设置正确的响应头
212
- const contentLength = response.headers.get('Content-Length');
215
+ // 头信息处理
213
216
  const safeHeaders = {
214
217
  'content-type': contentType,
215
- 'connection': 'keep-alive'
218
+ 'connection': 'keep-alive',
216
219
  };
217
- if (contentLength && !isNaN(Number(contentLength))) {
218
- safeHeaders['content-length'] = contentLength;
219
- }
220
- // 复制其他可能有用的头信息
221
- const headersToCopy = ['cache-control', 'etag', 'last-modified'];
222
- headersToCopy.forEach(header => {
220
+ // 复制关键头信息
221
+ ['cache-control', 'etag', 'last-modified', 'content-encoding'].forEach(header => {
223
222
  const value = response?.headers.get(header);
224
- if (value) {
223
+ if (value)
225
224
  safeHeaders[header] = value;
226
- }
227
225
  });
228
226
  res.writeHead(response.status, safeHeaders);
229
- // 使用 pipeline 处理流
230
- const { pipeline } = await import('stream');
231
- const { promisify } = await import('util');
232
- const pipelineAsync = promisify(pipeline);
227
+ // 流转换与传输
228
+ const nodeStream = Readable.fromWeb(response.body);
229
+ let isComplete = false;
230
+ const cleanUp = () => {
231
+ if (!isComplete)
232
+ nodeStream.destroy();
233
+ };
233
234
  try {
234
- // 将 ReadableStream 转换为 Node.js Readable
235
- const nodeStream = Readable.fromWeb(response.body);
236
- // 双向终止保护
237
- req.on('close', () => nodeStream.destroy());
238
- res.on('close', () => nodeStream.destroy());
239
- await pipelineAsync(nodeStream, res);
240
- return; // 成功完成管道传输后返回
235
+ req.on('close', cleanUp);
236
+ res.on('close', cleanUp);
237
+ await pipeline(nodeStream, res);
238
+ isComplete = true;
239
+ return;
241
240
  }
242
- catch (pipeError) {
243
- console.error(`Stream pipeline error for ${relativePathPrefixedWithSlash}:`, pipeError);
244
- if (!res.headersSent) {
245
- res.writeHead(502).end('Stream Error');
246
- }
247
- continue;
241
+ finally {
242
+ req.off('close', cleanUp);
243
+ res.off('close', cleanUp);
248
244
  }
249
245
  }
250
246
  }
251
247
  }
252
248
  catch (e) {
253
- console.error(`Failed to fetch from ${normalizedRegistryUrl}:`, e);
249
+ // 增强错误日志
250
+ if (e instanceof Error) {
251
+ console.error(e.code === 'ECONNREFUSED'
252
+ ? `Registry ${normalizedRegistryUrl} unreachable [ECONNREFUSED]`
253
+ : `Error from ${normalizedRegistryUrl}: ${e.message}`);
254
+ }
254
255
  }
255
256
  finally {
256
- // 不需要手动销毁 ReadableStream
257
257
  limiter.release();
258
258
  }
259
259
  }
260
- // 所有注册表都尝试失败
261
- console.error(`All registries failed for ${relativePathPrefixedWithSlash}`);
262
- res.writeHead(404).end('Not Found - All upstream registries failed');
260
+ // 所有注册表尝试失败
261
+ res.writeHead(404).end('All upstream registries failed');
263
262
  };
264
263
  let server;
265
264
  if (proxyInfo.https) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.jimuwd.xian.registry-proxy",
3
- "version": "1.0.33",
3
+ "version": "1.0.34",
4
4
  "type": "module",
5
5
  "description": "A lightweight npm registry proxy with fallback support",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ import {homedir} from 'os';
9
9
  import {join, resolve} from 'path';
10
10
  import {URL} from 'url';
11
11
  import {Readable} from "node:stream";
12
+ import {pipeline} from "node:stream/promises";
12
13
 
13
14
  const {readFile, writeFile} = fsPromises;
14
15
 
@@ -222,6 +223,7 @@ export async function startProxyServer(
222
223
 
223
224
  // 修改为按顺序尝试注册表,找到第一个成功响应即返回
224
225
  for (const {normalizedRegistryUrl, token} of registryInfos) {
226
+ if (req.destroyed) break;
225
227
  await limiter.acquire();
226
228
  let response: Response | null = null;
227
229
  try {
@@ -262,71 +264,62 @@ export async function startProxyServer(
262
264
  // 继续尝试下一个注册表
263
265
  }
264
266
  } else {
267
+ // 二进制流处理
265
268
  if (!response.body) {
266
- console.error(`Empty response body from ${response.url}, status: ${response.status}`);
269
+ console.error(`Empty response body from ${response.url}`);
267
270
  continue;
268
271
  }
269
272
 
270
- // 设置正确的响应头
271
- const contentLength = response.headers.get('Content-Length');
273
+ // 头信息处理
272
274
  const safeHeaders: OutgoingHttpHeaders = {
273
275
  'content-type': contentType,
274
- 'connection': 'keep-alive'
276
+ 'connection': 'keep-alive',
275
277
  };
276
278
 
277
- if (contentLength && !isNaN(Number(contentLength))) {
278
- safeHeaders['content-length'] = contentLength;
279
- }
280
-
281
- // 复制其他可能有用的头信息
282
- const headersToCopy = ['cache-control', 'etag', 'last-modified'];
283
- headersToCopy.forEach(header => {
279
+ // 复制关键头信息
280
+ ['cache-control', 'etag', 'last-modified', 'content-encoding'].forEach(header => {
284
281
  const value = response?.headers.get(header);
285
- if (value) {
286
- safeHeaders[header] = value;
287
- }
282
+ if (value) safeHeaders[header] = value;
288
283
  });
289
284
 
290
285
  res.writeHead(response.status, safeHeaders);
291
286
 
292
- // 使用 pipeline 处理流
293
- const {pipeline} = await import('stream');
294
- const {promisify} = await import('util');
295
- const pipelineAsync = promisify(pipeline);
287
+ // 流转换与传输
288
+ const nodeStream = Readable.fromWeb(response.body as any);
289
+ let isComplete = false;
290
+ const cleanUp = () => {
291
+ if (!isComplete) nodeStream.destroy();
292
+ };
293
+
296
294
 
297
295
  try {
298
- // 将 ReadableStream 转换为 Node.js Readable
299
- const nodeStream = Readable.fromWeb(response.body as any);
300
-
301
- // 双向终止保护
302
- req.on('close', () => nodeStream.destroy());
303
- res.on('close', () => nodeStream.destroy());
304
-
305
- await pipelineAsync(
306
- nodeStream,
307
- res
308
- );
309
- return; // 成功完成管道传输后返回
310
- } catch (pipeError) {
311
- console.error(`Stream pipeline error for ${relativePathPrefixedWithSlash}:`, pipeError);
312
- if (!res.headersSent) {
313
- res.writeHead(502).end('Stream Error');
314
- }
315
- continue;
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);
316
304
  }
317
305
  }
318
306
  }
319
307
  } catch (e) {
320
- console.error(`Failed to fetch from ${normalizedRegistryUrl}:`, e);
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
+ }
321
316
  } finally {
322
- // 不需要手动销毁 ReadableStream
323
317
  limiter.release();
324
318
  }
325
319
  }
326
320
 
327
- // 所有注册表都尝试失败
328
- console.error(`All registries failed for ${relativePathPrefixedWithSlash}`);
329
- res.writeHead(404).end('Not Found - All upstream registries failed');
321
+ // 所有注册表尝试失败
322
+ res.writeHead(404).end('All upstream registries failed');
330
323
  };
331
324
 
332
325
  let server: HttpServer | HttpsServer;