com.jimuwd.xian.registry-proxy 1.1.10 → 1.1.11

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.
@@ -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(registry.normalizedRegistryUrl).host;
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 confiured upstream registries failed, this is expected, not error.
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
- // other net error code, pring log with stacktrace
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
- // return null means skipping current upstream registry.
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 (!contentType) {
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.includes('application/octet-stream')) { // 二进制流处理
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}`);
@@ -292,15 +314,31 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
292
314
  }
293
315
  }
294
316
  else {
295
- logger.warn(`Write unsupported content-type=${contentType} response from upstream to downstream. Upstream url is ${targetUrl}`);
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
- resToDownstreamClient.removeHeader('transfer-encoding');
298
- // 默认是 connection: keep-alive 和 keep-alive: timeout=5,这里直接给它咔嚓掉
299
- resToDownstreamClient.setHeader('connection', 'close');
300
- resToDownstreamClient.removeHeader('Keep-Alive');
301
- resToDownstreamClient.setHeader('content-type', contentType);
302
- resToDownstreamClient.setHeader('content-length', Buffer.byteLength(bodyData));
303
- logger.debug(() => `Response to downstream client headers ${JSON.stringify(resToDownstreamClient.getHeaders())} Upstream url is ${targetUrl}`);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.jimuwd.xian.registry-proxy",
3
- "version": "1.1.10",
3
+ "version": "1.1.11",
4
4
  "description": "A lightweight npm registry proxy with fallback support",
5
5
  "type": "module",
6
6
  "main": "dist/server/index.js",