@mimik/request-retry 1.0.0 → 2.0.7
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/.eslintrc +17 -5
- package/.husky/pre-commit +4 -0
- package/.husky/pre-push +4 -0
- package/README.md +19 -3
- package/index.js +82 -26
- package/lib/rp-axios-wrapper.js +48 -0
- package/lib/validate.js +11 -11
- package/manual-test/httpMock.js +16 -0
- package/manual-test/man.js +23 -0
- package/manual-test/retryMockMan.js +60 -0
- package/manual-test/testRace.js +20 -19
- package/manual-test/testRetry.js +44 -42
- package/package.json +36 -35
- package/test/retryMock.js +8 -0
- package/test/rpRetry.spec.js +26 -16
- package/test/testEnv.js +18 -2
- package/.nyc_output/2c5af3ee-ee3e-4793-87db-2489e35a8e41.json +0 -1
- package/.nyc_output/a146b8c6-b874-4b4b-8aee-6ad533b39947.json +0 -1
- package/.nyc_output/processinfo/2c5af3ee-ee3e-4793-87db-2489e35a8e41.json +0 -1
- package/.nyc_output/processinfo/a146b8c6-b874-4b4b-8aee-6ad533b39947.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/Gulpfile.js +0 -42
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -79
- package/coverage/lcov-report/index.html +0 -97
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/rpRetry.spec.js.html +0 -1113
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -170
- package/coverage/lcov.info +0 -397
- package/package.json.bak +0 -60
package/.eslintrc
CHANGED
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
// Use this file as a starting point for your project's .eslintrc.
|
|
2
|
-
// Copy this file, and add rule overrides as needed.
|
|
3
1
|
{
|
|
2
|
+
"plugins": [
|
|
3
|
+
"@mimik/document-env",
|
|
4
|
+
"@mimik/dependencies"
|
|
5
|
+
],
|
|
4
6
|
"env": {
|
|
5
7
|
"node": true
|
|
6
8
|
},
|
|
9
|
+
"parserOptions": {
|
|
10
|
+
"ecmaVersion": 2020
|
|
11
|
+
},
|
|
7
12
|
"extends": "airbnb",
|
|
8
13
|
"rules": {
|
|
14
|
+
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
|
|
15
|
+
"import/no-unresolved": ["error", { "amd": true, "commonjs": true, "caseSensitiveStrict": true }],
|
|
9
16
|
"brace-style": [1, "stroustrup", {"allowSingleLine": true}],
|
|
10
|
-
"no-confusing-arrow": [0],
|
|
17
|
+
"no-confusing-arrow": [0], // arrow isnt confusing
|
|
11
18
|
"max-len": [1, 180, { "ignoreComments": true }],
|
|
12
19
|
"linebreak-style": 0,
|
|
13
20
|
"quotes": [1, "single"],
|
|
14
|
-
"semi": [1, "always"]
|
|
21
|
+
"semi": [1, "always"],
|
|
22
|
+
"no-process-env": ["error"],
|
|
23
|
+
"@mimik/document-env/validate-document-env": 2,
|
|
24
|
+
"@mimik/dependencies/case-sensitive": 2,
|
|
25
|
+
"@mimik/dependencies/no-cycles": 2,
|
|
26
|
+
"@mimik/dependencies/require-json-ext": 2
|
|
15
27
|
},
|
|
16
28
|
"settings":{
|
|
17
29
|
"react": {
|
|
18
|
-
"version": "
|
|
30
|
+
"version": "detect"
|
|
19
31
|
}
|
|
20
32
|
},
|
|
21
33
|
"globals": {
|
package/.husky/pre-push
ADDED
package/README.md
CHANGED
|
@@ -15,7 +15,15 @@ Make a request with retry.
|
|
|
15
15
|
**Category**: async
|
|
16
16
|
**Throws**:
|
|
17
17
|
|
|
18
|
-
- <code>Promise</code> Will throw an error generated by getRichError encapsulating an error generated by [request-promise](https://www.npmjs.com/package/request-promise) or a TimeoutError.
|
|
18
|
+
- <code>Promise</code> Will throw an error generated by `getRichError` encapsulating an error generated by [request-promise](https://www.npmjs.com/package/request-promise) or a TimeoutError.
|
|
19
|
+
|
|
20
|
+
The following environment variable are being used:
|
|
21
|
+
|
|
22
|
+
| Env variable name | Description | Default | Comments |
|
|
23
|
+
| ----------------- | ----------- | ------- | -------- |
|
|
24
|
+
| SERVER_TYPE | type of the server instance that makes the call | generic | for defining the agent
|
|
25
|
+
| SERVER_VERSION |version of the server instance that makes the call | 1.0 | for defining the agent
|
|
26
|
+
| SERVER_ID | id of the server instance that makes the call | generic | for defining the agent
|
|
19
27
|
|
|
20
28
|
The following properties are added to the `options` object under the `retry` property:
|
|
21
29
|
- retries `{int}`: maximum number of retries independent to the retryStrategy. The default is `2`. If the value is less than `0` the value is set to `0`, and if the value is more that `15` the value is set to `15`,
|
|
@@ -38,11 +46,19 @@ defaultRetry = (...args) => {
|
|
|
38
46
|
return (statusCode && (Math.floor(statusCode / 100) === 5 || statusCode === 429)) || (!statusCode && !args[1].message.includes('Invalid URI'));
|
|
39
47
|
};
|
|
40
48
|
```
|
|
49
|
+
If logLevel is empty, request and response will be logged as info and the response will be in response property with full details. Error on the request will generate a warning. If logLevel is not empty but not complete, logLevel will take control over default.
|
|
50
|
+
|
|
51
|
+
If not alredy set,the user agent will the set to `mimik-{serverType}/{serverVersion}/{serverId} {architecture} {node}`;
|
|
52
|
+
|
|
53
|
+
To facilitate the transition from request-promise the following action are taken on options:
|
|
54
|
+
- `uri` takes precednce on `url`
|
|
55
|
+
- `body` takes precedence on `json` if `json` is an object and are used to assign `data`
|
|
56
|
+
- `qs` is used to assign to `params`
|
|
41
57
|
|
|
42
58
|
**Requires**: <code>module:@mimik/sumologic-winston-logger</code>, <code>module:@mimik/response-helper</code>
|
|
43
|
-
**Fulfil**: <code>object</code> - Response of the [
|
|
59
|
+
**Fulfil**: <code>object</code> - Response of the [axios](https://www.npmjs.com/package/axios) response with option `resolveWithFullResponse` set to true otherwise only `response.data` is returned.
|
|
44
60
|
|
|
45
61
|
| Param | Type | Description |
|
|
46
62
|
| --- | --- | --- |
|
|
47
|
-
| options | <code>object</code> | Options for the request. Similar to [
|
|
63
|
+
| options | <code>object</code> | Options for the request. Similar to [axios](https://www.npmjs.com/package/axios) options. `validateStatus` options is disabled, an error will be created for statusCode outside of [200, 300[. The options `resolveWithFullResponse` is added and if set to true, the response will be a full axios response. If set to false or missing only `reponse.data` will be returned. |
|
|
48
64
|
|
package/index.js
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint no-process-env: "off" */
|
|
2
2
|
const Promise = require('bluebird');
|
|
3
|
+
const axios = require('axios');
|
|
3
4
|
|
|
4
5
|
const logger = require('@mimik/sumologic-winston-logger');
|
|
5
6
|
const { getRichError } = require('@mimik/response-helper');
|
|
6
|
-
|
|
7
|
+
const { getCorrelationId } = require('@mimik/request-helper');
|
|
7
8
|
const {
|
|
8
9
|
validateOptions,
|
|
9
10
|
calculateDelay,
|
|
10
11
|
isRetry,
|
|
11
12
|
setResponse,
|
|
12
13
|
} = require('./lib/validate');
|
|
14
|
+
const { rp } = require('./lib/rp-axios-wrapper');
|
|
15
|
+
|
|
16
|
+
const DEFAULT_RESPONSE_NAME = 'response';
|
|
17
|
+
const DEFAULT_LOGLEVEL_DETAILS = 'full';
|
|
18
|
+
const DEFAULT_LOGLEVEL_RESPONSE = 'info';
|
|
19
|
+
const DEFAULT_LOGLEVEL_ERROR = 'warn';
|
|
20
|
+
const DEFAULT_LOGLEVEL_REQUEST = 'info';
|
|
21
|
+
|
|
22
|
+
const nodeDescription = `${process.release.name} ${process.version} ${process.release.lts}`;
|
|
23
|
+
const serverDescription = `mimik-${process.env.SERVER_TYPE || 'generic'}/${process.env.SERVER_VERSION || '1.0'}/${process.env.SERVER_ID || 'generic'}`;
|
|
24
|
+
const platformDescription = `${process.platform}; ${process.arch}`;
|
|
25
|
+
const userAgent = `${serverDescription} (${platformDescription}; ${nodeDescription})`;
|
|
13
26
|
|
|
14
27
|
Promise.config({ cancellation: true });
|
|
15
28
|
|
|
@@ -26,10 +39,18 @@ Promise.config({ cancellation: true });
|
|
|
26
39
|
* @requires @mimik/sumologic-winston-logger
|
|
27
40
|
* @requires @mimik/response-helper
|
|
28
41
|
* @category async
|
|
29
|
-
* @param {object} options - Options for the request. Similar to [
|
|
42
|
+
* @param {object} options - Options for the request. Similar to [axios](https://www.npmjs.com/package/axios) options. `validateStatus` options is disabled, an error will be created for statusCode outside of [200, 300[. The options `resolveWithFullResponse` is added and if set to true, the response will be a full axios response. If set to false or missing only `reponse.data` will be returned.
|
|
30
43
|
* @return {Promise}.
|
|
31
|
-
* @fulfil {object} - Response of the [
|
|
32
|
-
* @throws {Promise} Will throw an error generated by getRichError encapsulating an error generated by [request-promise](https://www.npmjs.com/package/request-promise) or a TimeoutError.
|
|
44
|
+
* @fulfil {object} - Response of the [axios](https://www.npmjs.com/package/axios) response with option `resolveWithFullResponse` set to true otherwise only `response.data` is returned.
|
|
45
|
+
* @throws {Promise} Will throw an error generated by `getRichError` encapsulating an error generated by [request-promise](https://www.npmjs.com/package/request-promise) or a TimeoutError.
|
|
46
|
+
*
|
|
47
|
+
* The following environment variable are being used:
|
|
48
|
+
*
|
|
49
|
+
* | Env variable name | Description | Default | Comments |
|
|
50
|
+
* | ----------------- | ----------- | ------- | -------- |
|
|
51
|
+
* | SERVER_TYPE | type of the server instance that makes the call | generic | for defining the agent
|
|
52
|
+
* | SERVER_VERSION |version of the server instance that makes the call | 1.0 | for defining the agent
|
|
53
|
+
* | SERVER_ID | id of the server instance that makes the call | generic | for defining the agent
|
|
33
54
|
*
|
|
34
55
|
* The following properties are added to the `options` object under the `retry` property:
|
|
35
56
|
* - retries `{int}`: maximum number of retries independent to the retryStrategy. The default is `2`. If the value is less than `0` the value is set to `0`, and if the value is more that `15` the value is set to `15`,
|
|
@@ -52,32 +73,54 @@ Promise.config({ cancellation: true });
|
|
|
52
73
|
* return (statusCode && (Math.floor(statusCode / 100) === 5 || statusCode === 429)) || (!statusCode && !args[1].message.includes('Invalid URI'));
|
|
53
74
|
* };
|
|
54
75
|
*```
|
|
76
|
+
* If logLevel is empty, request and response will be logged as info and the response will be in response property with full details. Error on the request will generate a warning. If logLevel is not empty but not complete, logLevel will take control over default.
|
|
55
77
|
*
|
|
78
|
+
* If not alredy set,the user agent will the set to `mimik-{serverType}/{serverVersion}/{serverId} {architecture} {node}`;
|
|
79
|
+
*
|
|
80
|
+
* To facilitate the transition from request-promise the following action are taken on options:
|
|
81
|
+
* - `uri` takes precednce on `url`
|
|
82
|
+
* - `body` takes precedence on `json` if `json` is an object and are used to assign `data`
|
|
83
|
+
* - `qs` is used to assign to `params`
|
|
56
84
|
*/
|
|
57
|
-
const rpRetry = (
|
|
58
|
-
const
|
|
85
|
+
const rpRetry = (origOptions) => {
|
|
86
|
+
const { CancelToken } = axios;
|
|
87
|
+
const source = CancelToken.source();
|
|
88
|
+
const options = origOptions;
|
|
89
|
+
const correlationId = (options.headers && options.headers['x-correlation-id']) ? options.headers['x-correlation-id'] : getCorrelationId();
|
|
59
90
|
const errors = [];
|
|
60
91
|
const delayPromises = [];
|
|
92
|
+
const criteria = validateOptions(options, correlationId);
|
|
93
|
+
const { logLevel } = criteria;
|
|
94
|
+
const logLevelLength = Object.keys(logLevel).length;
|
|
61
95
|
let nbRetries = 0;
|
|
96
|
+
let mainTimeout;
|
|
97
|
+
|
|
98
|
+
if (!options.headers) options.headers = { 'user-agent': userAgent };
|
|
99
|
+
else if (!options.headers['user-agent']) options.headers['user-agent'] = userAgent;
|
|
100
|
+
options.cancelToken = source.token;
|
|
62
101
|
|
|
63
|
-
const retryProcess = (
|
|
102
|
+
const retryProcess = (nbRetry) => rp(options)
|
|
64
103
|
.then((response) => {
|
|
65
|
-
const info = { options
|
|
104
|
+
const info = { options, nbRetries, errors };
|
|
66
105
|
|
|
67
|
-
if (
|
|
68
|
-
info[
|
|
69
|
-
logger[
|
|
106
|
+
if (logLevel.response) {
|
|
107
|
+
info[logLevel.responseName] = setResponse(response, logLevel.responseDetails);
|
|
108
|
+
logger[logLevel.response]('request response', info, correlationId);
|
|
109
|
+
}
|
|
110
|
+
else if (logLevelLength === 0) {
|
|
111
|
+
info[DEFAULT_RESPONSE_NAME] = setResponse(response, DEFAULT_LOGLEVEL_DETAILS);
|
|
112
|
+
logger[DEFAULT_LOGLEVEL_RESPONSE]('request response', info, correlationId);
|
|
70
113
|
}
|
|
71
114
|
return response;
|
|
72
115
|
})
|
|
73
116
|
.catch((err) => {
|
|
74
|
-
if ((nbRetry <
|
|
117
|
+
if ((nbRetry < criteria.retries) && isRetry(options, nbRetry, criteria, err, correlationId)) {
|
|
75
118
|
nbRetries = nbRetry + 1;
|
|
76
|
-
errors.push(err
|
|
77
|
-
const delayPromise = Promise.delay(calculateDelay(
|
|
119
|
+
errors.push(err);
|
|
120
|
+
const delayPromise = Promise.delay(calculateDelay(options, nbRetry, criteria, err, correlationId));
|
|
78
121
|
|
|
79
122
|
delayPromises.unshift(delayPromise);
|
|
80
|
-
return delayPromise.then(() => retryProcess(
|
|
123
|
+
return delayPromise.then(() => retryProcess(nbRetry + 1));
|
|
81
124
|
}
|
|
82
125
|
const error = err;
|
|
83
126
|
|
|
@@ -88,36 +131,49 @@ const rpRetry = (options) => {
|
|
|
88
131
|
errors,
|
|
89
132
|
};
|
|
90
133
|
}
|
|
91
|
-
throw getRichError(
|
|
134
|
+
throw getRichError(
|
|
135
|
+
error.statusCode,
|
|
136
|
+
'request error response',
|
|
137
|
+
{ options },
|
|
138
|
+
error,
|
|
139
|
+
logLevel.error || ((logLevelLength === 0) ? DEFAULT_LOGLEVEL_ERROR : undefined),
|
|
140
|
+
correlationId,
|
|
141
|
+
);
|
|
92
142
|
});
|
|
93
143
|
|
|
94
|
-
|
|
95
|
-
const criteria = validateOptions(options);
|
|
96
|
-
const retryPromise = retryProcess(options, 0, criteria, correlationId);
|
|
144
|
+
const retryPromise = retryProcess(0);
|
|
97
145
|
const mainTimeoutPromise = new Promise((resolve, reject) => {
|
|
98
146
|
mainTimeout = setTimeout(() => {
|
|
99
|
-
delayPromises.forEach((delayPromise) => delayPromise.cancel());
|
|
100
|
-
|
|
147
|
+
delayPromises.forEach((delayPromise) => delayPromise.cancel(`retry timeout, ${criteria.timeout}, ${nbRetries}`));
|
|
148
|
+
source.cancel(`retry timeout, ${criteria.timeout}, ${nbRetries}`);
|
|
101
149
|
let error = new Error('retry timeout');
|
|
102
150
|
|
|
103
151
|
if (nbRetries !== 0) {
|
|
104
152
|
error.info = { retry: { nbRetries, errors } };
|
|
105
153
|
}
|
|
106
154
|
error.name = 'TimeoutError';
|
|
107
|
-
error = getRichError(
|
|
155
|
+
error = getRichError(
|
|
156
|
+
'System',
|
|
157
|
+
'request error response',
|
|
158
|
+
{ options },
|
|
159
|
+
error,
|
|
160
|
+
logLevel.error || ((logLevelLength === 0) ? DEFAULT_LOGLEVEL_ERROR : undefined),
|
|
161
|
+
correlationId,
|
|
162
|
+
);
|
|
108
163
|
reject(error);
|
|
109
164
|
}, criteria.timeout * 1000);
|
|
110
165
|
});
|
|
111
166
|
|
|
112
|
-
if (
|
|
167
|
+
if (logLevel.request) logger[logLevel.request]('making a request', { options, criteria }, correlationId);
|
|
168
|
+
else if (logLevelLength === 0) logger[DEFAULT_LOGLEVEL_REQUEST]('making a request', { options, criteria }, correlationId);
|
|
113
169
|
return Promise.race([retryPromise, mainTimeoutPromise])
|
|
114
170
|
.then((result) => {
|
|
115
|
-
mainTimeoutPromise.cancel();
|
|
171
|
+
mainTimeoutPromise.cancel('main timeout');
|
|
116
172
|
clearTimeout(mainTimeout);
|
|
117
173
|
return result;
|
|
118
174
|
})
|
|
119
175
|
.catch((err) => {
|
|
120
|
-
mainTimeoutPromise.cancel();
|
|
176
|
+
mainTimeoutPromise.cancel(`main timeout error: ${err.message}`);
|
|
121
177
|
clearTimeout(mainTimeout);
|
|
122
178
|
throw err;
|
|
123
179
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
|
|
4
|
+
const { getRichError } = require('@mimik/response-helper');
|
|
5
|
+
|
|
6
|
+
const rp = (origOptions) => {
|
|
7
|
+
const options = _.clone(origOptions);
|
|
8
|
+
|
|
9
|
+
if (options.uri) options.url = options.uri;
|
|
10
|
+
delete options.uri;
|
|
11
|
+
delete options.validateStatus;
|
|
12
|
+
if (options.body) {
|
|
13
|
+
options.data = options.body;
|
|
14
|
+
delete options.body;
|
|
15
|
+
}
|
|
16
|
+
else if (typeof options.json === 'object') {
|
|
17
|
+
options.data = options.body;
|
|
18
|
+
delete options.json;
|
|
19
|
+
}
|
|
20
|
+
else delete options.json;
|
|
21
|
+
if (options.qs) options.params = options.qs;
|
|
22
|
+
return axios(options)
|
|
23
|
+
.then((res) => {
|
|
24
|
+
if (options.resolveWithFullResponse) return res;
|
|
25
|
+
return res.data;
|
|
26
|
+
})
|
|
27
|
+
.catch((err) => {
|
|
28
|
+
const { response } = err;
|
|
29
|
+
let errorMsg = 'no response';
|
|
30
|
+
|
|
31
|
+
if (err.message) errorMsg = `${errorMsg}: ${err.message}`;
|
|
32
|
+
if (!response) {
|
|
33
|
+
throw getRichError('System', errorMsg, {
|
|
34
|
+
code: err.code,
|
|
35
|
+
address: err.address,
|
|
36
|
+
port: err.port,
|
|
37
|
+
syscall: err.syscall,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const { data } = response;
|
|
41
|
+
|
|
42
|
+
throw getRichError(response.status, data ? data.message || response.statusText : response.statusText, data);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
rp,
|
|
48
|
+
};
|
package/lib/validate.js
CHANGED
|
@@ -3,7 +3,7 @@ const logger = require('@mimik/sumologic-winston-logger');
|
|
|
3
3
|
const DETAILS = ['full', 'type', 'count'];
|
|
4
4
|
const TYPE = DETAILS[1];
|
|
5
5
|
const RESPONSE = 'response';
|
|
6
|
-
const
|
|
6
|
+
const DEFAULT_LOGLEVEL = logger.LEVELS[5]; // silly
|
|
7
7
|
const DEFAULT_RETRIES = 2;
|
|
8
8
|
const MIN_RETRIES = 0;
|
|
9
9
|
const MAX_RETRIES = 15;
|
|
@@ -22,7 +22,7 @@ const defaultRetry = (...args) => {
|
|
|
22
22
|
const unknownRetry = () => true;
|
|
23
23
|
const defaultDelay = () => DEFAULT_DELAY;
|
|
24
24
|
|
|
25
|
-
const validateOptions = (options) => {
|
|
25
|
+
const validateOptions = (options, correlationId) => {
|
|
26
26
|
if (!options.retry) {
|
|
27
27
|
return {
|
|
28
28
|
delay: DEFAULT_DELAY,
|
|
@@ -44,22 +44,22 @@ const validateOptions = (options) => {
|
|
|
44
44
|
const validateLogLevel = (val, name) => {
|
|
45
45
|
if (!val[name]) return null;
|
|
46
46
|
if (!logger.LEVELS.includes(val[name])) {
|
|
47
|
-
logger.warn(`invalid logLevel.${name}, reset to default`, { name, value: val[name], default:
|
|
48
|
-
return
|
|
47
|
+
logger.warn(`invalid logLevel.${name}, reset to default`, { name, value: val[name], default: DEFAULT_LOGLEVEL }, correlationId);
|
|
48
|
+
return DEFAULT_LOGLEVEL;
|
|
49
49
|
}
|
|
50
50
|
return val[name];
|
|
51
51
|
};
|
|
52
52
|
const validateValue = (val, name, defaultValue, minValue, maxValue) => {
|
|
53
53
|
if (typeof val !== 'number') {
|
|
54
|
-
if (val) logger.warn(`invalid ${name}, reset to default`, { type: typeof val, defaultValue });
|
|
54
|
+
if (val) logger.warn(`invalid ${name}, reset to default`, { type: typeof val, defaultValue }, correlationId);
|
|
55
55
|
return defaultValue;
|
|
56
56
|
}
|
|
57
57
|
if (val < minValue) {
|
|
58
|
-
logger.warn(`out of scope ${name}, reset to min`, { value: val, minValue });
|
|
58
|
+
logger.warn(`out of scope ${name}, reset to min`, { value: val, minValue }, correlationId);
|
|
59
59
|
return minValue;
|
|
60
60
|
}
|
|
61
61
|
if (val > maxValue) {
|
|
62
|
-
logger.warn(`out of scope ${name}, reset to max`, { value: val, maxValue });
|
|
62
|
+
logger.warn(`out of scope ${name}, reset to max`, { value: val, maxValue }, correlationId);
|
|
63
63
|
return maxValue;
|
|
64
64
|
}
|
|
65
65
|
return val;
|
|
@@ -67,7 +67,7 @@ const validateOptions = (options) => {
|
|
|
67
67
|
const validateResponseDetails = (val, name, defaultValue) => {
|
|
68
68
|
if (!val) return defaultValue;
|
|
69
69
|
if (!DETAILS.includes(val)) {
|
|
70
|
-
logger.warn(`invalid logLevel.${name}, reset to default`, { name, value: val, defaultValue });
|
|
70
|
+
logger.warn(`invalid logLevel.${name}, reset to default`, { name, value: val, defaultValue }, correlationId);
|
|
71
71
|
return defaultValue;
|
|
72
72
|
}
|
|
73
73
|
return val;
|
|
@@ -75,7 +75,7 @@ const validateOptions = (options) => {
|
|
|
75
75
|
const validateResponseName = (val, name, defaultValue) => {
|
|
76
76
|
if (!val && val !== '') return defaultValue;
|
|
77
77
|
if (typeof val !== 'string' || val.length === 0 || val.trim().length === 0) {
|
|
78
|
-
logger.warn(`invalid logLevel.${name}, reset to default`, { name, value: val, defaultValue });
|
|
78
|
+
logger.warn(`invalid logLevel.${name}, reset to default`, { name, value: val, defaultValue }, correlationId);
|
|
79
79
|
return defaultValue;
|
|
80
80
|
}
|
|
81
81
|
return val;
|
|
@@ -88,14 +88,14 @@ const validateOptions = (options) => {
|
|
|
88
88
|
if (typeof delayStrategy !== 'function') {
|
|
89
89
|
if (!delayStrategy) delayStrategy = defaultDelay;
|
|
90
90
|
else {
|
|
91
|
-
logger.warn('invalid delayStrategy, using delay', { strategyType: typeof delayStrategy, delay, default: DEFAULT_DELAY });
|
|
91
|
+
logger.warn('invalid delayStrategy, using delay', { strategyType: typeof delayStrategy, delay, default: DEFAULT_DELAY }, correlationId);
|
|
92
92
|
delayStrategy = setDelay;
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
if (typeof retryStrategy !== 'function') {
|
|
96
96
|
if (!retryStrategy) retryStrategy = defaultRetry;
|
|
97
97
|
else {
|
|
98
|
-
logger.warn('invalid retryStrategy, ignoring', { strategyType: typeof retryStrategy, using: unknownRetry });
|
|
98
|
+
logger.warn('invalid retryStrategy, ignoring', { strategyType: typeof retryStrategy, using: unknownRetry }, correlationId);
|
|
99
99
|
retryStrategy = unknownRetry;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
const express = require('express');
|
|
3
|
+
const bodyParser = require('body-parser');
|
|
4
|
+
|
|
5
|
+
const app = express();
|
|
6
|
+
|
|
7
|
+
app.use(bodyParser.json());
|
|
8
|
+
app.get('/success', (req, res) => {
|
|
9
|
+
res.statusCode = 200;
|
|
10
|
+
|
|
11
|
+
res.send({ statusCode: 200 });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
app.listen(9000, () => {
|
|
15
|
+
console.log('----->', `retry mock at ${9000}`);
|
|
16
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
require('../test/testEnv');
|
|
3
|
+
|
|
4
|
+
const { rpRetry } = require('../index');
|
|
5
|
+
|
|
6
|
+
const correlationId = `--request-retry-test--/${new Date().toISOString()}`;
|
|
7
|
+
|
|
8
|
+
const options = {
|
|
9
|
+
method: 'GET',
|
|
10
|
+
headers: {
|
|
11
|
+
'x-correlation-id': correlationId,
|
|
12
|
+
},
|
|
13
|
+
url: 'http://localhost:9080/test/retry',
|
|
14
|
+
json: true,
|
|
15
|
+
retry: {},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
options.retry.delayStrategy = () => 10000;
|
|
19
|
+
options.retry.timeout = 10;
|
|
20
|
+
options.retry.retries = 2;
|
|
21
|
+
|
|
22
|
+
rpRetry(options)
|
|
23
|
+
.catch((err) => console.log(err));
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
const express = require('express');
|
|
3
|
+
const bodyParser = require('body-parser');
|
|
4
|
+
|
|
5
|
+
const app = express();
|
|
6
|
+
const config = {
|
|
7
|
+
port: 9080,
|
|
8
|
+
path: '/retry',
|
|
9
|
+
base: '/test',
|
|
10
|
+
get: {
|
|
11
|
+
nbRetry: 400,
|
|
12
|
+
success: 200,
|
|
13
|
+
error: 500,
|
|
14
|
+
},
|
|
15
|
+
post: {
|
|
16
|
+
success: 201,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
let nbRequest = 0;
|
|
20
|
+
let time = 0;
|
|
21
|
+
let message;
|
|
22
|
+
|
|
23
|
+
app.use(bodyParser.json());
|
|
24
|
+
app.get(`${config.base}${config.path}`, (req, res) => {
|
|
25
|
+
if (nbRequest === 0) message = 'Recieved first request';
|
|
26
|
+
else {
|
|
27
|
+
const timeLap = Date.now() - time;
|
|
28
|
+
|
|
29
|
+
message = `Received ${nbRequest} retry with timelap ${timeLap}`;
|
|
30
|
+
}
|
|
31
|
+
console.log('----->', message);
|
|
32
|
+
time = Date.now();
|
|
33
|
+
|
|
34
|
+
if (nbRequest === config.get.nbRetry) {
|
|
35
|
+
res.statusCode = config.get.success;
|
|
36
|
+
res.send({ statusCode: config.get.success });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
nbRequest += 1;
|
|
40
|
+
res.statusCode = config.get.error;
|
|
41
|
+
|
|
42
|
+
res.send({ statusCode: config.get.error });
|
|
43
|
+
});
|
|
44
|
+
app.post(`${config.base}${config.path}`, (req, res) => {
|
|
45
|
+
console.log('----->', 'Received a POST request');
|
|
46
|
+
res.statusCode = config.post.success;
|
|
47
|
+
res.send({ statusCode: config.post.success });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
app.post('/logs/:id', (req, res) => {
|
|
51
|
+
console.log('--->', 'Recieved a log');
|
|
52
|
+
console.log('---> id:', req.params.id);
|
|
53
|
+
console.log('---> body:', req.body);
|
|
54
|
+
res.statusCode = config.post.success;
|
|
55
|
+
res.send({ statusCode: config.post.success });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
app.listen(config.port, () => {
|
|
59
|
+
console.log('----->', `retry mock at ${config.port}`);
|
|
60
|
+
});
|
package/manual-test/testRace.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
const doSomething = () => {
|
|
3
|
+
let add = 0;
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
for (let i = 0; i < 100000000; i += 1) {
|
|
6
|
+
add += 1;
|
|
7
|
+
console.log(add);
|
|
8
|
+
}
|
|
9
|
+
return add;
|
|
9
10
|
};
|
|
10
11
|
const timeout = 1; // ms
|
|
11
12
|
|
|
12
13
|
const race = () => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
let result;
|
|
15
|
+
const mainTimeout = setTimeout(() => {
|
|
16
|
+
throw new Error('timeout');
|
|
17
|
+
}, timeout);
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
19
|
+
try { result = doSomething(); }
|
|
20
|
+
catch (err) {
|
|
21
|
+
console.log(err);
|
|
22
|
+
result = 0;
|
|
23
|
+
}
|
|
24
|
+
clearTimeout(mainTimeout);
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
26
27
|
|
|
27
28
|
console.log(race());
|
package/manual-test/testRetry.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
const { rpRetry } = require('../index');
|
|
2
3
|
|
|
3
4
|
const options = {
|
|
4
5
|
method: 'GET',
|
|
@@ -9,49 +10,50 @@ const options = {
|
|
|
9
10
|
json: true,
|
|
10
11
|
timeout: 200,
|
|
11
12
|
// retry: {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
//}
|
|
13
|
+
// all default: maxRetry: 2, retryDelay: 1000ms, maxTimeout: 50s, logLevel: 3 silly, type response, retryDelayStrategy: setDelay, retryStrategy: defaultRetry
|
|
14
|
+
// retries: -1, // => 0
|
|
15
|
+
// retries: 20, // => 15
|
|
16
|
+
// retries: 'joe', // => 2
|
|
17
|
+
// retries: 0, // OK
|
|
18
|
+
// delay: 0, // => 20ms
|
|
19
|
+
// delay: 100000, // => 30000ms
|
|
20
|
+
// delay: 'joe', // => 1000ms
|
|
21
|
+
// delay: 1, // => 20ms
|
|
22
|
+
// delayStrategy: 'joe', // => setDelay returning retryDelay
|
|
23
|
+
// delayStrategy: function badDelay() { return 'joe' }, // => no change then bad delay strategy => retryDelay
|
|
24
|
+
// delayStrategy: function userDelay(...args) { return (Math.pow(2, args[0]) * 100) + Math.floor(Math.random() * 50) }, // OK
|
|
25
|
+
// retryStrategy: 'joe', // => unknowRetry returning false
|
|
26
|
+
// retryStrategy: function badRetry() { return 'joe' }, // => no change then bad retry strategy => false
|
|
27
|
+
// retryStrategy: function userRetry(...args) { // OK
|
|
28
|
+
// const { statusCode } = args[1]; // err
|
|
29
|
+
//
|
|
30
|
+
// return statusCode && (Math.floor(statusCode/100) !== 5 || statusCode !== 429); // retry if there is no statusCode or if there is 500 range statucCode or 429
|
|
31
|
+
// },
|
|
32
|
+
// retryStrategy: function invalidRetry(...args) { // defaultRetry
|
|
33
|
+
// const { statusCode } = args[34]; // null value
|
|
34
|
+
//
|
|
35
|
+
// return true // no retry
|
|
36
|
+
// },
|
|
37
|
+
// timeout: 0, // => 10s
|
|
38
|
+
// timeout: 150, // => 120s
|
|
39
|
+
// timeout: 'joe', // => 50s
|
|
40
|
+
// logLevel: {
|
|
41
|
+
// response: 'test', // silly with warning
|
|
42
|
+
// error: 1234, // => silly with warning
|
|
43
|
+
// request: null, // => silly no warning
|
|
44
|
+
// responseDetails: 'test', // => type with warning
|
|
45
|
+
// responseName: '', // => response with warning
|
|
46
|
+
// },
|
|
47
|
+
// logLevel: {
|
|
48
|
+
// response: 'info', // ok all the other value to default no warning
|
|
49
|
+
// },
|
|
50
|
+
// }
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
const sDate = Date.now();
|
|
53
54
|
|
|
54
55
|
rpRetry(options)
|
|
55
|
-
.then(response => console.log('success', { response: typeof response }, Date.now() - sDate))
|
|
56
|
-
.catch(err => {
|
|
57
|
-
|
|
56
|
+
.then((response) => console.log('success', { response: typeof response }, Date.now() - sDate))
|
|
57
|
+
.catch((err) => {
|
|
58
|
+
console.log('failure', { error: err, info: err.info }, Date.now() - sDate);
|
|
59
|
+
});
|