got 9.2.1 → 9.3.2
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/package.json +15 -12
- package/readme.md +131 -13
- package/source/as-promise.js +40 -42
- package/source/as-stream.js +8 -31
- package/source/create.js +24 -14
- package/source/errors.js +9 -5
- package/source/get-response.js +4 -33
- package/source/index.js +15 -7
- package/source/known-hook-events.js +6 -1
- package/source/merge.js +41 -0
- package/source/normalize-arguments.js +123 -132
- package/source/progress.js +34 -5
- package/source/request-as-event-emitter.js +171 -109
- package/source/{deep-freeze.js → utils/deep-freeze.js} +0 -0
- package/source/{get-body-size.js → utils/get-body-size.js} +0 -4
- package/source/{is-form-data.js → utils/is-form-data.js} +0 -0
- package/source/{is-retry-on-network-error-allowed.js → utils/is-retry-on-network-error-allowed.js} +4 -1
- package/source/utils/timed-out.js +153 -0
- package/source/{url-to-options.js → utils/url-to-options.js} +0 -0
- package/source/merge-instances.js +0 -36
- package/source/timed-out.js +0 -160
package/source/merge.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const {URL} = require('url');
|
|
3
3
|
const is = require('@sindresorhus/is');
|
|
4
|
+
const knownHookEvents = require('./known-hook-events');
|
|
4
5
|
|
|
5
6
|
const merge = (target, ...sources) => {
|
|
6
7
|
for (const source of sources) {
|
|
@@ -29,4 +30,44 @@ const merge = (target, ...sources) => {
|
|
|
29
30
|
return target;
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
const mergeOptions = (...sources) => {
|
|
34
|
+
sources = sources.map(source => source || {});
|
|
35
|
+
const merged = merge({}, ...sources);
|
|
36
|
+
|
|
37
|
+
const hooks = {};
|
|
38
|
+
for (const hook of knownHookEvents) {
|
|
39
|
+
hooks[hook] = [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const source of sources) {
|
|
43
|
+
if (source.hooks) {
|
|
44
|
+
for (const hook of knownHookEvents) {
|
|
45
|
+
hooks[hook] = hooks[hook].concat(source.hooks[hook]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
merged.hooks = hooks;
|
|
51
|
+
|
|
52
|
+
return merged;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const mergeInstances = (instances, methods) => {
|
|
56
|
+
const handlers = instances.map(instance => instance.defaults.handler);
|
|
57
|
+
const size = instances.length - 1;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
methods,
|
|
61
|
+
options: mergeOptions(...instances.map(instance => instance.defaults.options)),
|
|
62
|
+
handler: (options, next) => {
|
|
63
|
+
let iteration = -1;
|
|
64
|
+
const iterate = options => handlers[++iteration](options, iteration === size ? next : iterate);
|
|
65
|
+
|
|
66
|
+
return iterate(options);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
32
71
|
module.exports = merge;
|
|
72
|
+
module.exports.options = mergeOptions;
|
|
73
|
+
module.exports.instances = mergeInstances;
|
|
@@ -1,64 +1,102 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const {URL, URLSearchParams} = require('url'); // TODO: Use the `URL` global when targeting Node.js 10
|
|
3
|
+
const urlLib = require('url');
|
|
3
4
|
const is = require('@sindresorhus/is');
|
|
4
|
-
const toReadableStream = require('to-readable-stream');
|
|
5
5
|
const urlParseLax = require('url-parse-lax');
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
6
|
+
const lowercaseKeys = require('lowercase-keys');
|
|
7
|
+
const isRetryOnNetworkErrorAllowed = require('./utils/is-retry-on-network-error-allowed');
|
|
8
|
+
const urlToOptions = require('./utils/url-to-options');
|
|
9
|
+
const isFormData = require('./utils/is-form-data');
|
|
10
10
|
const merge = require('./merge');
|
|
11
|
+
const knownHookEvents = require('./known-hook-events');
|
|
11
12
|
|
|
12
13
|
const retryAfterStatusCodes = new Set([413, 429, 503]);
|
|
13
14
|
|
|
14
|
-
// `preNormalize` handles things
|
|
15
|
-
// While `normalize` does `preNormalize` + handles things
|
|
16
|
-
const preNormalize = options => {
|
|
17
|
-
options
|
|
18
|
-
headers
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
// `preNormalize` handles static things (lowercasing headers; normalizing baseUrl, timeout, retry)
|
|
16
|
+
// While `normalize` does `preNormalize` + handles things which need to be reworked when user changes them
|
|
17
|
+
const preNormalize = (options, defaults) => {
|
|
18
|
+
if (is.nullOrUndefined(options.headers)) {
|
|
19
|
+
options.headers = {};
|
|
20
|
+
} else {
|
|
21
|
+
options.headers = lowercaseKeys(options.headers);
|
|
22
|
+
}
|
|
21
23
|
|
|
22
24
|
if (options.baseUrl && !options.baseUrl.toString().endsWith('/')) {
|
|
23
25
|
options.baseUrl += '/';
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
if (
|
|
27
|
-
options.
|
|
28
|
+
if (options.stream) {
|
|
29
|
+
options.json = false;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
if (is.nullOrUndefined(options.hooks)) {
|
|
31
33
|
options.hooks = {};
|
|
34
|
+
} else if (!is.object(options.hooks)) {
|
|
35
|
+
throw new TypeError(`Parameter \`hooks\` must be an object, not ${is(options.hooks)}`);
|
|
32
36
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
options.hooks[
|
|
38
|
-
} else if (is.array(hooks)) {
|
|
39
|
-
for (const [index, hook] of hooks.entries()) {
|
|
40
|
-
if (!is.function(hook)) {
|
|
41
|
-
throw new TypeError(
|
|
42
|
-
`Parameter \`hooks.${hookEvent}[${index}]\` must be a function, not ${is(hook)}`
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
37
|
+
|
|
38
|
+
for (const event of knownHookEvents) {
|
|
39
|
+
if (is.nullOrUndefined(options.hooks[event])) {
|
|
40
|
+
if (defaults) {
|
|
41
|
+
options.hooks[event] = [...defaults.hooks[event]];
|
|
46
42
|
} else {
|
|
47
|
-
|
|
43
|
+
options.hooks[event] = [];
|
|
48
44
|
}
|
|
49
45
|
}
|
|
50
|
-
}
|
|
51
|
-
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (is.number(options.timeout)) {
|
|
49
|
+
options.gotTimeout = {request: options.timeout};
|
|
50
|
+
} else if (is.object(options.timeout)) {
|
|
51
|
+
options.gotTimeout = options.timeout;
|
|
52
|
+
}
|
|
53
|
+
delete options.timeout;
|
|
54
|
+
|
|
55
|
+
const {retry} = options;
|
|
56
|
+
options.retry = {
|
|
57
|
+
retries: 0,
|
|
58
|
+
methods: [],
|
|
59
|
+
statusCodes: []
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (is.nonEmptyObject(defaults) && retry !== false) {
|
|
63
|
+
options.retry = {...defaults.retry};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (retry !== false) {
|
|
67
|
+
if (is.number(retry)) {
|
|
68
|
+
options.retry.retries = retry;
|
|
69
|
+
} else {
|
|
70
|
+
options.retry = {...options.retry, ...retry};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (options.gotTimeout) {
|
|
75
|
+
options.retry.maxRetryAfter = Math.min(...[options.gotTimeout.request, options.gotTimeout.connection].filter(n => !is.nullOrUndefined(n)));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (is.array(options.retry.methods)) {
|
|
79
|
+
options.retry.methods = new Set(options.retry.methods.map(method => method.toUpperCase()));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (is.array(options.retry.statusCodes)) {
|
|
83
|
+
options.retry.statusCodes = new Set(options.retry.statusCodes);
|
|
52
84
|
}
|
|
53
85
|
|
|
54
86
|
return options;
|
|
55
87
|
};
|
|
56
88
|
|
|
57
|
-
|
|
58
|
-
|
|
89
|
+
const normalize = (url, options, defaults) => {
|
|
90
|
+
if (is.plainObject(url)) {
|
|
91
|
+
options = {...url, ...options};
|
|
92
|
+
url = options.url || {};
|
|
93
|
+
delete options.url;
|
|
94
|
+
}
|
|
59
95
|
|
|
60
|
-
if (
|
|
61
|
-
|
|
96
|
+
if (defaults) {
|
|
97
|
+
options = merge({}, defaults.options, options ? preNormalize(options, defaults.options) : {});
|
|
98
|
+
} else {
|
|
99
|
+
options = merge({}, options ? preNormalize(options) : {});
|
|
62
100
|
}
|
|
63
101
|
|
|
64
102
|
if (!is.string(url) && !is.object(url)) {
|
|
@@ -75,12 +113,6 @@ module.exports = (url, options, defaults) => {
|
|
|
75
113
|
} else {
|
|
76
114
|
url = url.replace(/^unix:/, 'http://$&');
|
|
77
115
|
|
|
78
|
-
try {
|
|
79
|
-
decodeURI(url);
|
|
80
|
-
} catch (_) {
|
|
81
|
-
throw new Error('Parameter `url` must contain valid UTF-8 character sequences');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
116
|
url = urlParseLax(url);
|
|
85
117
|
if (url.auth) {
|
|
86
118
|
throw new Error('Basic authentication must be done with the `auth` option');
|
|
@@ -90,12 +122,8 @@ module.exports = (url, options, defaults) => {
|
|
|
90
122
|
url = urlToOptions(url);
|
|
91
123
|
}
|
|
92
124
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
...url,
|
|
96
|
-
protocol: url.protocol || 'http:', // Override both null/undefined with default protocol
|
|
97
|
-
...options
|
|
98
|
-
};
|
|
125
|
+
// Override both null/undefined with default protocol
|
|
126
|
+
options = merge({path: ''}, url, {protocol: url.protocol || 'https:'}, options);
|
|
99
127
|
|
|
100
128
|
const {baseUrl} = options;
|
|
101
129
|
Object.defineProperty(options, 'baseUrl', {
|
|
@@ -106,7 +134,7 @@ module.exports = (url, options, defaults) => {
|
|
|
106
134
|
});
|
|
107
135
|
|
|
108
136
|
const {query} = options;
|
|
109
|
-
if (
|
|
137
|
+
if (is.nonEmptyString(query) || is.nonEmptyObject(query) || query instanceof URLSearchParams) {
|
|
110
138
|
if (!is.string(query)) {
|
|
111
139
|
options.query = (new URLSearchParams(query)).toString();
|
|
112
140
|
}
|
|
@@ -114,12 +142,18 @@ module.exports = (url, options, defaults) => {
|
|
|
114
142
|
delete options.query;
|
|
115
143
|
}
|
|
116
144
|
|
|
117
|
-
if (options.
|
|
118
|
-
|
|
119
|
-
}
|
|
145
|
+
if (options.hostname === 'unix') {
|
|
146
|
+
const matches = /(.+?):(.+)/.exec(options.path);
|
|
120
147
|
|
|
121
|
-
|
|
122
|
-
|
|
148
|
+
if (matches) {
|
|
149
|
+
const [, socketPath, path] = matches;
|
|
150
|
+
options = {
|
|
151
|
+
...options,
|
|
152
|
+
socketPath,
|
|
153
|
+
path,
|
|
154
|
+
host: null
|
|
155
|
+
};
|
|
156
|
+
}
|
|
123
157
|
}
|
|
124
158
|
|
|
125
159
|
const {headers} = options;
|
|
@@ -129,11 +163,19 @@ module.exports = (url, options, defaults) => {
|
|
|
129
163
|
}
|
|
130
164
|
}
|
|
131
165
|
|
|
166
|
+
if (options.json && is.undefined(headers.accept)) {
|
|
167
|
+
headers.accept = 'application/json';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (options.decompress && is.undefined(headers['accept-encoding'])) {
|
|
171
|
+
headers['accept-encoding'] = 'gzip, deflate';
|
|
172
|
+
}
|
|
173
|
+
|
|
132
174
|
const {body} = options;
|
|
133
175
|
if (is.nullOrUndefined(body)) {
|
|
134
|
-
options.method = options.method
|
|
176
|
+
options.method = options.method ? options.method.toUpperCase() : 'GET';
|
|
135
177
|
} else {
|
|
136
|
-
const isObject = is.object(body) && !
|
|
178
|
+
const isObject = is.object(body) && !is.buffer(body) && !is.nodeStream(body);
|
|
137
179
|
if (!is.nodeStream(body) && !is.string(body) && !is.buffer(body) && !(options.form || options.json)) {
|
|
138
180
|
throw new TypeError('The `body` option must be a stream.Readable, string or Buffer');
|
|
139
181
|
}
|
|
@@ -157,103 +199,52 @@ module.exports = (url, options, defaults) => {
|
|
|
157
199
|
options.body = JSON.stringify(body);
|
|
158
200
|
}
|
|
159
201
|
|
|
160
|
-
|
|
161
|
-
if (is.buffer(body)) {
|
|
162
|
-
options.body = toReadableStream(body);
|
|
163
|
-
options.body._buffer = body;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
options.method = options.method || 'POST';
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
options.method = options.method.toUpperCase();
|
|
170
|
-
|
|
171
|
-
if (options.decompress && is.undefined(options.headers['accept-encoding'])) {
|
|
172
|
-
options.headers['accept-encoding'] = 'gzip, deflate';
|
|
202
|
+
options.method = options.method ? options.method.toUpperCase() : 'POST';
|
|
173
203
|
}
|
|
174
204
|
|
|
175
|
-
if (options.
|
|
176
|
-
const
|
|
205
|
+
if (!is.function(options.retry.retries)) {
|
|
206
|
+
const {retries} = options.retry;
|
|
177
207
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
...options,
|
|
182
|
-
socketPath,
|
|
183
|
-
path,
|
|
184
|
-
host: null
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
options.gotRetry = {retries: 0, methods: [], statusCodes: []};
|
|
190
|
-
if (options.retry !== false) {
|
|
191
|
-
if (is.number(options.retry)) {
|
|
192
|
-
if (is.object(defaults.options.retry)) {
|
|
193
|
-
options.gotRetry = {...defaults.options.retry, retries: options.retry};
|
|
194
|
-
} else {
|
|
195
|
-
options.gotRetry.retries = options.retry;
|
|
208
|
+
options.retry.retries = (iteration, error) => {
|
|
209
|
+
if (iteration > retries) {
|
|
210
|
+
return 0;
|
|
196
211
|
}
|
|
197
|
-
} else {
|
|
198
|
-
options.gotRetry = {...options.gotRetry, ...options.retry};
|
|
199
|
-
}
|
|
200
|
-
delete options.retry;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
options.gotRetry.methods = new Set(options.gotRetry.methods.map(method => method.toUpperCase()));
|
|
204
|
-
options.gotRetry.statusCodes = new Set(options.gotRetry.statusCodes);
|
|
205
|
-
|
|
206
|
-
if (!options.gotRetry.maxRetryAfter && Reflect.has(options, 'timeout')) {
|
|
207
|
-
if (is.number(options.timeout)) {
|
|
208
|
-
options.gotRetry.maxRetryAfter = options.timeout;
|
|
209
|
-
} else {
|
|
210
|
-
options.gotRetry.maxRetryAfter = Math.min(...[options.timeout.request, options.timeout.connection].filter(n => !is.nullOrUndefined(n)));
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
212
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
options.gotTimeout = options.timeout;
|
|
219
|
-
}
|
|
220
|
-
delete options.timeout;
|
|
221
|
-
}
|
|
213
|
+
if (error !== null) {
|
|
214
|
+
if (!isRetryOnNetworkErrorAllowed(error) && (!options.retry.methods.has(error.method) || !options.retry.statusCodes.has(error.statusCode))) {
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
222
217
|
|
|
223
|
-
|
|
224
|
-
|
|
218
|
+
if (Reflect.has(error, 'headers') && Reflect.has(error.headers, 'retry-after') && retryAfterStatusCodes.has(error.statusCode)) {
|
|
219
|
+
let after = Number(error.headers['retry-after']);
|
|
220
|
+
if (is.nan(after)) {
|
|
221
|
+
after = Date.parse(error.headers['retry-after']) - Date.now();
|
|
222
|
+
} else {
|
|
223
|
+
after *= 1000;
|
|
224
|
+
}
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
226
|
+
if (after > options.retry.maxRetryAfter) {
|
|
227
|
+
return 0;
|
|
228
|
+
}
|
|
230
229
|
|
|
231
|
-
|
|
232
|
-
let after = Number(error.headers['retry-after']);
|
|
233
|
-
if (is.nan(after)) {
|
|
234
|
-
after = Date.parse(error.headers['retry-after']) - Date.now();
|
|
235
|
-
} else {
|
|
236
|
-
after *= 1000;
|
|
230
|
+
return after;
|
|
237
231
|
}
|
|
238
232
|
|
|
239
|
-
if (
|
|
233
|
+
if (error.statusCode === 413) {
|
|
240
234
|
return 0;
|
|
241
235
|
}
|
|
242
|
-
|
|
243
|
-
return after;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (error.statusCode === 413) {
|
|
247
|
-
return 0;
|
|
248
236
|
}
|
|
249
237
|
|
|
250
238
|
const noise = Math.random() * 100;
|
|
251
|
-
|
|
252
|
-
return ((1 << iteration) * 1000) + noise;
|
|
239
|
+
return ((2 ** (iteration - 1)) * 1000) + noise;
|
|
253
240
|
};
|
|
254
241
|
}
|
|
255
242
|
|
|
256
243
|
return options;
|
|
257
244
|
};
|
|
258
245
|
|
|
246
|
+
const reNormalize = options => normalize(urlLib.format(options), options);
|
|
247
|
+
|
|
248
|
+
module.exports = normalize;
|
|
259
249
|
module.exports.preNormalize = preNormalize;
|
|
250
|
+
module.exports.reNormalize = reNormalize;
|
package/source/progress.js
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
const {Transform} = require('stream');
|
|
2
3
|
|
|
3
4
|
module.exports = {
|
|
5
|
+
download(response, emitter, downloadBodySize) {
|
|
6
|
+
let downloaded = 0;
|
|
7
|
+
|
|
8
|
+
return new Transform({
|
|
9
|
+
transform(chunk, encoding, callback) {
|
|
10
|
+
downloaded += chunk.length;
|
|
11
|
+
|
|
12
|
+
const percent = downloadBodySize ? downloaded / downloadBodySize : 0;
|
|
13
|
+
|
|
14
|
+
// Let `flush()` be responsible for emitting the last event
|
|
15
|
+
if (percent < 1) {
|
|
16
|
+
emitter.emit('downloadProgress', {
|
|
17
|
+
percent,
|
|
18
|
+
transferred: downloaded,
|
|
19
|
+
total: downloadBodySize
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
callback(null, chunk);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
flush(callback) {
|
|
27
|
+
emitter.emit('downloadProgress', {
|
|
28
|
+
percent: 1,
|
|
29
|
+
transferred: downloaded,
|
|
30
|
+
total: downloadBodySize
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
callback();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
|
|
4
38
|
upload(request, emitter, uploadBodySize) {
|
|
5
39
|
const uploadEventFrequency = 150;
|
|
6
40
|
let uploaded = 0;
|
|
@@ -34,11 +68,6 @@ module.exports = {
|
|
|
34
68
|
const headersSize = request._header ? Buffer.byteLength(request._header) : 0;
|
|
35
69
|
uploaded = socket.bytesWritten - headersSize;
|
|
36
70
|
|
|
37
|
-
/* istanbul ignore next: see https://github.com/sindresorhus/got/pull/322#pullrequestreview-51647813 (no proof) */
|
|
38
|
-
if (uploadBodySize && uploaded > uploadBodySize) {
|
|
39
|
-
uploaded = uploadBodySize;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
71
|
// Don't emit events with unchanged progress and
|
|
43
72
|
// prevent last event from being emitted, because
|
|
44
73
|
// it's emitted when `response` is emitted
|