fix(api): return 503 to npm/yarn on uplink connection timeout (#1331)

fix  #1328 and #720

Type: bug

The following has been addressed in the PR:

Instead of returning a 404 (Not Found) when npm, yarn, etc requests a package and the package cannot be acquired from an uplink due to a connection timeout, socket timeout, or connection reset problem, a 503 (service unavailable) is returned by Verdaccio instead. In limited testing of a few versions of npm and yarn, both of these clients correctly attempt to retry the request when a 503 is returned.

Added functional tests to verify the behavior (this adds a dev dependency on nock, which provides HTTP request mocking

Description:

This resolves issue #1328 and #720, and ensures npm/yarn install commands don't fail immediately when there is an intermittent network timeout problem with an uplink. Instead Verdaccio will appropriately respond to the client with a 503. A 404 response (current behavior) incorrectly tells the client that the package does not exist (which may or may not be true) and to not try again.
This commit is contained in:
Will Smythe 2019-06-13 15:42:01 -04:00 committed by Juan Picado @jotadeveloper
parent f242d1b261
commit eb7a8e3528
8 changed files with 164 additions and 8 deletions

View File

@ -73,6 +73,7 @@
"jest": "24.7.1",
"jest-environment-node": "24.7.1",
"lint-staged": "8.1.5",
"nock": "10.0.6",
"prettier": "1.17.0",
"puppeteer": "1.8.0",
"rimraf": "2.6.3",

View File

@ -411,13 +411,13 @@ class Storage implements IStorageHandler {
returns callback(err, result, uplink_errors)
*/
_syncUplinksMetadata(name: string, packageInfo: Package, options: ISyncUplinks, callback: Callback): void {
let exists = true;
let found = true;
const self = this;
const upLinks = [];
const hasToLookIntoUplinks = _.isNil(options.uplinksLook) || options.uplinksLook;
if (!packageInfo) {
exists = false;
found = false;
packageInfo = generatePackageTemplate(name);
}
@ -489,15 +489,37 @@ class Storage implements IStorageHandler {
// if we got to this point, assume that the correct package exists
// on the uplink
exists = true;
found = true;
cb();
});
},
(err: Error, upLinksErrors: any) => {
assert(!err && Array.isArray(upLinksErrors));
if (!exists) {
// Check for connection timeout or reset errors with uplink(s)
// (these should be handled differently from the package not being found)
if (!found) {
let uplinkTimeoutError;
for (let i = 0; i < upLinksErrors.length; i++) {
if (upLinksErrors[i]) {
for (let j = 0; j < upLinksErrors[i].length; j++) {
if (upLinksErrors[i][j]) {
const code = upLinksErrors[i][j].code;
if (code === 'ETIMEDOUT' || code === 'ESOCKETTIMEDOUT' || code === 'ECONNRESET') {
uplinkTimeoutError = true;
break;
}
}
}
}
}
if (uplinkTimeoutError) {
return callback(ErrorCode.getServiceUnavailable(), null, upLinksErrors);
} else {
return callback(ErrorCode.getNotFound(API_ERROR.NO_PACKAGE), null, upLinksErrors);
}
}
if (upLinks.length === 0) {
return callback(null, packageInfo);

View File

@ -0,0 +1,21 @@
const nock = require('nock');
function Plugin(config) {
const self = Object.create(Plugin.prototype);
self._config = config;
return self;
}
Plugin.prototype.register_middlewares = function (app, auth, storage) {
app.get('/test-uplink-timeout-*', function (req, res, next) {
nock('http://localhost:55552')
.get(req.path)
.socketDelay(31000).reply(200); // 31s is greater than the default 30s connection timeout
next();
});
}
module.exports = Plugin;

View File

@ -28,6 +28,7 @@ import race from './performance/race';
import pluginsAuth from './plugins/auth';
import middleware from './plugins/middleware';
import upLinkCache from './uplinks/cache';
import uplinkTimeout from './uplinks/timeout';
describe('functional test verdaccio', function() {
jest.setTimeout(10000);
@ -55,6 +56,7 @@ describe('functional test verdaccio', function() {
addtag(server1);
pluginsAuth(server2);
notify(app);
uplinkTimeout(server1, server2, server3);
// requires packages published to server1/server2
upLinkCache(server1, server2, server3);
adduser(server1);

View File

@ -6,6 +6,10 @@ web:
enable: true
title: verdaccio-server-1
middlewares:
../fixtures/plugins/middlewares.uplink:
message: provides uplink mocking (e.g. simulates socket timeout)
auth:
auth-memory:
users:
@ -116,6 +120,12 @@ packages:
publish: $authenticated
storage: sub_storage
'test-uplink-timeout-*':
access: $all
proxy:
- server2
- server3
'*':
access: test $anonymous
publish: test $anonymous

View File

@ -35,6 +35,10 @@ packages:
access: $all
proxy: server2
'test-uplink-timeout-*':
access: $all
publish: $all
'*':
access: $all

View File

@ -0,0 +1,27 @@
import {HTTP_STATUS} from "../../../src/lib/constants";
const PKG_SINGLE_UPLINK = 'test-uplink-timeout-single';
const PKG_MULTIPLE_UPLINKS = 'test-uplink-timeout-multiple';
export default function (server, server2, server3) {
describe('uplink connection timeouts', () => {
beforeAll(async () => {
await server2.addPackage(PKG_SINGLE_UPLINK).status(HTTP_STATUS.CREATED);
await server2.addPackage(PKG_MULTIPLE_UPLINKS).status(HTTP_STATUS.CREATED);
await server3.addPackage(PKG_MULTIPLE_UPLINKS).status(HTTP_STATUS.CREATED);
});
describe('get package', () => {
test('503 response when uplink connection ESOCKETTIMEDOUT', () => {
return server.getPackage(PKG_SINGLE_UPLINK).status(HTTP_STATUS.SERVICE_UNAVAILABLE);
});
test('200 response even though one uplink timesout', () => {
return server.getPackage(PKG_MULTIPLE_UPLINKS).status(HTTP_STATUS.OK)
});
});
});
}

View File

@ -1742,6 +1742,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
resolved "https://registry.verdaccio.org/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
assertion-error@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
assign-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.verdaccio.org/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
@ -2368,6 +2373,18 @@ caseless@~0.12.0:
resolved "https://registry.verdaccio.org/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
chai@^4.1.2:
version "4.2.0"
resolved "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5"
integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==
dependencies:
assertion-error "^1.1.0"
check-error "^1.0.2"
deep-eql "^3.0.1"
get-func-name "^2.0.0"
pathval "^1.1.0"
type-detect "^4.0.5"
chalk@2.3.1:
version "2.3.1"
resolved "https://registry.verdaccio.org/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
@ -2402,6 +2419,11 @@ chardet@^0.7.0:
resolved "https://registry.verdaccio.org/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
check-error@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
chokidar@^2.0.3:
version "2.1.5"
resolved "https://registry.verdaccio.org/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d"
@ -3000,6 +3022,18 @@ dedent@^0.7.0:
resolved "https://registry.verdaccio.org/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
deep-eql@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==
dependencies:
type-detect "^4.0.0"
deep-equal@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.verdaccio.org/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
@ -3976,6 +4010,11 @@ get-caller-file@^1.0.1:
resolved "https://registry.verdaccio.org/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
get-func-name@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.0"
resolved "https://registry.verdaccio.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz#b877b49a5c16aefac3655f2ed2ea5b684df8d203"
@ -5712,7 +5751,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.verdaccio.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@4.17.11, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.2.1:
lodash@4.17.11, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1:
version "4.17.11"
resolved "https://registry.verdaccio.org/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
@ -6172,6 +6211,21 @@ nice-try@^1.0.4:
resolved "https://registry.verdaccio.org/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
nock@10.0.6:
version "10.0.6"
resolved "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz#e6d90ee7a68b8cfc2ab7f6127e7d99aa7d13d111"
integrity sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==
dependencies:
chai "^4.1.2"
debug "^4.1.0"
deep-equal "^1.0.0"
json-stringify-safe "^5.0.1"
lodash "^4.17.5"
mkdirp "^0.5.0"
propagate "^1.0.0"
qs "^6.5.1"
semver "^5.5.0"
node-fetch@^2.2.0:
version "2.3.0"
resolved "https://registry.verdaccio.org/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5"
@ -6695,6 +6749,11 @@ path-type@^3.0.0:
dependencies:
pify "^3.0.0"
pathval@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA=
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.verdaccio.org/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
@ -6837,6 +6896,11 @@ prompts@^2.0.1:
kleur "^3.0.2"
sisteransi "^1.0.0"
propagate@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz#00c2daeedda20e87e3782b344adba1cddd6ad709"
integrity sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=
property-expr@^1.5.0:
version "1.5.1"
resolved "https://registry.npmjs.org/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f"
@ -8165,6 +8229,11 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
type-detect@^4.0.0, type-detect@^4.0.5:
version "4.0.8"
resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
type-fest@^0.4.1:
version "0.4.1"
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8"