com.jimuwd.xian.registry-proxy 1.1.10 → 1.1.12
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 +58 -20
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -146,8 +146,8 @@ async function fetchFromRegistry(registry, targetUrl, reqFromDownstreamClient, l
|
|
|
146
146
|
// 合并 headersFromDownstreamClient 和 authorizationHeaders
|
|
147
147
|
const mergedHeaders = { ...headersFromDownstreamClient, ...authorizationHeaders, };
|
|
148
148
|
// (mergedHeaders as any).connection = "keep-alive"; 不允许私自添加 connection: keep-alive header,应当最终下游客户端自己的选择
|
|
149
|
-
// 替换“Host”头为upstream的host
|
|
150
|
-
const upstreamHost = new URL(
|
|
149
|
+
// 替换“Host”头为upstream的host,注意这里要目标url,不能直接使用registryInfo内的normalizedUrl,以便兼容重定向的情况(此时targetUrl的host可能与registryInfo.normalizedUrl不同).
|
|
150
|
+
const upstreamHost = new URL(targetUrl).host;
|
|
151
151
|
if (mergedHeaders.host) {
|
|
152
152
|
logger.debug(() => `Replace 'Host=${mergedHeaders.host}' header in downstream request to upstream 'Host=${upstreamHost}' header when proxying to upstream ${targetUrl}.`);
|
|
153
153
|
mergedHeaders.host = upstreamHost;
|
|
@@ -158,15 +158,41 @@ async function fetchFromRegistry(registry, targetUrl, reqFromDownstreamClient, l
|
|
|
158
158
|
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
159
|
return response;
|
|
160
160
|
}
|
|
161
|
+
else if (response.status === 302 || response.status === 307 || response.status === 303) {
|
|
162
|
+
// 302 Found / 303 See Other / 307 Temporary Redirect 临时重定向 是常见的 HTTP 状态码,用于告知客户端请求的资源已被临时移动到另一个 URL,客户端应使用新的 URL 重新发起请求。
|
|
163
|
+
// 一个典型的例子:registry.npmmirror.com镜像仓库 会对其tarball下载地址临时重定向到cdn地址,比如你访问https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz,会得到
|
|
164
|
+
// Status Code: 302 Found
|
|
165
|
+
// location: https://cdn.npmmirror.com/packages/color-name/1.1.4/color-name-1.1.4.tgz
|
|
166
|
+
// 此时 registry-proxy 的行为:主动跟随重定向,然后将 response from redirected url 透传给下游。
|
|
167
|
+
const redirectedLocation = response.headers.get('location');
|
|
168
|
+
if (redirectedLocation) {
|
|
169
|
+
logger.info(`${response.status} ${response.statusText} response from upstream ${targetUrl}
|
|
170
|
+
Fetching from redirected location=${redirectedLocation}`);
|
|
171
|
+
return await fetchFromRegistry(registry, redirectedLocation, reqFromDownstreamClient, limiter);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
logger.warn(`${response.status} ${response.statusText} response from upstream ${targetUrl}, but redirect location is empty, skip fetching from ${targetUrl}`);
|
|
175
|
+
// Return null means skipping current upstream registry.
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else if (response.status === 301) {
|
|
180
|
+
// HTTP 301 Permanently Moved.
|
|
181
|
+
logger.info(`${response.status} ${response.statusText} response from upstream ${targetUrl}, moved to location=${response.headers.get('location')}`);
|
|
182
|
+
// 对于301永久转义响应,registry-proxy 的行为:透传给下游客户端,让客户端自行跳转(提示:这个跳转后的请求将不再走registry-proxy代理了)
|
|
183
|
+
return response;
|
|
184
|
+
}
|
|
161
185
|
else {
|
|
186
|
+
// Fetch form one of the configured upstream registries failed, this is expected behavior, not error.
|
|
162
187
|
logger.debug(async () => `Failure response from upstream ${targetUrl}: ${response.status} ${response.statusText}
|
|
163
188
|
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')}
|
|
164
189
|
body=${await response.text()}`);
|
|
190
|
+
// Return null means skipping current upstream registry.
|
|
165
191
|
return null;
|
|
166
192
|
}
|
|
167
193
|
}
|
|
168
194
|
catch (e) {
|
|
169
|
-
// Fetch form one of the
|
|
195
|
+
// Fetch form one of the configured upstream registries failed, this is expected behavior, not error.
|
|
170
196
|
if (e instanceof Error) {
|
|
171
197
|
const errCode = e.code;
|
|
172
198
|
if (errCode === 'ECONNREFUSED') {
|
|
@@ -176,14 +202,14 @@ async function fetchFromRegistry(registry, targetUrl, reqFromDownstreamClient, l
|
|
|
176
202
|
logger.info(`Unknown upstream domain name in ${targetUrl} [ENOTFOUND], skip fetching from registry ${registry.normalizedRegistryUrl}.`);
|
|
177
203
|
}
|
|
178
204
|
else {
|
|
179
|
-
//
|
|
205
|
+
// Other net error code, print log with stacktrace
|
|
180
206
|
logger.warn(`Failed to fetch from ${targetUrl}, ${e.message}`, e);
|
|
181
207
|
}
|
|
182
208
|
}
|
|
183
209
|
else {
|
|
184
210
|
logger.error("Unknown error", e);
|
|
185
211
|
}
|
|
186
|
-
//
|
|
212
|
+
// Return null means skipping current upstream registry.
|
|
187
213
|
return null;
|
|
188
214
|
}
|
|
189
215
|
finally {
|
|
@@ -196,11 +222,7 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
|
|
|
196
222
|
throw new Error("Only 2xx upstream response is supported");
|
|
197
223
|
try {
|
|
198
224
|
const contentType = upstreamResponse.headers.get("content-type");
|
|
199
|
-
if (
|
|
200
|
-
logger.error(`Response from upstream content-type header is absent, ${targetUrl} `);
|
|
201
|
-
await gracefulShutdown();
|
|
202
|
-
}
|
|
203
|
-
else if (contentType.includes('application/json')) { // JSON 处理逻辑
|
|
225
|
+
if (contentType?.includes('application/json')) { // JSON 处理逻辑
|
|
204
226
|
const data = await upstreamResponse.json();
|
|
205
227
|
if (data.versions) { // 处理node依赖包元数据
|
|
206
228
|
logger.debug(() => "Write package meta data application/json response from upstream to downstream", targetUrl);
|
|
@@ -230,7 +252,7 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
|
|
|
230
252
|
logger.info(`Response to downstream client headers`, JSON.stringify(resToDownstreamClient.getHeaders()), targetUrl);
|
|
231
253
|
resToDownstreamClient.writeHead(upstreamResponse.status).end(bodyData);
|
|
232
254
|
}
|
|
233
|
-
else if (contentType
|
|
255
|
+
else if (contentType?.includes('application/octet-stream')) { // 二进制流处理
|
|
234
256
|
logger.debug(() => `Write application/octet-stream response from upstream to downstream, upstream url is ${targetUrl}`);
|
|
235
257
|
if (!upstreamResponse.body) {
|
|
236
258
|
logger.error(`Empty response body from upstream ${targetUrl}`);
|
|
@@ -279,7 +301,7 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
|
|
|
279
301
|
// pipe upstream body-stream to downstream stream and automatically ends the stream to downstream when upstream stream is ended.
|
|
280
302
|
upstreamResponse.body.pipe(resToDownstreamClient, { end: true });
|
|
281
303
|
upstreamResponse.body
|
|
282
|
-
.on('data', (chunk) => logger.
|
|
304
|
+
.on('data', (chunk) => logger.debug(() => `Chunk transferred from ${targetUrl} to downstream client size=${Buffer.byteLength(chunk)}bytes`))
|
|
283
305
|
.on('end', () => logger.info(`Upstream server ${targetUrl} response.body stream ended.`))
|
|
284
306
|
// connection will be closed automatically when all chunk data is transferred (after stream ends).
|
|
285
307
|
.on('close', () => logger.debug(() => `Upstream ${targetUrl} closed stream.`))
|
|
@@ -292,15 +314,31 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
|
|
|
292
314
|
}
|
|
293
315
|
}
|
|
294
316
|
else {
|
|
295
|
-
|
|
317
|
+
if (!contentType) {
|
|
318
|
+
logger.warn(`Response from upstream content-type header is absent, ${targetUrl} `);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
logger.warn(`Write unsupported content-type=${contentType} response from upstream to downstream. Upstream url is ${targetUrl}`);
|
|
322
|
+
}
|
|
296
323
|
const bodyData = await upstreamResponse.text();
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
324
|
+
{
|
|
325
|
+
// 对于不规范的registry server,我们对其header做合理化整理,如下
|
|
326
|
+
{
|
|
327
|
+
// 默认是 connection: keep-alive 和 keep-alive: timeout=5,这里直接给它咔嚓掉
|
|
328
|
+
resToDownstreamClient.setHeader('connection', 'close');
|
|
329
|
+
resToDownstreamClient.removeHeader('Keep-Alive');
|
|
330
|
+
}
|
|
331
|
+
if (contentType)
|
|
332
|
+
resToDownstreamClient.setHeader('content-type', contentType);
|
|
333
|
+
else
|
|
334
|
+
resToDownstreamClient.removeHeader('content-type');
|
|
335
|
+
{
|
|
336
|
+
// 强制用固定的content-length
|
|
337
|
+
resToDownstreamClient.removeHeader('transfer-encoding');
|
|
338
|
+
resToDownstreamClient.setHeader('content-length', Buffer.byteLength(bodyData));
|
|
339
|
+
}
|
|
340
|
+
logger.debug(() => `Response to downstream client headers ${JSON.stringify(resToDownstreamClient.getHeaders())} Upstream url is ${targetUrl}`);
|
|
341
|
+
}
|
|
304
342
|
resToDownstreamClient.writeHead(upstreamResponse.status).end(bodyData);
|
|
305
343
|
}
|
|
306
344
|
}
|