com.jimuwd.xian.registry-proxy 1.0.97 → 1.0.99
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
CHANGED
|
@@ -8,32 +8,8 @@ import { homedir } from 'os';
|
|
|
8
8
|
import { join, resolve } from 'path';
|
|
9
9
|
import { URL } from 'url';
|
|
10
10
|
import logger from "./utils/logger.js";
|
|
11
|
+
import ConcurrencyLimiter from "./utils/ConcurrencyLimiter.js";
|
|
11
12
|
const { readFile, writeFile } = fsPromises;
|
|
12
|
-
class ConcurrencyLimiter {
|
|
13
|
-
maxConcurrency;
|
|
14
|
-
current = 0;
|
|
15
|
-
queue = [];
|
|
16
|
-
constructor(maxConcurrency) {
|
|
17
|
-
this.maxConcurrency = maxConcurrency;
|
|
18
|
-
}
|
|
19
|
-
async acquire() {
|
|
20
|
-
if (this.current < this.maxConcurrency) {
|
|
21
|
-
this.current++;
|
|
22
|
-
return Promise.resolve();
|
|
23
|
-
}
|
|
24
|
-
return new Promise((resolve) => {
|
|
25
|
-
this.queue.push(resolve);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
release() {
|
|
29
|
-
this.current--;
|
|
30
|
-
const next = this.queue.shift();
|
|
31
|
-
if (next) {
|
|
32
|
-
this.current++;
|
|
33
|
-
next();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
13
|
const limiter = new ConcurrencyLimiter(Infinity);
|
|
38
14
|
function removeEndingSlashAndForceStartingSlash(str) {
|
|
39
15
|
if (!str)
|
|
@@ -208,33 +184,34 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
|
|
|
208
184
|
// 默认是 connection: keep-alive 和 keep-alive: timeout=5,这里直接给它咔嚓掉
|
|
209
185
|
resToDownstreamClient.setHeader('Connection', 'close');
|
|
210
186
|
resToDownstreamClient.removeHeader('Keep-Alive');
|
|
211
|
-
resToDownstreamClient.setHeader('content-type',
|
|
187
|
+
resToDownstreamClient.setHeader('content-type', contentType);
|
|
212
188
|
resToDownstreamClient.setHeader('content-length', Buffer.byteLength(bodyData));
|
|
213
189
|
logger.info(`Response to downstream client headers`, JSON.stringify(resToDownstreamClient.getHeaders()), targetUrl);
|
|
214
190
|
resToDownstreamClient.writeHead(upstreamResponse.status).end(bodyData);
|
|
215
191
|
}
|
|
216
192
|
else if (contentType.includes('application/octet-stream')) { // 二进制流处理
|
|
217
193
|
logger.info("Write application/octet-stream response from upstream to downstream", targetUrl);
|
|
218
|
-
// 准备通用响应头信息
|
|
219
|
-
const safeHeaders = {};
|
|
220
|
-
// 复制所有可能需要的头信息(不包含安全相关的敏感头信息,如access-control-allow-origin、set-cookie、server、strict-transport-security等,这意味着代理服务器向下游客户端屏蔽了这些认证等安全数据)
|
|
221
|
-
// 也不能包含cf-cache-status、cf-ray(Cloudflare 特有字段)可能干扰客户端解析。
|
|
222
|
-
const headersToCopy = ['cache-control', 'connection', 'content-type', 'content-encoding', 'content-length', 'date', 'etag', 'last-modified', 'transfer-encoding', 'vary',];
|
|
223
|
-
headersToCopy.forEach(header => {
|
|
224
|
-
const value = upstreamResponse.headers.get(header);
|
|
225
|
-
if (value)
|
|
226
|
-
safeHeaders[header] = value;
|
|
227
|
-
});
|
|
228
|
-
if (!safeHeaders['content-type'])
|
|
229
|
-
safeHeaders['content-type'] = 'application/octet-stream';
|
|
230
194
|
if (!upstreamResponse.body) {
|
|
231
195
|
logger.error(`Empty response body from upstream ${targetUrl}`);
|
|
232
196
|
resToDownstreamClient.writeHead(502).end('Empty Upstream Response');
|
|
233
197
|
}
|
|
234
198
|
else {
|
|
235
199
|
// write back to client
|
|
200
|
+
// 准备通用响应头信息
|
|
201
|
+
const safeHeaders = { 'content-type': contentType };
|
|
202
|
+
// 复制所有可能需要的头信息(不包含安全相关的敏感头信息,如access-control-allow-origin、set-cookie、server、strict-transport-security等,这意味着代理服务器向下游客户端屏蔽了这些认证等安全数据)
|
|
203
|
+
// 也不能包含cf-cache-status、cf-ray(Cloudflare 特有字段)可能干扰客户端解析。
|
|
204
|
+
const headersToCopy = ['cache-control', 'connection', 'content-encoding', 'content-length', 'date', 'etag', 'last-modified', 'transfer-encoding', 'vary',];
|
|
205
|
+
headersToCopy.forEach(header => {
|
|
206
|
+
const value = upstreamResponse.headers.get(header);
|
|
207
|
+
if (value)
|
|
208
|
+
safeHeaders[header] = value;
|
|
209
|
+
});
|
|
210
|
+
// 必须使用 ServerResponse.setHeaders(safeHeaders)来覆盖现有headers而不是ServerResponse.writeHead(status,headers)来合并headers!
|
|
211
|
+
// 这个坑害我浪费很久事件来调试!
|
|
212
|
+
resToDownstreamClient.setHeaders(safeHeaders);
|
|
236
213
|
logger.info(`Response to downstream client headers`, JSON.stringify(safeHeaders), targetUrl);
|
|
237
|
-
resToDownstreamClient.writeHead(upstreamResponse.status
|
|
214
|
+
resToDownstreamClient.writeHead(upstreamResponse.status);
|
|
238
215
|
// stop pipe when req from client is closed accidentally.
|
|
239
216
|
const cleanup = () => {
|
|
240
217
|
reqFromDownstreamClient.off('close', cleanup);
|
|
@@ -274,9 +251,14 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
|
|
|
274
251
|
else {
|
|
275
252
|
logger.warn(`Write unsupported content-type=${contentType} response from upstream to downstream ${targetUrl}`);
|
|
276
253
|
const bodyData = await upstreamResponse.text();
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
resToDownstreamClient.
|
|
254
|
+
resToDownstreamClient.removeHeader('Transfer-Encoding');
|
|
255
|
+
// 默认是 connection: keep-alive 和 keep-alive: timeout=5,这里直接给它咔嚓掉
|
|
256
|
+
resToDownstreamClient.setHeader('Connection', 'close');
|
|
257
|
+
resToDownstreamClient.removeHeader('Keep-Alive');
|
|
258
|
+
resToDownstreamClient.setHeader('content-type', contentType);
|
|
259
|
+
resToDownstreamClient.setHeader('content-length', Buffer.byteLength(bodyData));
|
|
260
|
+
logger.info(`Response to downstream client headers`, JSON.stringify(resToDownstreamClient.getHeaders()), targetUrl);
|
|
261
|
+
resToDownstreamClient.writeHead(upstreamResponse.status).end(bodyData);
|
|
280
262
|
}
|
|
281
263
|
}
|
|
282
264
|
catch (err) {
|
|
@@ -309,7 +291,8 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
309
291
|
const downstreamRequestedHttpMethod = reqFromDownstreamClient.method; // "GET", "POST", etc.
|
|
310
292
|
const downstreamRequestedHost = reqFromDownstreamClient.headers.host; // "example.com:8080"
|
|
311
293
|
const downstreamRequestedFullPath = reqFromDownstreamClient.url; // "/some/path?param=1¶m=2"
|
|
312
|
-
logger.info(`Received downstream request ${downstreamUserAgent} ${downstreamIp} ${downstreamRequestedHttpMethod} ${downstreamRequestedHost} ${downstreamRequestedFullPath}
|
|
294
|
+
logger.info(`Received downstream request from '${downstreamUserAgent}' ${downstreamIp} ${downstreamRequestedHttpMethod} ${downstreamRequestedHost} ${downstreamRequestedFullPath}
|
|
295
|
+
Proxy server request handler rate limit is ${limiter.maxConcurrency}`);
|
|
313
296
|
if (!downstreamRequestedFullPath || !downstreamRequestedHost) {
|
|
314
297
|
logger.warn(`400 Invalid Request, downstream ${downstreamUserAgent} req.url is absent or downstream.headers.host is absent.`);
|
|
315
298
|
resToDownstreamClient.writeHead(400).end('Invalid Request');
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export default class ConcurrencyLimiter {
|
|
2
|
+
maxConcurrency;
|
|
3
|
+
current = 0;
|
|
4
|
+
queue = [];
|
|
5
|
+
constructor(maxConcurrency) {
|
|
6
|
+
if (maxConcurrency <= 0) {
|
|
7
|
+
throw new Error("maxConcurrency must be positive");
|
|
8
|
+
}
|
|
9
|
+
this.maxConcurrency = maxConcurrency;
|
|
10
|
+
}
|
|
11
|
+
async acquire() {
|
|
12
|
+
if (this.current < this.maxConcurrency) {
|
|
13
|
+
this.current++;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
this.queue.push(resolve);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
release() {
|
|
21
|
+
if (this.current <= 0) {
|
|
22
|
+
throw new Error("release() called without acquire()");
|
|
23
|
+
}
|
|
24
|
+
this.current--;
|
|
25
|
+
const next = this.queue.shift();
|
|
26
|
+
if (next) {
|
|
27
|
+
// 异步执行,避免递归调用栈溢出
|
|
28
|
+
Promise.resolve().then(() => {
|
|
29
|
+
this.current++;
|
|
30
|
+
next();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async run(task) {
|
|
35
|
+
await this.acquire();
|
|
36
|
+
try {
|
|
37
|
+
return await task();
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
this.release();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|