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.
- package/dist/index.js +33 -34
- package/package.json +1 -1
- 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}
|
|
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
|
-
|
|
218
|
-
|
|
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
|
-
//
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
const
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
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}
|
|
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
|
-
|
|
278
|
-
|
|
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
|
-
//
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
const
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|