got 8.0.2 → 8.3.0

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.
Files changed (3) hide show
  1. package/index.js +111 -62
  2. package/package.json +5 -4
  3. package/readme.md +66 -4
package/index.js CHANGED
@@ -142,59 +142,12 @@ function requestAsEventEmitter(opts) {
142
142
  return;
143
143
  }
144
144
 
145
- const downloadBodySize = Number(res.headers['content-length']) || null;
146
- let downloaded = 0;
147
-
148
145
  setImmediate(() => {
149
- const progressStream = new Transform({
150
- transform(chunk, encoding, callback) {
151
- downloaded += chunk.length;
152
-
153
- const percent = downloadBodySize ? downloaded / downloadBodySize : 0;
154
-
155
- // Let flush() be responsible for emitting the last event
156
- if (percent < 1) {
157
- ee.emit('downloadProgress', {
158
- percent,
159
- transferred: downloaded,
160
- total: downloadBodySize
161
- });
162
- }
163
-
164
- callback(null, chunk);
165
- },
166
-
167
- flush(callback) {
168
- ee.emit('downloadProgress', {
169
- percent: 1,
170
- transferred: downloaded,
171
- total: downloadBodySize
172
- });
173
-
174
- callback();
175
- }
176
- });
177
-
178
- mimicResponse(res, progressStream);
179
- progressStream.redirectUrls = redirects;
180
-
181
- const response = opts.decompress === true &&
182
- is.function(decompressResponse) &&
183
- opts.method !== 'HEAD' ? decompressResponse(progressStream) : progressStream;
184
-
185
- if (!opts.decompress && ['gzip', 'deflate'].indexOf(res.headers['content-encoding']) !== -1) {
186
- opts.encoding = null;
146
+ try {
147
+ getResponse(res, opts, ee, redirects);
148
+ } catch (e) {
149
+ ee.emit('error', e);
187
150
  }
188
-
189
- ee.emit('response', response);
190
-
191
- ee.emit('downloadProgress', {
192
- percent: 0,
193
- transferred: 0,
194
- total: downloadBodySize
195
- });
196
-
197
- res.pipe(progressStream);
198
151
  });
199
152
  });
200
153
 
@@ -207,9 +160,18 @@ function requestAsEventEmitter(opts) {
207
160
  });
208
161
 
