@verdaccio/proxy 6.0.0-6-next.20 → 6.0.0-6-next.21
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/CHANGELOG.md +37 -0
- package/build/agent.d.ts +17 -0
- package/build/agent.js +63 -0
- package/build/agent.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.js +4 -4
- package/build/index.js.map +1 -1
- package/build/proxy-utils.js.map +1 -1
- package/build/{up-storage.d.ts → proxy.d.ts} +28 -54
- package/build/proxy.js +565 -0
- package/build/proxy.js.map +1 -0
- package/jest.config.js +4 -5
- package/package.json +66 -67
- package/src/agent.ts +54 -0
- package/src/index.ts +1 -1
- package/src/{up-storage.ts → proxy.ts} +248 -352
- package/test/headers.auth.spec.ts +15 -18
- package/test/noProxy.spec.ts +215 -76
- package/test/partials/ssl-cert-snakeoil.key +15 -0
- package/test/partials/ssl-cert-snakeoil.pem +12 -0
- package/test/proxy.error.___.ts +126 -0
- package/test/proxy.metadata.spec.ts +354 -0
- package/test/proxy.search.spec.ts +1 -7
- package/test/proxy.tarball.spec.ts +142 -204
- package/tsconfig.json +0 -6
- package/build/up-storage.js +0 -691
- package/build/up-storage.js.map +0 -1
- package/test/proxy.error.spec.ts +0 -126
- package/test/proxy.metadata.ts +0 -133
|
@@ -1,28 +1,27 @@
|
|
|
1
|
-
/* global AbortController */
|
|
2
1
|
import JSONStream from 'JSONStream';
|
|
3
2
|
import buildDebug from 'debug';
|
|
3
|
+
import got, { RequiredRetryOptions, Headers as gotHeaders } from 'got';
|
|
4
|
+
import type { Agents, Options } from 'got';
|
|
4
5
|
import _ from 'lodash';
|
|
5
|
-
import requestDeprecated from 'request';
|
|
6
6
|
import Stream, { PassThrough, Readable } from 'stream';
|
|
7
7
|
import { Headers, fetch as undiciFetch } from 'undici';
|
|
8
8
|
import { URL } from 'url';
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
API_ERROR,
|
|
12
12
|
HEADERS,
|
|
13
|
-
HEADER_TYPE,
|
|
14
13
|
HTTP_STATUS,
|
|
15
14
|
TOKEN_BASIC,
|
|
16
15
|
TOKEN_BEARER,
|
|
17
16
|
constants,
|
|
18
17
|
errorUtils,
|
|
19
18
|
searchUtils,
|
|
20
|
-
validatioUtils,
|
|
21
19
|
} from '@verdaccio/core';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
20
|
+
import { Manifest } from '@verdaccio/types';
|
|
21
|
+
import { Config, Logger, UpLinkConf } from '@verdaccio/types';
|
|
24
22
|
import { buildToken } from '@verdaccio/utils';
|
|
25
23
|
|
|
24
|
+
import CustomAgents, { AgentOptionsConf } from './agent';
|
|
26
25
|
import { parseInterval } from './proxy-utils';
|
|
27
26
|
|
|
28
27
|
const LoggerApi = require('@verdaccio/logger');
|
|
@@ -39,7 +38,7 @@ const contentTypeAccept = `${jsonContentType};`;
|
|
|
39
38
|
/**
|
|
40
39
|
* Just a helper (`config[key] || default` doesn't work because of zeroes)
|
|
41
40
|
*/
|
|
42
|
-
const setConfig = (config, key, def): string => {
|
|
41
|
+
const setConfig = (config: UpLinkConfLocal, key: string, def): string => {
|
|
43
42
|
return _.isNil(config[key]) === false ? config[key] : def;
|
|
44
43
|
};
|
|
45
44
|
|
|
@@ -70,9 +69,21 @@ export interface IProxy {
|
|
|
70
69
|
max_fails: number;
|
|
71
70
|
fail_timeout: number;
|
|
72
71
|
upname: string;
|
|
73
|
-
fetchTarball(url: string): IReadTarball;
|
|
74
72
|
search(options: ProxySearchParams): Promise<Stream.Readable>;
|
|
75
|
-
|
|
73
|
+
getRemoteMetadataNext(name: string, options: ISyncUplinksOptions): Promise<[Manifest, string]>;
|
|
74
|
+
fetchTarballNext(
|
|
75
|
+
url: string,
|
|
76
|
+
options: Pick<ISyncUplinksOptions, 'remoteAddress' | 'etag' | 'retry'>
|
|
77
|
+
): PassThrough;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// this type is need it by storage
|
|
81
|
+
export { Options as FetchOptions };
|
|
82
|
+
|
|
83
|
+
export interface ISyncUplinksOptions extends Options {
|
|
84
|
+
uplinksLook?: boolean;
|
|
85
|
+
etag?: string;
|
|
86
|
+
remoteAddress?: string;
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
/**
|
|
@@ -91,33 +102,33 @@ class ProxyStorage implements IProxy {
|
|
|
91
102
|
public timeout: number;
|
|
92
103
|
public max_fails: number;
|
|
93
104
|
public fail_timeout: number;
|
|
94
|
-
public agent_options:
|
|
105
|
+
public agent_options: AgentOptionsConf;
|
|
95
106
|
// FIXME: upname is assigned to each instance
|
|
96
107
|
// @ts-ignore
|
|
97
108
|
public upname: string;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
public proxy: any;
|
|
109
|
+
public proxy: string | undefined;
|
|
110
|
+
private agent: Agents;
|
|
101
111
|
// @ts-ignore
|
|
102
112
|
public last_request_time: number | null;
|
|
103
113
|
public strict_ssl: boolean;
|
|
114
|
+
private retry: Partial<RequiredRetryOptions> | number;
|
|
104
115
|
|
|
105
|
-
|
|
106
|
-
* Constructor
|
|
107
|
-
* @param {*} config
|
|
108
|
-
* @param {*} mainConfig
|
|
109
|
-
*/
|
|
110
|
-
public constructor(config: UpLinkConfLocal, mainConfig: Config) {
|
|
116
|
+
public constructor(config: UpLinkConfLocal, mainConfig: Config, agent?: Agents) {
|
|
111
117
|
this.config = config;
|
|
112
118
|
this.failed_requests = 0;
|
|
113
119
|
this.userAgent = mainConfig.user_agent;
|
|
114
120
|
this.ca = config.ca;
|
|
115
121
|
this.logger = LoggerApi.logger.child({ sub: 'out' });
|
|
116
122
|
this.server_id = mainConfig.server_id;
|
|
117
|
-
|
|
123
|
+
this.agent_options = setConfig(this.config, 'agent_options', {
|
|
124
|
+
keepAlive: true,
|
|
125
|
+
maxSockets: 40,
|
|
126
|
+
maxFreeSockets: 10,
|
|
127
|
+
}) as AgentOptionsConf;
|
|
118
128
|
this.url = new URL(this.config.url);
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
const isHTTPS = this.url.protocol === 'https:';
|
|
130
|
+
this._setupProxy(this.url.hostname, config, mainConfig, isHTTPS);
|
|
131
|
+
this.agent = agent ?? this.getAgent();
|
|
121
132
|
this.config.url = this.config.url.replace(/\/$/, '');
|
|
122
133
|
|
|
123
134
|
if (this.config.timeout && Number(this.config.timeout) >= 1000) {
|
|
@@ -133,192 +144,25 @@ class ProxyStorage implements IProxy {
|
|
|
133
144
|
|
|
134
145
|
// a bunch of different configurable timers
|
|
135
146
|
this.maxage = parseInterval(setConfig(this.config, 'maxage', '2m'));
|
|
147
|
+
// https://github.com/sindresorhus/got/blob/main/documentation/6-timeout.md
|
|
136
148
|
this.timeout = parseInterval(setConfig(this.config, 'timeout', '30s'));
|
|
137
|
-
this.max_fails = Number(setConfig(this.config, 'max_fails', 2));
|
|
149
|
+
this.max_fails = Number(setConfig(this.config, 'max_fails', this.config.max_fails ?? 2));
|
|
138
150
|
this.fail_timeout = parseInterval(setConfig(this.config, 'fail_timeout', '5m'));
|
|
139
151
|
this.strict_ssl = Boolean(setConfig(this.config, 'strict_ssl', true));
|
|
140
|
-
this.
|
|
141
|
-
keepAlive: true,
|
|
142
|
-
maxSockets: 40,
|
|
143
|
-
maxFreeSockets: 10,
|
|
144
|
-
});
|
|
152
|
+
this.retry = { limit: this.max_fails ?? 2 };
|
|
145
153
|
}
|
|
146
154
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
let json;
|
|
155
|
-
|
|
156
|
-
if (this._statusCheck() === false) {
|
|
157
|
-
const streamRead = new Stream.Readable();
|
|
158
|
-
|
|
159
|
-
process.nextTick(function (): void {
|
|
160
|
-
if (cb) {
|
|
161
|
-
cb(errorUtils.getInternalError(errorUtils.API_ERROR.UPLINK_OFFLINE));
|
|
162
|
-
}
|
|
163
|
-
streamRead.emit('error', errorUtils.getInternalError(errorUtils.API_ERROR.UPLINK_OFFLINE));
|
|
164
|
-
});
|
|
165
|
-
streamRead._read = function (): void {};
|
|
166
|
-
// preventing 'Uncaught, unspecified "error" event'
|
|
167
|
-
streamRead.on('error', function (): void {});
|
|
168
|
-
return streamRead;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const self = this;
|
|
172
|
-
const headers: Headers = this._setHeaders(options);
|
|
173
|
-
|
|
174
|
-
this._addProxyHeaders(options.req, headers);
|
|
175
|
-
this._overrideWithUpLinkConfLocaligHeaders(headers);
|
|
176
|
-
|
|
177
|
-
const method = options.method || 'GET';
|
|
178
|
-
const uri = options.uri_full || this.config.url + options.uri;
|
|
179
|
-
|
|
180
|
-
self.logger.info(
|
|
181
|
-
{
|
|
182
|
-
method: method,
|
|
183
|
-
headers: headers,
|
|
184
|
-
uri: uri,
|
|
185
|
-
},
|
|
186
|
-
"making request: '@{method} @{uri}'"
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
if (validatioUtils.isObject(options.json)) {
|
|
190
|
-
json = JSON.stringify(options.json);
|
|
191
|
-
headers['Content-Type'] = headers['Content-Type'] || HEADERS.JSON;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const requestCallback = cb
|
|
195
|
-
? function (err, res, body): void {
|
|
196
|
-
let error;
|
|
197
|
-
const responseLength = err ? 0 : body.length;
|
|
198
|
-
// $FlowFixMe
|
|
199
|
-
processBody();
|
|
200
|
-
logActivity();
|
|
201
|
-
// $FlowFixMe
|
|
202
|
-
cb(err, res, body);
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Perform a decode.
|
|
206
|
-
*/
|
|
207
|
-
function processBody(): void {
|
|
208
|
-
if (err) {
|
|
209
|
-
error = err.message;
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (options.json && res.statusCode < 300) {
|
|
214
|
-
try {
|
|
215
|
-
// $FlowFixMe
|
|
216
|
-
body = JSON.parse(body.toString(CHARACTER_ENCODING.UTF8));
|
|
217
|
-
} catch (_err: any) {
|
|
218
|
-
body = {};
|
|
219
|
-
err = _err;
|
|
220
|
-
error = err.message;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (!err && validatioUtils.isObject(body)) {
|
|
225
|
-
if (_.isString(body.error)) {
|
|
226
|
-
error = body.error;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Perform a log.
|
|
232
|
-
*/
|
|
233
|
-
function logActivity(): void {
|
|
234
|
-
let message = "@{!status}, req: '@{request.method} @{request.url}'";
|
|
235
|
-
// FIXME: use LOG_VERDACCIO_BYTES
|
|
236
|
-
message += error ? ', error: @{!error}' : ', bytes: @{bytes.in}/@{bytes.out}';
|
|
237
|
-
self.logger.http(
|
|
238
|
-
{
|
|
239
|
-
// if error is null/false change this to undefined so it wont log
|
|
240
|
-
err: err || undefined,
|
|
241
|
-
request: { method: method, url: uri },
|
|
242
|
-
status: res != null ? res.statusCode : 'ERR',
|
|
243
|
-
error: error,
|
|
244
|
-
bytes: {
|
|
245
|
-
in: json ? json.length : 0,
|
|
246
|
-
out: responseLength || 0,
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
|
-
message
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
: undefined;
|
|
254
|
-
|
|
255
|
-
let requestOptions = {
|
|
256
|
-
url: uri,
|
|
257
|
-
method: method,
|
|
258
|
-
headers: headers,
|
|
259
|
-
body: json,
|
|
260
|
-
proxy: this.proxy,
|
|
261
|
-
encoding: null,
|
|
262
|
-
gzip: true,
|
|
263
|
-
timeout: this.timeout,
|
|
264
|
-
strictSSL: this.strict_ssl,
|
|
265
|
-
agentOptions: this.agent_options,
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
if (this.ca) {
|
|
269
|
-
requestOptions = Object.assign({}, requestOptions, {
|
|
270
|
-
ca: this.ca,
|
|
271
|
-
});
|
|
155
|
+
private getAgent() {
|
|
156
|
+
if (!this.agent) {
|
|
157
|
+
// TODO: the config.ca (certificates) is not yet injected here
|
|
158
|
+
const agentInstance = new CustomAgents(this.config.url, this.proxy, this.agent_options);
|
|
159
|
+
return agentInstance.get();
|
|
160
|
+
} else {
|
|
161
|
+
return this.agent;
|
|
272
162
|
}
|
|
273
|
-
|
|
274
|
-
const req = requestDeprecated(requestOptions, requestCallback);
|
|
275
|
-
|
|
276
|
-
let statusCalled = false;
|
|
277
|
-
req.on('response', function (res): void {
|
|
278
|
-
// FIXME: _verdaccio_aborted seems not used
|
|
279
|
-
// @ts-ignore
|
|
280
|
-
if (!req._verdaccio_aborted && !statusCalled) {
|
|
281
|
-
statusCalled = true;
|
|
282
|
-
self._statusCheck(true);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (_.isNil(requestCallback) === false) {
|
|
286
|
-
(function do_log(): void {
|
|
287
|
-
const message = "@{!status}, req: '@{request.method} @{request.url}' (streaming)";
|
|
288
|
-
self.logger.http(
|
|
289
|
-
{
|
|
290
|
-
request: {
|
|
291
|
-
method: method,
|
|
292
|
-
url: uri,
|
|
293
|
-
},
|
|
294
|
-
status: _.isNull(res) === false ? res.statusCode : 'ERR',
|
|
295
|
-
},
|
|
296
|
-
message
|
|
297
|
-
);
|
|
298
|
-
})();
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
302
|
-
req.on('error', function (_err): void {
|
|
303
|
-
// FIXME: _verdaccio_aborted seems not used
|
|
304
|
-
// @ts-ignore
|
|
305
|
-
if (!req._verdaccio_aborted && !statusCalled) {
|
|
306
|
-
statusCalled = true;
|
|
307
|
-
self._statusCheck(false);
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
// @ts-ignore
|
|
311
|
-
return req;
|
|
312
163
|
}
|
|
313
164
|
|
|
314
|
-
|
|
315
|
-
* Set default headers.
|
|
316
|
-
* @param {Object} options
|
|
317
|
-
* @return {Object}
|
|
318
|
-
* @private
|
|
319
|
-
*/
|
|
320
|
-
private _setHeaders(options: any): Headers {
|
|
321
|
-
const headers = options.headers || {};
|
|
165
|
+
public getHeadersNext(headers = {}): gotHeaders {
|
|
322
166
|
const accept = HEADERS.ACCEPT;
|
|
323
167
|
const acceptEncoding = HEADERS.ACCEPT_ENCODING;
|
|
324
168
|
const userAgent = HEADERS.USER_AGENT;
|
|
@@ -327,8 +171,7 @@ class ProxyStorage implements IProxy {
|
|
|
327
171
|
headers[acceptEncoding] = headers[acceptEncoding] || 'gzip';
|
|
328
172
|
// registry.npmjs.org will only return search result if user-agent include string 'npm'
|
|
329
173
|
headers[userAgent] = headers[userAgent] || `npm (${this.userAgent})`;
|
|
330
|
-
|
|
331
|
-
return this._setAuth(headers);
|
|
174
|
+
return this.setAuthNext(headers);
|
|
332
175
|
}
|
|
333
176
|
|
|
334
177
|
/**
|
|
@@ -337,10 +180,9 @@ class ProxyStorage implements IProxy {
|
|
|
337
180
|
* @return {Object}
|
|
338
181
|
* @private
|
|
339
182
|
*/
|
|
340
|
-
private
|
|
183
|
+
private setAuthNext(headers: gotHeaders): gotHeaders {
|
|
341
184
|
const { auth } = this.config;
|
|
342
|
-
|
|
343
|
-
if (_.isNil(auth) || headers[HEADERS.AUTHORIZATION]) {
|
|
185
|
+
if (typeof auth === 'undefined' || typeof headers[HEADERS.AUTHORIZATION] === 'string') {
|
|
344
186
|
return headers;
|
|
345
187
|
}
|
|
346
188
|
|
|
@@ -353,13 +195,12 @@ class ProxyStorage implements IProxy {
|
|
|
353
195
|
// https://github.com/verdaccio/verdaccio/releases/tag/v2.5.0
|
|
354
196
|
let token: any;
|
|
355
197
|
const tokenConf: any = auth;
|
|
356
|
-
|
|
357
198
|
if (_.isNil(tokenConf.token) === false && _.isString(tokenConf.token)) {
|
|
358
199
|
token = tokenConf.token;
|
|
359
200
|
} else if (_.isNil(tokenConf.token_env) === false) {
|
|
360
|
-
if (
|
|
201
|
+
if (typeof tokenConf.token_env === 'string') {
|
|
361
202
|
token = process.env[tokenConf.token_env];
|
|
362
|
-
} else if (
|
|
203
|
+
} else if (typeof tokenConf.token_env === 'boolean' && tokenConf.token_env) {
|
|
363
204
|
token = process.env.NPM_TOKEN;
|
|
364
205
|
} else {
|
|
365
206
|
this.logger.error(constants.ERROR_CODE.token_required);
|
|
@@ -369,7 +210,7 @@ class ProxyStorage implements IProxy {
|
|
|
369
210
|
token = process.env.NPM_TOKEN;
|
|
370
211
|
}
|
|
371
212
|
|
|
372
|
-
if (
|
|
213
|
+
if (typeof token === 'undefined') {
|
|
373
214
|
this._throwErrorAuth(constants.ERROR_CODE.token_required);
|
|
374
215
|
}
|
|
375
216
|
|
|
@@ -426,6 +267,7 @@ class ProxyStorage implements IProxy {
|
|
|
426
267
|
|
|
427
268
|
* @param {Object} headers
|
|
428
269
|
* @private
|
|
270
|
+
* @deprecated use applyUplinkHeaders
|
|
429
271
|
*/
|
|
430
272
|
private _overrideWithUpLinkConfLocaligHeaders(headers: Headers): any {
|
|
431
273
|
if (!this.config.headers) {
|
|
@@ -439,98 +281,200 @@ class ProxyStorage implements IProxy {
|
|
|
439
281
|
}
|
|
440
282
|
}
|
|
441
283
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const headers
|
|
284
|
+
private applyUplinkHeaders(headers: gotHeaders): gotHeaders {
|
|
285
|
+
if (!this.config.headers) {
|
|
286
|
+
return headers;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// add/override headers specified in the config
|
|
290
|
+
/* eslint guard-for-in: 0 */
|
|
291
|
+
for (const key in this.config.headers) {
|
|
292
|
+
headers[key] = this.config.headers[key];
|
|
293
|
+
}
|
|
294
|
+
return headers;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
public async getRemoteMetadataNext(
|
|
298
|
+
name: string,
|
|
299
|
+
options: ISyncUplinksOptions
|
|
300
|
+
): Promise<[Manifest, string]> {
|
|
301
|
+
if (this._ifRequestFailure()) {
|
|
302
|
+
throw errorUtils.getInternalError(API_ERROR.UPLINK_OFFLINE);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// FUTURE: allow mix headers that comes from the client
|
|
306
|
+
debug('get metadata for %s', name);
|
|
307
|
+
let headers = this.getHeadersNext(options?.headers);
|
|
308
|
+
headers = this.addProxyHeaders(headers, options.remoteAddress);
|
|
309
|
+
headers = this.applyUplinkHeaders(headers);
|
|
310
|
+
// the following headers cannot be overwritten
|
|
450
311
|
if (_.isNil(options.etag) === false) {
|
|
451
|
-
headers[
|
|
312
|
+
headers[HEADERS.NONE_MATCH] = options.etag;
|
|
452
313
|
headers[HEADERS.ACCEPT] = contentTypeAccept;
|
|
453
314
|
}
|
|
315
|
+
const method = options.method || 'GET';
|
|
316
|
+
const uri = this.config.url + `/${encode(name)}`;
|
|
317
|
+
debug('request uri for %s retry %s', uri);
|
|
318
|
+
let response;
|
|
319
|
+
let responseLength = 0;
|
|
320
|
+
try {
|
|
321
|
+
const retry = options?.retry ?? this.retry;
|
|
322
|
+
debug('retry times %s for %s', retry, uri);
|
|
323
|
+
response = await got(uri, {
|
|
324
|
+
headers,
|
|
325
|
+
responseType: 'json',
|
|
326
|
+
method,
|
|
327
|
+
agent: this.agent,
|
|
328
|
+
retry,
|
|
329
|
+
// @ts-ignore
|
|
330
|
+
timeout: { request: options?.timeout ?? this.timeout },
|
|
331
|
+
hooks: {
|
|
332
|
+
afterResponse: [
|
|
333
|
+
(afterResponse) => {
|
|
334
|
+
const code = afterResponse.statusCode;
|
|
335
|
+
debug('code response %s', code);
|
|
336
|
+
if (code >= HTTP_STATUS.OK && code < HTTP_STATUS.MULTIPLE_CHOICES) {
|
|
337
|
+
if (this.failed_requests >= this.max_fails) {
|
|
338
|
+
this.failed_requests = 0;
|
|
339
|
+
this.logger.warn(
|
|
340
|
+
{
|
|
341
|
+
host: this.url.host,
|
|
342
|
+
},
|
|
343
|
+
'host @{host} is now online'
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
454
347
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
348
|
+
return afterResponse;
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
beforeRetry: [
|
|
352
|
+
// FUTURE: got 12.0.0, the option arg should be removed
|
|
353
|
+
(_options, error: any, count) => {
|
|
354
|
+
this.failed_requests = count ?? 0;
|
|
355
|
+
this.logger.info(
|
|
356
|
+
{
|
|
357
|
+
request: {
|
|
358
|
+
method: method,
|
|
359
|
+
url: uri,
|
|
360
|
+
},
|
|
361
|
+
error: error.message,
|
|
362
|
+
retryCount: this.failed_requests,
|
|
363
|
+
},
|
|
364
|
+
"retry @{retryCount} req: '@{request.method} @{request.url}'"
|
|
365
|
+
);
|
|
366
|
+
if (this.failed_requests >= this.max_fails) {
|
|
367
|
+
this.logger.warn(
|
|
368
|
+
{
|
|
369
|
+
host: this.url.host,
|
|
370
|
+
},
|
|
371
|
+
'host @{host} is now offline'
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
})
|
|
378
|
+
.on('request', () => {
|
|
379
|
+
this.last_request_time = Date.now();
|
|
380
|
+
})
|
|
381
|
+
.on('response', (eventResponse) => {
|
|
382
|
+
const message = "@{!status}, req: '@{request.method} @{request.url}' (streaming)";
|
|
383
|
+
this.logger.http(
|
|
384
|
+
{
|
|
385
|
+
request: {
|
|
386
|
+
method: method,
|
|
387
|
+
url: uri,
|
|
388
|
+
},
|
|
389
|
+
status: _.isNull(eventResponse) === false ? eventResponse.statusCode : 'ERR',
|
|
390
|
+
},
|
|
391
|
+
message
|
|
392
|
+
);
|
|
393
|
+
})
|
|
394
|
+
.on('downloadProgress', (progress) => {
|
|
395
|
+
if (progress.total) {
|
|
396
|
+
debug('responseLength %s', progress.total);
|
|
397
|
+
responseLength = progress.total;
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
const etag = response.headers.etag as string;
|
|
401
|
+
const data = response.body;
|
|
402
|
+
|
|
403
|
+
// not modified status (304) registry does not return any payload
|
|
404
|
+
// it is handled as an error
|
|
405
|
+
if (response?.statusCode === HTTP_STATUS.NOT_MODIFIED) {
|
|
406
|
+
throw errorUtils.getCode(HTTP_STATUS.NOT_MODIFIED, API_ERROR.NOT_MODIFIED_NO_DATA);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
debug('uri %s success', uri);
|
|
410
|
+
const message = "@{!status}, req: '@{request.method} @{request.url}'";
|
|
411
|
+
this.logger.http(
|
|
412
|
+
{
|
|
413
|
+
// if error is null/false change this to undefined so it wont log
|
|
414
|
+
request: { method: method, url: uri },
|
|
415
|
+
status: response.statusCode,
|
|
416
|
+
bytes: {
|
|
417
|
+
in: options?.json ? JSON.stringify(options?.json).length : 0,
|
|
418
|
+
out: responseLength || 0,
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
message
|
|
422
|
+
);
|
|
423
|
+
return [data, etag];
|
|
424
|
+
} catch (err: any) {
|
|
425
|
+
debug('uri %s fail', uri);
|
|
426
|
+
if (err.code === 'ERR_NON_2XX_3XX_RESPONSE') {
|
|
427
|
+
const code = err.response.statusCode;
|
|
428
|
+
if (code === HTTP_STATUS.NOT_FOUND) {
|
|
429
|
+
throw errorUtils.getNotFound(errorUtils.API_ERROR.NOT_PACKAGE_UPLINK);
|
|
468
430
|
}
|
|
469
|
-
|
|
431
|
+
|
|
432
|
+
if (!(code >= HTTP_STATUS.OK && code < HTTP_STATUS.MULTIPLE_CHOICES)) {
|
|
470
433
|
const error = errorUtils.getInternalError(
|
|
471
|
-
`${errorUtils.API_ERROR.BAD_STATUS_CODE}: ${
|
|
434
|
+
`${errorUtils.API_ERROR.BAD_STATUS_CODE}: ${code}`
|
|
472
435
|
);
|
|
473
|
-
|
|
474
|
-
error.remoteStatus =
|
|
475
|
-
|
|
436
|
+
// we need this code to identify outside which status code triggered the error
|
|
437
|
+
error.remoteStatus = code;
|
|
438
|
+
throw error;
|
|
476
439
|
}
|
|
477
|
-
callback(null, body, res.headers.etag);
|
|
478
440
|
}
|
|
479
|
-
|
|
441
|
+
throw err;
|
|
442
|
+
}
|
|
480
443
|
}
|
|
481
444
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const
|
|
489
|
-
let
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
readStream
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
stream.emit(HEADER_TYPE.CONTENT_LENGTH, res.headers[HEADER_TYPE.CONTENT_LENGTH]);
|
|
514
|
-
}
|
|
445
|
+
// FIXME: handle stream and retry
|
|
446
|
+
public fetchTarballNext(
|
|
447
|
+
url: string,
|
|
448
|
+
overrideOptions: Pick<ISyncUplinksOptions, 'remoteAddress' | 'etag' | 'retry'>
|
|
449
|
+
): any {
|
|
450
|
+
debug('fetching url for %s', url);
|
|
451
|
+
const options = { ...this.config, ...overrideOptions };
|
|
452
|
+
let headers = this.getHeadersNext(options?.headers);
|
|
453
|
+
headers = this.addProxyHeaders(headers, options.remoteAddress);
|
|
454
|
+
headers = this.applyUplinkHeaders(headers);
|
|
455
|
+
// the following headers cannot be overwritten
|
|
456
|
+
if (_.isNil(options.etag) === false) {
|
|
457
|
+
headers[HEADERS.NONE_MATCH] = options.etag;
|
|
458
|
+
headers[HEADERS.ACCEPT] = contentTypeAccept;
|
|
459
|
+
}
|
|
460
|
+
const method = 'GET';
|
|
461
|
+
// const uri = this.config.url + `/${encode(name)}`;
|
|
462
|
+
debug('request uri for %s', url);
|
|
463
|
+
|
|
464
|
+
const readStream = got
|
|
465
|
+
.stream(url, {
|
|
466
|
+
headers,
|
|
467
|
+
method,
|
|
468
|
+
agent: this.agent,
|
|
469
|
+
// FIXME: this should be taken from construtor as priority
|
|
470
|
+
retry: this.retry ?? options?.retry,
|
|
471
|
+
timeout: this.timeout,
|
|
472
|
+
})
|
|
473
|
+
.on('request', () => {
|
|
474
|
+
this.last_request_time = Date.now();
|
|
475
|
+
});
|
|
515
476
|
|
|
516
|
-
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
readStream.on('error', function (err) {
|
|
520
|
-
stream.emit('error', err);
|
|
521
|
-
});
|
|
522
|
-
readStream.on('data', function (data) {
|
|
523
|
-
current_length += data.length;
|
|
524
|
-
});
|
|
525
|
-
readStream.on('end', function (data) {
|
|
526
|
-
if (data) {
|
|
527
|
-
current_length += data.length;
|
|
528
|
-
}
|
|
529
|
-
if (expected_length && current_length != expected_length) {
|
|
530
|
-
stream.emit('error', errorUtils.getInternalError(errorUtils.API_ERROR.CONTENT_MISMATCH));
|
|
531
|
-
}
|
|
532
|
-
});
|
|
533
|
-
return stream;
|
|
477
|
+
return readStream;
|
|
534
478
|
}
|
|
535
479
|
|
|
536
480
|
/**
|
|
@@ -577,65 +521,21 @@ class ProxyStorage implements IProxy {
|
|
|
577
521
|
}
|
|
578
522
|
}
|
|
579
523
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
// Only submit X-Forwarded-For field if we don't have a proxy selected
|
|
589
|
-
// in the config file.
|
|
590
|
-
//
|
|
591
|
-
// Otherwise misconfigured proxy could return 407:
|
|
592
|
-
// https://github.com/rlidwka/sinopia/issues/254
|
|
593
|
-
// @ts-ignore
|
|
594
|
-
if (!this.proxy) {
|
|
595
|
-
headers[HEADERS.FORWARDED_FOR] =
|
|
596
|
-
(req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'] + ', ' : '') +
|
|
597
|
-
req.connection.remoteAddress;
|
|
598
|
-
}
|
|
524
|
+
private addProxyHeaders(headers: gotHeaders, remoteAddress?: string): gotHeaders {
|
|
525
|
+
// Only submit X-Forwarded-For field if we don't have a proxy selected
|
|
526
|
+
// in the config file.
|
|
527
|
+
//
|
|
528
|
+
// Otherwise misconfigured proxy could return 407
|
|
529
|
+
if (!this.proxy) {
|
|
530
|
+
headers[HEADERS.FORWARDED_FOR] =
|
|
531
|
+
(headers['x-forwarded-for'] ? headers['x-forwarded-for'] + ', ' : '') + remoteAddress;
|
|
599
532
|
}
|
|
600
533
|
|
|
601
534
|
// always attach Via header to avoid loops, even if we're not proxying
|
|
602
|
-
headers['
|
|
603
|
-
|
|
604
|
-
headers['Via'] += '1.1 ' + this.server_id + ' (Verdaccio)';
|
|
605
|
-
}
|
|
535
|
+
headers['via'] = headers['via'] ? headers['via'] + ', ' : '';
|
|
536
|
+
headers['via'] += '1.1 ' + this.server_id + ' (Verdaccio)';
|
|
606
537
|
|
|
607
|
-
|
|
608
|
-
* Check whether the remote host is available.
|
|
609
|
-
* @param {*} alive
|
|
610
|
-
* @return {Boolean}
|
|
611
|
-
*/
|
|
612
|
-
private _statusCheck(alive?: boolean): boolean | void {
|
|
613
|
-
if (arguments.length === 0) {
|
|
614
|
-
return this._ifRequestFailure() === false;
|
|
615
|
-
}
|
|
616
|
-
if (alive) {
|
|
617
|
-
if (this.failed_requests >= this.max_fails) {
|
|
618
|
-
this.logger.warn(
|
|
619
|
-
{
|
|
620
|
-
host: this.url.host,
|
|
621
|
-
},
|
|
622
|
-
'host @{host} is back online'
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
this.failed_requests = 0;
|
|
626
|
-
} else {
|
|
627
|
-
this.failed_requests++;
|
|
628
|
-
if (this.failed_requests === this.max_fails) {
|
|
629
|
-
this.logger.warn(
|
|
630
|
-
{
|
|
631
|
-
host: this.url.host,
|
|
632
|
-
},
|
|
633
|
-
'host @{host} is now offline'
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
this.last_request_time = Date.now();
|
|
538
|
+
return headers;
|
|
639
539
|
}
|
|
640
540
|
|
|
641
541
|
/**
|
|
@@ -693,24 +593,20 @@ class ProxyStorage implements IProxy {
|
|
|
693
593
|
if (noProxyItem[0] !== '.') {
|
|
694
594
|
noProxyItem = '.' + noProxyItem;
|
|
695
595
|
}
|
|
696
|
-
if (hostname.
|
|
596
|
+
if (hostname.endsWith(noProxyItem)) {
|
|
697
597
|
if (this.proxy) {
|
|
698
598
|
this.logger.debug(
|
|
699
599
|
{ url: this.url.href, rule: noProxyItem },
|
|
700
600
|
'not using proxy for @{url}, excluded by @{rule} rule'
|
|
701
601
|
);
|
|
702
|
-
|
|
703
|
-
this.proxy = false;
|
|
602
|
+
this.proxy = undefined;
|
|
704
603
|
}
|
|
705
604
|
break;
|
|
706
605
|
}
|
|
707
606
|
}
|
|
708
607
|
}
|
|
709
608
|
|
|
710
|
-
|
|
711
|
-
if (_.isString(this.proxy) === false) {
|
|
712
|
-
delete this.proxy;
|
|
713
|
-
} else {
|
|
609
|
+
if (typeof this.proxy === 'string') {
|
|
714
610
|
this.logger.debug(
|
|
715
611
|
{ url: this.url.href, proxy: this.proxy },
|
|
716
612
|
'using proxy @{proxy} for @{url}'
|