superagent 3.7.0 → 3.8.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/History.md +12 -0
- package/docs/index.md +38 -5
- package/lib/agent-base.js +20 -0
- package/lib/client.js +61 -48
- package/lib/node/agent.js +38 -37
- package/lib/node/index.js +209 -192
- package/lib/node/parsers/image.js +6 -6
- package/lib/node/parsers/index.js +1 -1
- package/lib/node/parsers/json.js +4 -2
- package/lib/node/parsers/text.js +3 -1
- package/lib/node/parsers/urlencoded.js +5 -3
- package/lib/node/response.js +12 -13
- package/lib/node/unzip.js +16 -15
- package/lib/request-base.js +73 -16
- package/lib/response-base.js +2 -2
- package/lib/utils.js +4 -2
- package/package.json +2 -2
- package/superagent.js +166 -99
- package/yarn.lock +163 -393
- package/lib/should-retry.js +0 -25
package/lib/node/index.js
CHANGED
|
@@ -4,30 +4,29 @@
|
|
|
4
4
|
* Module dependencies.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
var request = exports = module.exports = function(method, url) {
|
|
7
|
+
const debug = require('debug')('superagent');
|
|
8
|
+
const formidable = require('formidable');
|
|
9
|
+
const FormData = require('form-data');
|
|
10
|
+
const Response = require('./response');
|
|
11
|
+
const parse = require('url').parse;
|
|
12
|
+
const format = require('url').format;
|
|
13
|
+
const resolve = require('url').resolve;
|
|
14
|
+
let methods = require('methods');
|
|
15
|
+
const Stream = require('stream');
|
|
16
|
+
const utils = require('../utils');
|
|
17
|
+
const unzip = require('./unzip').unzip;
|
|
18
|
+
const extend = require('extend');
|
|
19
|
+
const mime = require('mime');
|
|
20
|
+
const https = require('https');
|
|
21
|
+
const http = require('http');
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const qs = require('qs');
|
|
24
|
+
const zlib = require('zlib');
|
|
25
|
+
const util = require('util');
|
|
26
|
+
const pkg = require('../../package.json');
|
|
27
|
+
const RequestBase = require('../request-base');
|
|
28
|
+
|
|
29
|
+
function request(method, url) {
|
|
31
30
|
// callback
|
|
32
31
|
if ('function' == typeof url) {
|
|
33
32
|
return new exports.Request('GET', method).end(url);
|
|
@@ -40,6 +39,7 @@ var request = exports = module.exports = function(method, url) {
|
|
|
40
39
|
|
|
41
40
|
return new exports.Request(method, url);
|
|
42
41
|
}
|
|
42
|
+
exports = module.exports = request;
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* Expose `Request`.
|
|
@@ -79,7 +79,7 @@ mime.define({
|
|
|
79
79
|
|
|
80
80
|
exports.protocols = {
|
|
81
81
|
'http:': http,
|
|
82
|
-
'https:': https
|
|
82
|
+
'https:': https,
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
/**
|
|
@@ -93,7 +93,7 @@ exports.protocols = {
|
|
|
93
93
|
|
|
94
94
|
exports.serialize = {
|
|
95
95
|
'application/x-www-form-urlencoded': qs.stringify,
|
|
96
|
-
'application/json': JSON.stringify
|
|
96
|
+
'application/json': JSON.stringify,
|
|
97
97
|
};
|
|
98
98
|
|
|
99
99
|
/**
|
|
@@ -114,7 +114,7 @@ exports.parse = require('./parsers');
|
|
|
114
114
|
* @api private
|
|
115
115
|
*/
|
|
116
116
|
function _initHeaders(req) {
|
|
117
|
-
|
|
117
|
+
const ua = `node-superagent/${pkg.version}`;
|
|
118
118
|
req._header = { // coerces header names to lowercase
|
|
119
119
|
'user-agent': ua
|
|
120
120
|
};
|
|
@@ -164,7 +164,7 @@ RequestBase(Request.prototype);
|
|
|
164
164
|
*
|
|
165
165
|
* ``` js
|
|
166
166
|
* request.post('http://localhost/upload')
|
|
167
|
-
* .attach(
|
|
167
|
+
* .attach('field', Buffer.from('<b>Hello world</b>'), 'hello.html')
|
|
168
168
|
* .end(callback);
|
|
169
169
|
* ```
|
|
170
170
|
*
|
|
@@ -189,7 +189,7 @@ Request.prototype.attach = function(field, file, options){
|
|
|
189
189
|
throw Error("superagent can't mix .send() and .attach()");
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
let o = options || {};
|
|
193
193
|
if ('string' == typeof options) {
|
|
194
194
|
o = { filename: options };
|
|
195
195
|
}
|
|
@@ -210,10 +210,9 @@ Request.prototype.attach = function(field, file, options){
|
|
|
210
210
|
Request.prototype._getFormData = function() {
|
|
211
211
|
if (!this._formData) {
|
|
212
212
|
this._formData = new FormData();
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
that.abort();
|
|
213
|
+
this._formData.on('error', err => {
|
|
214
|
+
this.emit('error', err);
|
|
215
|
+
this.abort();
|
|
217
216
|
});
|
|
218
217
|
}
|
|
219
218
|
return this._formData;
|
|
@@ -259,10 +258,11 @@ Request.prototype.agent = function(agent){
|
|
|
259
258
|
* @api public
|
|
260
259
|
*/
|
|
261
260
|
|
|
262
|
-
Request.prototype.type = function(type){
|
|
263
|
-
return this.set(
|
|
264
|
-
|
|
265
|
-
: mime.lookup(type)
|
|
261
|
+
Request.prototype.type = function(type) {
|
|
262
|
+
return this.set(
|
|
263
|
+
'Content-Type',
|
|
264
|
+
~type.indexOf('/') ? type : mime.lookup(type)
|
|
265
|
+
);
|
|
266
266
|
};
|
|
267
267
|
|
|
268
268
|
/**
|
|
@@ -324,7 +324,7 @@ Request.prototype.query = function(val){
|
|
|
324
324
|
*/
|
|
325
325
|
|
|
326
326
|
Request.prototype.write = function(data, encoding){
|
|
327
|
-
|
|
327
|
+
const req = this.request();
|
|
328
328
|
if (!this._streamRequest) {
|
|
329
329
|
this._streamRequest = true;
|
|
330
330
|
}
|
|
@@ -348,25 +348,32 @@ Request.prototype.pipe = function(stream, options){
|
|
|
348
348
|
};
|
|
349
349
|
|
|
350
350
|
Request.prototype._pipeContinue = function(stream, options){
|
|
351
|
-
|
|
352
|
-
this.req.once('response', function(res){
|
|
351
|
+
this.req.once('response', res => {
|
|
353
352
|
// redirect
|
|
354
|
-
|
|
355
|
-
if (redirect &&
|
|
356
|
-
return
|
|
353
|
+
const redirect = isRedirect(res.statusCode);
|
|
354
|
+
if (redirect && this._redirects++ != this._maxRedirects) {
|
|
355
|
+
return this._redirect(res)._pipeContinue(stream, options);
|
|
357
356
|
}
|
|
358
357
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (
|
|
358
|
+
this.res = res;
|
|
359
|
+
this._emitResponse();
|
|
360
|
+
if (this._aborted) return;
|
|
362
361
|
|
|
363
|
-
if (
|
|
364
|
-
|
|
362
|
+
if (this._shouldUnzip(res)) {
|
|
363
|
+
const unzipObj = zlib.createUnzip();
|
|
364
|
+
unzipObj.on('error', err => {
|
|
365
|
+
if (err && err.code === 'Z_BUF_ERROR') { // unexpected end of file is ignored by browsers and curl
|
|
366
|
+
stream.emit('end');
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
stream.emit('error', err);
|
|
370
|
+
});
|
|
371
|
+
res.pipe(unzipObj).pipe(stream, options);
|
|
365
372
|
} else {
|
|
366
373
|
res.pipe(stream, options);
|
|
367
374
|
}
|
|
368
|
-
res.once('end',
|
|
369
|
-
|
|
375
|
+
res.once('end', () => {
|
|
376
|
+
this.emit('end');
|
|
370
377
|
});
|
|
371
378
|
});
|
|
372
379
|
return stream;
|
|
@@ -394,7 +401,7 @@ Request.prototype.buffer = function(val){
|
|
|
394
401
|
*/
|
|
395
402
|
|
|
396
403
|
Request.prototype._redirect = function(res){
|
|
397
|
-
|
|
404
|
+
let url = res.headers.location;
|
|
398
405
|
if (!url) {
|
|
399
406
|
return this.callback(new Error('No location header for redirect'), res);
|
|
400
407
|
}
|
|
@@ -408,15 +415,15 @@ Request.prototype._redirect = function(res){
|
|
|
408
415
|
// this is required for Node v0.10+
|
|
409
416
|
res.resume();
|
|
410
417
|
|
|
411
|
-
|
|
418
|
+
let headers = this.req._headers;
|
|
412
419
|
|
|
413
|
-
|
|
420
|
+
const changesOrigin = parse(url).host !== parse(this.url).host;
|
|
414
421
|
|
|
415
422
|
// implementation of 302 following defacto standard
|
|
416
423
|
if (res.statusCode == 301 || res.statusCode == 302){
|
|
417
424
|
// strip Content-* related fields
|
|
418
425
|
// in case of POST etc
|
|
419
|
-
headers = utils.cleanHeader(this.req._headers,
|
|
426
|
+
headers = utils.cleanHeader(this.req._headers, changesOrigin);
|
|
420
427
|
|
|
421
428
|
// force GET
|
|
422
429
|
this.method = 'HEAD' == this.method
|
|
@@ -430,7 +437,7 @@ Request.prototype._redirect = function(res){
|
|
|
430
437
|
if (res.statusCode == 303) {
|
|
431
438
|
// strip Content-* related fields
|
|
432
439
|
// in case of POST etc
|
|
433
|
-
headers = utils.cleanHeader(this.req._headers,
|
|
440
|
+
headers = utils.cleanHeader(this.req._headers, changesOrigin);
|
|
434
441
|
|
|
435
442
|
// force method
|
|
436
443
|
this.method = 'GET';
|
|
@@ -446,7 +453,7 @@ Request.prototype._redirect = function(res){
|
|
|
446
453
|
delete this._formData;
|
|
447
454
|
|
|
448
455
|
// remove all add header except User-Agent
|
|
449
|
-
_initHeaders(this)
|
|
456
|
+
_initHeaders(this);
|
|
450
457
|
|
|
451
458
|
// redirect
|
|
452
459
|
this._endCalled = false;
|
|
@@ -479,19 +486,19 @@ Request.prototype._redirect = function(res){
|
|
|
479
486
|
|
|
480
487
|
Request.prototype.auth = function(user, pass, options){
|
|
481
488
|
if (1 === arguments.length) pass = '';
|
|
482
|
-
if (
|
|
489
|
+
if (typeof pass === 'object' && pass !== null) { // pass is optional and can be replaced with options
|
|
490
|
+
options = pass;
|
|
491
|
+
pass = '';
|
|
492
|
+
}
|
|
483
493
|
if (!options) {
|
|
484
494
|
options = { type: 'basic' };
|
|
485
495
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
var str = new Buffer(user + pass).toString('base64');
|
|
493
|
-
return this.set('Authorization', 'Basic ' + str);
|
|
494
|
-
}
|
|
496
|
+
|
|
497
|
+
var encoder = function(string) {
|
|
498
|
+
return new Buffer(string).toString('base64');
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
return this._auth(user, pass, options, encoder);
|
|
495
502
|
};
|
|
496
503
|
|
|
497
504
|
/**
|
|
@@ -528,8 +535,8 @@ Request.prototype.key = function(cert){
|
|
|
528
535
|
* @api public
|
|
529
536
|
*/
|
|
530
537
|
|
|
531
|
-
Request.prototype.pfx = function(cert){
|
|
532
|
-
if(typeof cert === 'object' && !Buffer.isBuffer(cert)){
|
|
538
|
+
Request.prototype.pfx = function(cert) {
|
|
539
|
+
if (typeof cert === 'object' && !Buffer.isBuffer(cert)) {
|
|
533
540
|
this._pfx = cert.pfx;
|
|
534
541
|
this._passphrase = cert.passphrase;
|
|
535
542
|
} else {
|
|
@@ -561,11 +568,13 @@ Request.prototype.cert = function(cert){
|
|
|
561
568
|
Request.prototype.request = function(){
|
|
562
569
|
if (this.req) return this.req;
|
|
563
570
|
|
|
564
|
-
|
|
565
|
-
var options = {};
|
|
571
|
+
const options = {};
|
|
566
572
|
|
|
567
573
|
try {
|
|
568
|
-
|
|
574
|
+
const query = qs.stringify(this.qs, {
|
|
575
|
+
indices: false,
|
|
576
|
+
strictNullHandling: true,
|
|
577
|
+
});
|
|
569
578
|
if (query) {
|
|
570
579
|
this.qs = {};
|
|
571
580
|
this._query.push(query);
|
|
@@ -575,20 +584,20 @@ Request.prototype.request = function(){
|
|
|
575
584
|
return this.emit('error', e);
|
|
576
585
|
}
|
|
577
586
|
|
|
578
|
-
|
|
579
|
-
|
|
587
|
+
let url = this.url;
|
|
588
|
+
const retries = this._retries;
|
|
580
589
|
|
|
581
590
|
// default to http://
|
|
582
|
-
if (0 != url.indexOf('http')) url =
|
|
591
|
+
if (0 != url.indexOf('http')) url = `http://${url}`;
|
|
583
592
|
url = parse(url);
|
|
584
593
|
|
|
585
594
|
// support unix sockets
|
|
586
595
|
if (/^https?\+unix:/.test(url.protocol) === true) {
|
|
587
596
|
// get the protocol
|
|
588
|
-
url.protocol = url.protocol.split('+')[0]
|
|
597
|
+
url.protocol = `${url.protocol.split('+')[0]}:`;
|
|
589
598
|
|
|
590
599
|
// get the socket, path
|
|
591
|
-
|
|
600
|
+
const unixParts = url.path.match(/^([^/]+)(.+)$/);
|
|
592
601
|
options.socketPath = unixParts[1].replace(/%2F/g, '/');
|
|
593
602
|
url.path = unixParts[2];
|
|
594
603
|
}
|
|
@@ -606,10 +615,10 @@ Request.prototype.request = function(){
|
|
|
606
615
|
options.agent = this._agent;
|
|
607
616
|
|
|
608
617
|
// initiate request
|
|
609
|
-
|
|
618
|
+
const mod = exports.protocols[url.protocol];
|
|
610
619
|
|
|
611
620
|
// request
|
|
612
|
-
|
|
621
|
+
const req = (this.req = mod.request(options));
|
|
613
622
|
|
|
614
623
|
// set tcp no delay
|
|
615
624
|
req.setNoDelay(true);
|
|
@@ -621,32 +630,35 @@ Request.prototype.request = function(){
|
|
|
621
630
|
this.host = url.host;
|
|
622
631
|
|
|
623
632
|
// expose events
|
|
624
|
-
req.once('drain',
|
|
633
|
+
req.once('drain', () => { this.emit('drain'); });
|
|
625
634
|
|
|
626
|
-
req.once('error',
|
|
635
|
+
req.once('error', err => {
|
|
627
636
|
// flag abortion here for out timeouts
|
|
628
637
|
// because node will emit a faux-error "socket hang up"
|
|
629
638
|
// when request is aborted before a connection is made
|
|
630
|
-
if (
|
|
639
|
+
if (this._aborted) return;
|
|
631
640
|
// if not the same, we are in the **old** (cancelled) request,
|
|
632
641
|
// so need to continue (same as for above)
|
|
633
|
-
if (
|
|
642
|
+
if (this._retries !== retries) return;
|
|
634
643
|
// if we've received a response then we don't want to let
|
|
635
644
|
// an error in the request blow up the response
|
|
636
|
-
if (
|
|
637
|
-
|
|
645
|
+
if (this.response) return;
|
|
646
|
+
this.callback(err);
|
|
638
647
|
});
|
|
639
648
|
|
|
640
649
|
// auth
|
|
641
650
|
if (url.auth) {
|
|
642
|
-
|
|
651
|
+
const auth = url.auth.split(':');
|
|
643
652
|
this.auth(auth[0], auth[1]);
|
|
644
653
|
}
|
|
654
|
+
if (this.username && this.password) {
|
|
655
|
+
this.auth(this.username, this.password);
|
|
656
|
+
}
|
|
645
657
|
|
|
646
658
|
// add cookies
|
|
647
659
|
if (this.cookies) req.setHeader('Cookie', this.cookies);
|
|
648
660
|
|
|
649
|
-
for (
|
|
661
|
+
for (const key in this.header) {
|
|
650
662
|
if (this.header.hasOwnProperty(key))
|
|
651
663
|
req.setHeader(key, this.header[key]);
|
|
652
664
|
}
|
|
@@ -664,33 +676,35 @@ Request.prototype.request = function(){
|
|
|
664
676
|
*/
|
|
665
677
|
|
|
666
678
|
Request.prototype.callback = function(err, res){
|
|
667
|
-
|
|
668
|
-
if (this._maxRetries && this._retries++ < this._maxRetries && shouldRetry(err, res)) {
|
|
679
|
+
if (this._shouldRetry(err, res)) {
|
|
669
680
|
return this._retry();
|
|
670
681
|
}
|
|
671
682
|
|
|
672
683
|
// Avoid the error which is emitted from 'socket hang up' to cause the fn undefined error on JS runtime.
|
|
673
|
-
|
|
684
|
+
const fn = this._callback || noop;
|
|
674
685
|
this.clearTimeout();
|
|
675
686
|
if (this.called) return console.warn('superagent: double callback bug');
|
|
676
687
|
this.called = true;
|
|
677
688
|
|
|
678
689
|
if (!err) {
|
|
679
690
|
try {
|
|
680
|
-
if (this._isResponseOK(res)) {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
691
|
+
if (!this._isResponseOK(res)) {
|
|
692
|
+
let msg = 'Unsuccessful HTTP response';
|
|
693
|
+
if (res) {
|
|
694
|
+
msg = http.STATUS_CODES[res.status] || msg;
|
|
695
|
+
}
|
|
696
|
+
err = new Error(msg);
|
|
697
|
+
err.status = res ? res.status : undefined;
|
|
687
698
|
}
|
|
688
|
-
err = new Error(msg);
|
|
689
|
-
err.status = res ? res.status : undefined;
|
|
690
699
|
} catch (new_err) {
|
|
691
700
|
err = new_err;
|
|
692
701
|
}
|
|
693
702
|
}
|
|
703
|
+
// It's important that the callback is called outside try/catch
|
|
704
|
+
// to avoid double callback
|
|
705
|
+
if (!err) {
|
|
706
|
+
return fn(null, res);
|
|
707
|
+
}
|
|
694
708
|
|
|
695
709
|
err.response = res;
|
|
696
710
|
if (this._maxRetries) err.retries = this._retries - 1;
|
|
@@ -724,24 +738,26 @@ Request.prototype._isHost = function _isHost(obj) {
|
|
|
724
738
|
* @api public
|
|
725
739
|
*/
|
|
726
740
|
|
|
727
|
-
Request.prototype._emitResponse = function(body, files){
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
741
|
+
Request.prototype._emitResponse = function(body, files) {
|
|
742
|
+
const response = new Response(this);
|
|
743
|
+
this.response = response;
|
|
744
|
+
response.redirects = this._redirectList;
|
|
745
|
+
if (undefined !== body) {
|
|
746
|
+
response.body = body;
|
|
747
|
+
}
|
|
748
|
+
response.files = files;
|
|
749
|
+
this.emit('response', response);
|
|
750
|
+
return response;
|
|
737
751
|
};
|
|
738
752
|
|
|
739
|
-
Request.prototype.end = function(fn){
|
|
753
|
+
Request.prototype.end = function(fn) {
|
|
740
754
|
this.request();
|
|
741
755
|
debug('%s %s', this.method, this.url);
|
|
742
756
|
|
|
743
757
|
if (this._endCalled) {
|
|
744
|
-
console.warn(
|
|
758
|
+
console.warn(
|
|
759
|
+
'Warning: .end() was called twice. This is not supported in superagent'
|
|
760
|
+
);
|
|
745
761
|
}
|
|
746
762
|
this._endCalled = true;
|
|
747
763
|
|
|
@@ -752,11 +768,10 @@ Request.prototype.end = function(fn){
|
|
|
752
768
|
};
|
|
753
769
|
|
|
754
770
|
Request.prototype._end = function() {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
var method = this.method;
|
|
771
|
+
let data = this._data;
|
|
772
|
+
const req = this.req;
|
|
773
|
+
let buffer = this._buffer;
|
|
774
|
+
const method = this.method;
|
|
760
775
|
|
|
761
776
|
this._setTimeouts();
|
|
762
777
|
|
|
@@ -764,10 +779,10 @@ Request.prototype._end = function() {
|
|
|
764
779
|
if ('HEAD' != method && !req._headerSent) {
|
|
765
780
|
// serialize stuff
|
|
766
781
|
if ('string' != typeof data) {
|
|
767
|
-
|
|
782
|
+
let contentType = req.getHeader('Content-Type');
|
|
768
783
|
// Parse out just the content type from the header (ignore the charset)
|
|
769
|
-
if (contentType) contentType = contentType.split(';')[0]
|
|
770
|
-
|
|
784
|
+
if (contentType) contentType = contentType.split(';')[0];
|
|
785
|
+
let serialize = exports.serialize[contentType];
|
|
771
786
|
if (!serialize && isJSON(contentType)) {
|
|
772
787
|
serialize = exports.serialize['application/json'];
|
|
773
788
|
}
|
|
@@ -781,40 +796,40 @@ Request.prototype._end = function() {
|
|
|
781
796
|
}
|
|
782
797
|
|
|
783
798
|
// response
|
|
784
|
-
req.once('response',
|
|
785
|
-
debug('%s %s -> %s',
|
|
799
|
+
req.once('response', res => {
|
|
800
|
+
debug('%s %s -> %s', this.method, this.url, res.statusCode);
|
|
786
801
|
|
|
787
|
-
if (
|
|
788
|
-
clearTimeout(
|
|
802
|
+
if (this._responseTimeoutTimer) {
|
|
803
|
+
clearTimeout(this._responseTimeoutTimer);
|
|
789
804
|
}
|
|
790
805
|
|
|
791
|
-
if (
|
|
806
|
+
if (this.piped) {
|
|
792
807
|
return;
|
|
793
808
|
}
|
|
794
809
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
810
|
+
const max = this._maxRedirects;
|
|
811
|
+
const mime = utils.type(res.headers['content-type'] || '') || 'text/plain';
|
|
812
|
+
const type = mime.split('/')[0];
|
|
813
|
+
const multipart = 'multipart' == type;
|
|
814
|
+
const redirect = isRedirect(res.statusCode);
|
|
815
|
+
let parser = this._parser;
|
|
816
|
+
const responseType = this._responseType;
|
|
802
817
|
|
|
803
|
-
|
|
818
|
+
this.res = res;
|
|
804
819
|
|
|
805
820
|
// redirect
|
|
806
|
-
if (redirect &&
|
|
807
|
-
return
|
|
821
|
+
if (redirect && this._redirects++ != max) {
|
|
822
|
+
return this._redirect(res);
|
|
808
823
|
}
|
|
809
824
|
|
|
810
|
-
if ('HEAD' ==
|
|
811
|
-
|
|
812
|
-
|
|
825
|
+
if ('HEAD' == this.method) {
|
|
826
|
+
this.emit('end');
|
|
827
|
+
this.callback(null, this._emitResponse());
|
|
813
828
|
return;
|
|
814
829
|
}
|
|
815
830
|
|
|
816
831
|
// zlib support
|
|
817
|
-
if (
|
|
832
|
+
if (this._shouldUnzip(res)) {
|
|
818
833
|
unzip(req, res);
|
|
819
834
|
}
|
|
820
835
|
|
|
@@ -823,7 +838,7 @@ Request.prototype._end = function() {
|
|
|
823
838
|
parser = exports.parse.image; // It's actually a generic Buffer
|
|
824
839
|
buffer = true;
|
|
825
840
|
} else if (multipart) {
|
|
826
|
-
|
|
841
|
+
const form = new formidable.IncomingForm();
|
|
827
842
|
parser = form.parse.bind(form);
|
|
828
843
|
buffer = true;
|
|
829
844
|
} else if (isImageOrVideo(mime)) {
|
|
@@ -845,15 +860,15 @@ Request.prototype._end = function() {
|
|
|
845
860
|
}
|
|
846
861
|
|
|
847
862
|
// by default only buffer text/*, json and messed up thing from hell
|
|
848
|
-
if (undefined === buffer && isText(mime) || isJSON(mime)) {
|
|
863
|
+
if ((undefined === buffer && isText(mime)) || isJSON(mime)) {
|
|
849
864
|
buffer = true;
|
|
850
865
|
}
|
|
851
866
|
|
|
852
|
-
|
|
867
|
+
let parserHandlesEnd = false;
|
|
853
868
|
if (buffer) {
|
|
854
869
|
// Protectiona against zip bombs and other nuisance
|
|
855
|
-
let responseBytesLeft =
|
|
856
|
-
res.on('data',
|
|
870
|
+
let responseBytesLeft = this._maxResponseSize || 200000000;
|
|
871
|
+
res.on('data', buf => {
|
|
857
872
|
responseBytesLeft -= buf.byteLength || buf.length;
|
|
858
873
|
if (responseBytesLeft < 0) {
|
|
859
874
|
// This will propagate through error event
|
|
@@ -874,71 +889,72 @@ Request.prototype._end = function() {
|
|
|
874
889
|
// which is weird BTW, because response.body won't be there.
|
|
875
890
|
parserHandlesEnd = buffer;
|
|
876
891
|
|
|
877
|
-
parser(res,
|
|
878
|
-
if (
|
|
892
|
+
parser(res, (err, obj, files) => {
|
|
893
|
+
if (this.timedout) {
|
|
879
894
|
// Timeout has already handled all callbacks
|
|
880
895
|
return;
|
|
881
896
|
}
|
|
882
897
|
|
|
883
898
|
// Intentional (non-timeout) abort is supposed to preserve partial response,
|
|
884
899
|
// even if it doesn't parse.
|
|
885
|
-
if (err && !
|
|
886
|
-
return
|
|
900
|
+
if (err && !this._aborted) {
|
|
901
|
+
return this.callback(err);
|
|
887
902
|
}
|
|
888
903
|
|
|
889
904
|
if (parserHandlesEnd) {
|
|
890
|
-
|
|
891
|
-
|
|
905
|
+
this.emit('end');
|
|
906
|
+
this.callback(null, this._emitResponse(obj, files));
|
|
892
907
|
}
|
|
893
908
|
});
|
|
894
909
|
} catch (err) {
|
|
895
|
-
|
|
910
|
+
this.callback(err);
|
|
896
911
|
return;
|
|
897
912
|
}
|
|
898
913
|
}
|
|
899
914
|
|
|
900
|
-
|
|
915
|
+
this.res = res;
|
|
901
916
|
|
|
902
917
|
// unbuffered
|
|
903
918
|
if (!buffer) {
|
|
904
|
-
debug('unbuffered %s %s',
|
|
905
|
-
|
|
906
|
-
if (multipart) return // allow multipart to handle end event
|
|
907
|
-
res.once('end',
|
|
908
|
-
debug('end %s %s',
|
|
909
|
-
|
|
910
|
-
})
|
|
919
|
+
debug('unbuffered %s %s', this.method, this.url);
|
|
920
|
+
this.callback(null, this._emitResponse());
|
|
921
|
+
if (multipart) return; // allow multipart to handle end event
|
|
922
|
+
res.once('end', () => {
|
|
923
|
+
debug('end %s %s', this.method, this.url);
|
|
924
|
+
this.emit('end');
|
|
925
|
+
});
|
|
911
926
|
return;
|
|
912
927
|
}
|
|
913
928
|
|
|
914
929
|
// terminating events
|
|
915
|
-
res.once('error',
|
|
930
|
+
res.once('error', err => {
|
|
916
931
|
parserHandlesEnd = false;
|
|
917
|
-
|
|
918
|
-
});
|
|
919
|
-
if (!parserHandlesEnd) res.once('end', function(){
|
|
920
|
-
debug('end %s %s', self.method, self.url);
|
|
921
|
-
// TODO: unless buffering emit earlier to stream
|
|
922
|
-
self.emit('end');
|
|
923
|
-
self.callback(null, self._emitResponse());
|
|
932
|
+
this.callback(err, null);
|
|
924
933
|
});
|
|
934
|
+
if (!parserHandlesEnd)
|
|
935
|
+
res.once('end', () => {
|
|
936
|
+
debug('end %s %s', this.method, this.url);
|
|
937
|
+
// TODO: unless buffering emit earlier to stream
|
|
938
|
+
this.emit('end');
|
|
939
|
+
this.callback(null, this._emitResponse());
|
|
940
|
+
});
|
|
925
941
|
});
|
|
926
942
|
|
|
927
943
|
this.emit('request', this);
|
|
928
944
|
|
|
929
945
|
// if a FormData instance got created, then we send that as the request body
|
|
930
|
-
|
|
946
|
+
const formData = this._formData;
|
|
931
947
|
if (formData) {
|
|
932
948
|
|
|
933
949
|
// set headers
|
|
934
|
-
|
|
935
|
-
for (
|
|
950
|
+
const headers = formData.getHeaders();
|
|
951
|
+
for (const i in headers) {
|
|
936
952
|
debug('setting FormData header: "%s: %s"', i, headers[i]);
|
|
937
953
|
req.setHeader(i, headers[i]);
|
|
938
954
|
}
|
|
939
955
|
|
|
940
956
|
// attempt to get "Content-Length" header
|
|
941
|
-
formData.getLength(
|
|
957
|
+
formData.getLength((err, length) => {
|
|
942
958
|
// TODO: Add chunked encoding when no length (if err)
|
|
943
959
|
|
|
944
960
|
debug('got FormData Content-Length: %s', length);
|
|
@@ -946,19 +962,19 @@ Request.prototype._end = function() {
|
|
|
946
962
|
req.setHeader('Content-Length', length);
|
|
947
963
|
}
|
|
948
964
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
965
|
+
const getProgressMonitor = () => {
|
|
966
|
+
const lengthComputable = true;
|
|
967
|
+
const total = req.getHeader('Content-Length');
|
|
968
|
+
let loaded = 0;
|
|
953
969
|
|
|
954
|
-
|
|
955
|
-
progress._transform =
|
|
970
|
+
const progress = new Stream.Transform();
|
|
971
|
+
progress._transform = (chunk, encoding, cb) => {
|
|
956
972
|
loaded += chunk.length;
|
|
957
|
-
|
|
973
|
+
this.emit('progress', {
|
|
958
974
|
direction: 'upload',
|
|
959
|
-
lengthComputable
|
|
960
|
-
loaded
|
|
961
|
-
total
|
|
975
|
+
lengthComputable,
|
|
976
|
+
loaded,
|
|
977
|
+
total,
|
|
962
978
|
});
|
|
963
979
|
cb(null, chunk);
|
|
964
980
|
};
|
|
@@ -976,7 +992,7 @@ Request.prototype._end = function() {
|
|
|
976
992
|
/**
|
|
977
993
|
* Check whether response has a non-0-sized gzip-encoded body
|
|
978
994
|
*/
|
|
979
|
-
Request.prototype._shouldUnzip =
|
|
995
|
+
Request.prototype._shouldUnzip = res => {
|
|
980
996
|
if (res.statusCode === 204 || res.statusCode === 304) {
|
|
981
997
|
// These aren't supposed to have any body
|
|
982
998
|
return false;
|
|
@@ -1000,14 +1016,14 @@ if (methods.indexOf('del') == -1) {
|
|
|
1000
1016
|
methods = methods.slice(0);
|
|
1001
1017
|
methods.push('del');
|
|
1002
1018
|
}
|
|
1003
|
-
methods.forEach(
|
|
1004
|
-
|
|
1019
|
+
methods.forEach(method => {
|
|
1020
|
+
const name = method;
|
|
1005
1021
|
method = 'del' == method ? 'delete' : method;
|
|
1006
1022
|
|
|
1007
1023
|
method = method.toUpperCase();
|
|
1008
|
-
request[name] =
|
|
1009
|
-
|
|
1010
|
-
if ('function' == typeof data) fn = data, data = null;
|
|
1024
|
+
request[name] = (url, data, fn) => {
|
|
1025
|
+
const req = request(method, url);
|
|
1026
|
+
if ('function' == typeof data) (fn = data), (data = null);
|
|
1011
1027
|
if (data) {
|
|
1012
1028
|
if (method === 'GET' || method === 'HEAD') {
|
|
1013
1029
|
req.query(data);
|
|
@@ -1029,16 +1045,15 @@ methods.forEach(function(method){
|
|
|
1029
1045
|
*/
|
|
1030
1046
|
|
|
1031
1047
|
function isText(mime) {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1048
|
+
const parts = mime.split('/');
|
|
1049
|
+
const type = parts[0];
|
|
1050
|
+
const subtype = parts[1];
|
|
1035
1051
|
|
|
1036
|
-
return 'text' == type
|
|
1037
|
-
|| 'x-www-form-urlencoded' == subtype;
|
|
1052
|
+
return 'text' == type || 'x-www-form-urlencoded' == subtype;
|
|
1038
1053
|
}
|
|
1039
1054
|
|
|
1040
1055
|
function isImageOrVideo(mime) {
|
|
1041
|
-
|
|
1056
|
+
const type = mime.split('/')[0];
|
|
1042
1057
|
|
|
1043
1058
|
return 'image' == type || 'video' == type;
|
|
1044
1059
|
}
|
|
@@ -1052,7 +1067,9 @@ function isImageOrVideo(mime) {
|
|
|
1052
1067
|
*/
|
|
1053
1068
|
|
|
1054
1069
|
function isJSON(mime) {
|
|
1055
|
-
|
|
1070
|
+
// should match /json or +json
|
|
1071
|
+
// but not /json-seq
|
|
1072
|
+
return /[\/+]json($|[^-\w])/.test(mime);
|
|
1056
1073
|
}
|
|
1057
1074
|
|
|
1058
1075
|
/**
|