209
162
  cacheReq.once('request', req => {
163
+ let aborted = false;
164
+ req.once('abort', _ => {
165
+ aborted = true;
166
+ });
167
+
210
168
  req.once('error', err => {
211
169
  clearInterval(progressInterval);
212
170
 
171
+ if (aborted) {
172
+ return;
173
+ }
174
+
213
175
  const backoff = opts.retries(++retryCount, err);
214
176
 
215
177
  if (backoff) {
@@ -227,14 +189,18 @@ function requestAsEventEmitter(opts) {
227
189
  total: uploadBodySize
228
190
  });
229
191
 
230
- if (req.connection) {
231
- req.connection.once('connect', () => {
192
+ const socket = req.connection;
193
+ if (socket) {
194
+ // `._connecting` was the old property which was made public in node v6.1.0
195
+ const isConnecting = socket.connecting === undefined ? socket._connecting : socket.connecting;
196
+
197
+ const onSocketConnect = () => {
232
198
  const uploadEventFrequency = 150;
233
199
 
234
200
  progressInterval = setInterval(() => {
235
201
  const lastUploaded = uploaded;
236
202
  const headersSize = Buffer.byteLength(req._header);
237
- uploaded = req.connection.bytesWritten - headersSize;
203
+ uploaded = socket.bytesWritten - headersSize;
238
204
 
239
205
  // Prevent the known issue of `bytesWritten` being larger than body size
240
206
  if (uploadBodySize && uploaded > uploadBodySize) {
@@ -254,7 +220,17 @@ function requestAsEventEmitter(opts) {
254
220
  total: uploadBodySize
255
221
  });
256
222
  }, uploadEventFrequency);
257
- });
223
+ };
224
+
225
+ // Only subscribe to 'connect' event if we're actually connecting a new
226
+ // socket, otherwise if we're already connected (because this is a
227
+ // keep-alive connection) do not bother. This is important since we won't
228
+ // get a 'connect' event for an already connected socket.
229
+ if (isConnecting) {
230
+ socket.once('connect', onSocketConnect);
231
+ } else {
232
+ onSocketConnect();
233
+ }
258
234
  }
259
235
  });
260
236
 
@@ -283,6 +259,61 @@ function requestAsEventEmitter(opts) {
283
259
  return ee;
284
260
  }
285
261
 
262
+ function getResponse(res, opts, ee, redirects) {
263
+ const downloadBodySize = Number(res.headers['content-length']) || null;
264
+ let downloaded = 0;
265
+
266
+ const progressStream = new Transform({
267
+ transform(chunk, encoding, callback) {
268
+ downloaded += chunk.length;
269
+
270
+ const percent = downloadBodySize ? downloaded / downloadBodySize : 0;
271
+
272
+ // Let flush() be responsible for emitting the last event
273
+ if (percent < 1) {
274
+ ee.emit('downloadProgress', {
275
+ percent,
276
+ transferred: downloaded,
277
+ total: downloadBodySize
278
+ });
279
+ }
280
+
281
+ callback(null, chunk);
282
+ },
283
+
284
+ flush(callback) {
285
+ ee.emit('downloadProgress', {
286
+ percent: 1,
287
+ transferred: downloaded,
288
+ total: downloadBodySize
289
+ });
290
+
291
+ callback();
292
+ }
293
+ });
294
+
295
+ mimicResponse(res, progressStream);
296
+ progressStream.redirectUrls = redirects;
297
+
298
+ const response = opts.decompress === true &&
299
+ is.function(decompressResponse) &&
300
+ opts.method !== 'HEAD' ? decompressResponse(progressStream) : progressStream;
301
+
302
+ if (!opts.decompress && ['gzip', 'deflate'].indexOf(res.headers['content-encoding']) !== -1) {
303
+ opts.encoding = null;
304
+ }
305
+
306
+ ee.emit('response', response);
307
+
308
+ ee.emit('downloadProgress', {
309
+ percent: 0,
310
+ transferred: 0,
311
+ total: downloadBodySize
312
+ });
313
+
314
+ res.pipe(progressStream);
315
+ }
316
+
286
317
  function asPromise(opts) {
287
318
  const timeoutFn = requestPromise => opts.gotTimeout && opts.gotTimeout.request ?
288
319
  pTimeout(requestPromise, opts.gotTimeout.request, new got.RequestError({message: 'Request timed out', code: 'ETIMEDOUT'}, opts)) :
@@ -290,7 +321,7 @@ function asPromise(opts) {
290
321
 
291
322
  const proxy = new EventEmitter();
292
323
 
293
- const cancelable = new PCancelable((onCancel, resolve, reject) => {
324
+ const cancelable = new PCancelable((resolve, reject, onCancel) => {
294
325
  const ee = requestAsEventEmitter(opts);
295
326
  let cancelOnRequest = false;
296
327
 
@@ -337,7 +368,7 @@ function asPromise(opts) {
337
368
  }
338
369
  }
339
370
 
340
- if (statusCode !== 304 && (statusCode < 200 || statusCode > limitStatusCode)) {
371
+ if (opts.throwHttpErrors && statusCode !== 304 && (statusCode < 200 || statusCode > limitStatusCode)) {
341
372
  throw new got.HTTPError(statusCode, res.statusMessage, res.headers, opts);
342
373
  }
343
374
 
@@ -355,6 +386,14 @@ function asPromise(opts) {
355
386
  ee.on('downloadProgress', proxy.emit.bind(proxy, 'downloadProgress'));
356
387
  });
357
388
 
389
+ // Preserve backwards-compatibility
390
+ // TODO: Remove this in the next major version
391
+ Object.defineProperty(cancelable, 'canceled', {
392
+ get() {
393
+ return cancelable.isCanceled;
394
+ }
395
+ });
396
+
358
397
  const promise = timeoutFn(cancelable);
359
398
 
360
399
  promise.cancel = cancelable.cancel.bind(cancelable);
@@ -425,7 +464,7 @@ function asStream(opts) {
425
464
 
426
465
  res.pipe(output);
427
466
 
428
- if (statusCode !== 304 && (statusCode < 200 || statusCode > 299)) {
467
+ if (opts.throwHttpErrors && statusCode !== 304 && (statusCode < 200 || statusCode > 299)) {
429
468
  proxy.emit('error', new got.HTTPError(statusCode, res.statusMessage, res.headers, opts), null, res);
430
469
  return;
431
470
  }
@@ -467,7 +506,8 @@ function normalizeArguments(url, opts) {
467
506
  retries: 2,
468
507
  cache: false,
469
508
  decompress: true,
470
- useElectronNet: false
509
+ useElectronNet: false,
510
+ throwHttpErrors: true
471
511
  },
472
512
  url,
473
513
  {
@@ -484,10 +524,13 @@ function normalizeArguments(url, opts) {
484
524
  }
485
525
 
486
526
  opts.headers = Object.assign({
487
- 'user-agent': `${pkg.name}/${pkg.version} (https://github.com/sindresorhus/got)`,
488
- 'accept-encoding': 'gzip,deflate'
527
+ 'user-agent': `${pkg.name}/${pkg.version} (https://github.com/sindresorhus/got)`
489
528
  }, headers);
490
529
 
530
+ if (opts.decompress) {
531
+ opts.headers['accept-encoding'] = 'gzip,deflate';
532
+ }
533
+
491
534
  const query = opts.query;
492
535
 
493
536
  if (query) {
@@ -585,7 +628,13 @@ function normalizeArguments(url, opts) {
585
628
 
586
629
  function got(url, opts) {
587
630
  try {
588
- return asPromise(normalizeArguments(url, opts));
631
+ const normalizedArgs = normalizeArguments(url, opts);
632
+
633
+ if (normalizedArgs.stream) {
634
+ return asStream(normalizedArgs);
635
+ }
636
+
637
+ return asPromise(normalizedArgs);
589
638
  } catch (err) {
590
639
  return Promise.reject(err);
591
640
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "got",
3
- "version": "8.0.2",
3
+ "version": "8.3.0",
4
4
  "description": "Simplified HTTP requests",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/got",
@@ -61,7 +61,7 @@
61
61
  "isurl": "^1.0.0-alpha5",
62
62
  "lowercase-keys": "^1.0.0",
63
63
  "mimic-response": "^1.0.0",
64
- "p-cancelable": "^0.3.0",
64
+ "p-cancelable": "^0.4.0",
65
65
  "p-timeout": "^2.0.1",
66
66
  "pify": "^3.0.0",
67
67
  "safe-buffer": "^5.1.1",
@@ -70,19 +70,20 @@
70
70
  "url-to-options": "^1.0.1"
71
71
  },
72
72
  "devDependencies": {
73
- "ava": "^0.24.0",
73
+ "ava": "^0.25.0",
74
74
  "coveralls": "^3.0.0",
75
75
  "form-data": "^2.1.1",
76
76
  "get-port": "^3.0.0",
77
77
  "nyc": "^11.0.2",
78
78
  "p-event": "^1.3.0",
79
79
  "pem": "^1.4.4",
80
+ "proxyquire": "^1.8.0",
80
81
  "sinon": "^4.0.0",
81
82
  "slow-stream": "0.0.4",
82
83
  "tempfile": "^2.0.0",
83
84
  "tempy": "^0.2.1",
84
85
  "universal-url": "1.0.0-alpha",
85
- "xo": "^0.18.0"
86
+ "xo": "^0.20.0"
86
87
  },
87
88
  "ava": {
88
89
  "concurrency": 4
package/readme.md CHANGED
@@ -1,10 +1,15 @@
1
- <h1 align="center">
1
+ <div align="center">
2
+ <br>
2
3
  <br>
3
4
  <img width="360" src="https://rawgit.com/sindresorhus/got/master/media/logo.svg" alt="got">
4
5
  <br>
5
6
  <br>
6
7
  <br>
7
- </h1>
8
+ <p align="center">Huge thanks to <a href="https://moxy.studio"><img src="https://sindresorhus.com/assets/thanks/moxy-logo.svg" width="150"></a> for sponsoring me!
9
+ </p>
10
+ <br>
11
+ <br>
12
+ </div>
8
13
 
9
14
  > Simplified HTTP requests
10
15
 
@@ -37,6 +42,10 @@ Created because [`request`](https://github.com/request/request) is bloated *(sev
37
42
  $ npm install got
38
43
  ```
39
44
 
45
+ <a href="https://www.patreon.com/sindresorhus">
46
+ <img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
47
+ </a>
48
+
40
49
 
41
50
  ## Usage
42
51
 
@@ -96,6 +105,13 @@ Type: `Object`
96
105
 
97
106
  Any of the [`http.request`](http://nodejs.org/api/http.html#http_http_request_options_callback) options.
98
107
 
108
+ ###### stream
109
+
110
+ Type: `boolean`<br>
111
+ Default: `false`
112
+
113
+ Returns a `Stream` instead of a `Promise`. This is equivalent to calling `got.stream(url, [options])`.
114
+
99
115
  ###### body
100
116
 
101
117
  Type: `string` `Buffer` `stream.Readable`
@@ -113,7 +129,7 @@ If `content-length` or `transfer-encoding` is not set in `options.headers` and `
113
129
  Type: `string` `null`<br>
114
130
  Default: `'utf8'`
115
131
 
116
- [Encoding](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) to be used on `setEncoding` of the response data. If `null`, the body is returned as a Buffer.
132
+ [Encoding](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) to be used on `setEncoding` of the response data. If `null`, the body is returned as a [`Buffer`](https://nodejs.org/api/buffer.html) (binary data).
117
133
 
118
134
  ###### form
119
135
 
@@ -197,6 +213,14 @@ Default: `false`
197
213
 
198
214
  When used in Electron, Got will use [`electron.net`](https://electronjs.org/docs/api/net/) instead of the Node.js `http` module. According to the Electron docs, it should be fully compatible, but it's not entirely. See [#315](https://github.com/sindresorhus/got/issues/315).
199
215
 
216
+ ###### throwHttpErrors
217
+
218
+ Type: `boolean`<br>
219
+ Default: `true`
220
+
221
+ Determines if a `got.HTTPError` is thrown for error responses (non-2xx status codes).
222
+
223
+ If this is disabled, requests that encounter an error status code will be resolved with the `response` instead of throwing. This may be useful if you are checking for resource availability and are expecting error responses.
200
224
 
201
225
  #### Streams
202
226
 
@@ -327,7 +351,7 @@ The promise returned by Got has a [`.cancel()`](https://github.com/sindresorhus/
327
351
  try {
328
352
  await request;
329
353
  } catch (error) {
330
- if (request.canceled) { // Or `error instanceof got.CancelError`
354
+ if (request.isCanceled) { // Or `error instanceof got.CancelError`
331
355
  // Handle cancelation
332
356
  }
333
357
 
@@ -545,6 +569,44 @@ request(`https://${config.host}/production/`, {
545
569
  ```
546
570
 
547
571
 
572
+ ## Testing
573
+
574
+ You can test your requests by using the [`nock`](https://github.com/node-nock/nock) module to mock an endpoint:
575
+
576
+ ```js
577
+ const got = require('got');
578
+ const nock = require('nock');
579
+
580
+ nock('https://sindresorhus.com')
581
+ .get('/')
582
+ .reply(200, 'Hello world!');
583
+
584
+ (async () => {
585
+ const response = await got('sindresorhus.com');
586
+ console.log(response.body);
587
+ //=> 'Hello world!'
588
+ })();
589
+ ```
590
+
591
+ If you need real integration tests you can use [`create-test-server`](https://github.com/lukechilds/create-test-server):
592
+
593
+ ```js
594
+ const got = require('got');
595
+ const createTestServer = require('create-test-server');
596
+
597
+ (async () => {
598
+ const server = await createTestServer();
599
+ server.get('/', 'Hello world!');
600
+
601
+ const response = await got(server.url);
602
+ console.log(response.body);
603
+ //=> 'Hello world!'
604
+
605
+ await server.close();
606
+ })();
607
+ ```
608
+
609
+
548
610
  ## Tips
549
611
 
550
612
  ### User Agent