Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80676 views
1
'use strict'
2
3
var http = require('http')
4
, https = require('https')
5
, url = require('url')
6
, util = require('util')
7
, stream = require('stream')
8
, zlib = require('zlib')
9
, helpers = require('./lib/helpers')
10
, bl = require('bl')
11
, hawk = require('hawk')
12
, aws = require('aws-sign2')
13
, httpSignature = require('http-signature')
14
, mime = require('mime-types')
15
, tunnel = require('tunnel-agent')
16
, stringstream = require('stringstream')
17
, caseless = require('caseless')
18
, ForeverAgent = require('forever-agent')
19
, FormData = require('form-data')
20
, cookies = require('./lib/cookies')
21
, copy = require('./lib/copy')
22
, getProxyFromURI = require('./lib/getProxyFromURI')
23
, Querystring = require('./lib/querystring').Querystring
24
, Har = require('./lib/har').Har
25
, Auth = require('./lib/auth').Auth
26
, OAuth = require('./lib/oauth').OAuth
27
, Multipart = require('./lib/multipart').Multipart
28
, Redirect = require('./lib/redirect').Redirect
29
30
var safeStringify = helpers.safeStringify
31
, isReadStream = helpers.isReadStream
32
, toBase64 = helpers.toBase64
33
, defer = helpers.defer
34
, globalCookieJar = cookies.jar()
35
36
37
var globalPool = {}
38
39
var defaultProxyHeaderWhiteList = [
40
'accept',
41
'accept-charset',
42
'accept-encoding',
43
'accept-language',
44
'accept-ranges',
45
'cache-control',
46
'content-encoding',
47
'content-language',
48
'content-length',
49
'content-location',
50
'content-md5',
51
'content-range',
52
'content-type',
53
'connection',
54
'date',
55
'expect',
56
'max-forwards',
57
'pragma',
58
'referer',
59
'te',
60
'transfer-encoding',
61
'user-agent',
62
'via'
63
]
64
65
var defaultProxyHeaderExclusiveList = [
66
'proxy-authorization'
67
]
68
69
function filterForNonReserved(reserved, options) {
70
// Filter out properties that are not reserved.
71
// Reserved values are passed in at call site.
72
73
var object = {}
74
for (var i in options) {
75
var notReserved = (reserved.indexOf(i) === -1)
76
if (notReserved) {
77
object[i] = options[i]
78
}
79
}
80
return object
81
}
82
83
function filterOutReservedFunctions(reserved, options) {
84
// Filter out properties that are functions and are reserved.
85
// Reserved values are passed in at call site.
86
87
var object = {}
88
for (var i in options) {
89
var isReserved = !(reserved.indexOf(i) === -1)
90
var isFunction = (typeof options[i] === 'function')
91
if (!(isReserved && isFunction)) {
92
object[i] = options[i]
93
}
94
}
95
return object
96
97
}
98
99
function constructProxyHost(uriObject) {
100
var port = uriObject.portA
101
, protocol = uriObject.protocol
102
, proxyHost = uriObject.hostname + ':'
103
104
if (port) {
105
proxyHost += port
106
} else if (protocol === 'https:') {
107
proxyHost += '443'
108
} else {
109
proxyHost += '80'
110
}
111
112
return proxyHost
113
}
114
115
function constructProxyHeaderWhiteList(headers, proxyHeaderWhiteList) {
116
var whiteList = proxyHeaderWhiteList
117
.reduce(function (set, header) {
118
set[header.toLowerCase()] = true
119
return set
120
}, {})
121
122
return Object.keys(headers)
123
.filter(function (header) {
124
return whiteList[header.toLowerCase()]
125
})
126
.reduce(function (set, header) {
127
set[header] = headers[header]
128
return set
129
}, {})
130
}
131
132
function getTunnelOption(self, options) {
133
// Tunnel HTTPS by default, or if a previous request in the redirect chain
134
// was tunneled. Allow the user to override this setting.
135
136
// If self.tunnel is already set (because this is a redirect), use the
137
// existing value.
138
if (typeof self.tunnel !== 'undefined') {
139
return self.tunnel
140
}
141
142
// If options.tunnel is set (the user specified a value), use it.
143
if (typeof options.tunnel !== 'undefined') {
144
return options.tunnel
145
}
146
147
// If the destination is HTTPS, tunnel.
148
if (self.uri.protocol === 'https:') {
149
return true
150
}
151
152
// Otherwise, leave tunnel unset, because if a later request in the redirect
153
// chain is HTTPS then that request (and any subsequent ones) should be
154
// tunneled.
155
return undefined
156
}
157
158
function constructTunnelOptions(request) {
159
var proxy = request.proxy
160
161
var tunnelOptions = {
162
proxy : {
163
host : proxy.hostname,
164
port : +proxy.port,
165
proxyAuth : proxy.auth,
166
headers : request.proxyHeaders
167
},
168
headers : request.headers,
169
ca : request.ca,
170
cert : request.cert,
171
key : request.key,
172
passphrase : request.passphrase,
173
pfx : request.pfx,
174
ciphers : request.ciphers,
175
rejectUnauthorized : request.rejectUnauthorized,
176
secureOptions : request.secureOptions,
177
secureProtocol : request.secureProtocol
178
}
179
180
return tunnelOptions
181
}
182
183
function constructTunnelFnName(uri, proxy) {
184
var uriProtocol = (uri.protocol === 'https:' ? 'https' : 'http')
185
var proxyProtocol = (proxy.protocol === 'https:' ? 'Https' : 'Http')
186
return [uriProtocol, proxyProtocol].join('Over')
187
}
188
189
function getTunnelFn(request) {
190
var uri = request.uri
191
var proxy = request.proxy
192
var tunnelFnName = constructTunnelFnName(uri, proxy)
193
return tunnel[tunnelFnName]
194
}
195
196
// Function for properly handling a connection error
197
function connectionErrorHandler(error) {
198
var socket = this
199
if (socket.res) {
200
if (socket.res.request) {
201
socket.res.request.emit('error', error)
202
} else {
203
socket.res.emit('error', error)
204
}
205
} else {
206
socket._httpMessage.emit('error', error)
207
}
208
}
209
210
// Return a simpler request object to allow serialization
211
function requestToJSON() {
212
var self = this
213
return {
214
uri: self.uri,
215
method: self.method,
216
headers: self.headers
217
}
218
}
219
220
// Return a simpler response object to allow serialization
221
function responseToJSON() {
222
var self = this
223
return {
224
statusCode: self.statusCode,
225
body: self.body,
226
headers: self.headers,
227
request: requestToJSON.call(self.request)
228
}
229
}
230
231
function Request (options) {
232
// if given the method property in options, set property explicitMethod to true
233
234
// extend the Request instance with any non-reserved properties
235
// remove any reserved functions from the options object
236
// set Request instance to be readable and writable
237
// call init
238
239
var self = this
240
241
// start with HAR, then override with additional options
242
if (options.har) {
243
self._har = new Har(self)
244
options = self._har.options(options)
245
}
246
247
stream.Stream.call(self)
248
var reserved = Object.keys(Request.prototype)
249
var nonReserved = filterForNonReserved(reserved, options)
250
251
stream.Stream.call(self)
252
util._extend(self, nonReserved)
253
options = filterOutReservedFunctions(reserved, options)
254
255
self.readable = true
256
self.writable = true
257
if (options.method) {
258
self.explicitMethod = true
259
}
260
self._qs = new Querystring(self)
261
self._auth = new Auth(self)
262
self._oauth = new OAuth(self)
263
self._multipart = new Multipart(self)
264
self._redirect = new Redirect(self)
265
self.init(options)
266
}
267
268
util.inherits(Request, stream.Stream)
269
270
// Debugging
271
Request.debug = process.env.NODE_DEBUG && /\brequest\b/.test(process.env.NODE_DEBUG)
272
function debug() {
273
if (Request.debug) {
274
console.error('REQUEST %s', util.format.apply(util, arguments))
275
}
276
}
277
Request.prototype.debug = debug
278
279
Request.prototype.setupTunnel = function () {
280
var self = this
281
282
if (typeof self.proxy === 'string') {
283
self.proxy = url.parse(self.proxy)
284
}
285
286
if (!self.proxy || !self.tunnel) {
287
return false
288
}
289
290
// Setup Proxy Header Exclusive List and White List
291
self.proxyHeaderExclusiveList = self.proxyHeaderExclusiveList || []
292
self.proxyHeaderWhiteList = self.proxyHeaderWhiteList || defaultProxyHeaderWhiteList
293
var proxyHeaderExclusiveList = self.proxyHeaderExclusiveList.concat(defaultProxyHeaderExclusiveList)
294
var proxyHeaderWhiteList = self.proxyHeaderWhiteList.concat(proxyHeaderExclusiveList)
295
296
// Setup Proxy Headers and Proxy Headers Host
297
// Only send the Proxy White Listed Header names
298
self.proxyHeaders = constructProxyHeaderWhiteList(self.headers, proxyHeaderWhiteList)
299
self.proxyHeaders.host = constructProxyHost(self.uri)
300
proxyHeaderExclusiveList.forEach(self.removeHeader, self)
301
302
// Set Agent from Tunnel Data
303
var tunnelFn = getTunnelFn(self)
304
var tunnelOptions = constructTunnelOptions(self)
305
self.agent = tunnelFn(tunnelOptions)
306
307
return true
308
}
309
310
Request.prototype.init = function (options) {
311
// init() contains all the code to setup the request object.
312
// the actual outgoing request is not started until start() is called
313
// this function is called from both the constructor and on redirect.
314
var self = this
315
if (!options) {
316
options = {}
317
}
318
self.headers = self.headers ? copy(self.headers) : {}
319
320
// Delete headers with value undefined since they break
321
// ClientRequest.OutgoingMessage.setHeader in node 0.12
322
for (var headerName in self.headers) {
323
if (typeof self.headers[headerName] === 'undefined') {
324
delete self.headers[headerName]
325
}
326
}
327
328
caseless.httpify(self, self.headers)
329
330
if (!self.method) {
331
self.method = options.method || 'GET'
332
}
333
if (!self.localAddress) {
334
self.localAddress = options.localAddress
335
}
336
337
self._qs.init(options)
338
339
debug(options)
340
if (!self.pool && self.pool !== false) {
341
self.pool = globalPool
342
}
343
self.dests = self.dests || []
344
self.__isRequestRequest = true
345
346
// Protect against double callback
347
if (!self._callback && self.callback) {
348
self._callback = self.callback
349
self.callback = function () {
350
if (self._callbackCalled) {
351
return // Print a warning maybe?
352
}
353
self._callbackCalled = true
354
self._callback.apply(self, arguments)
355
}
356
self.on('error', self.callback.bind())
357
self.on('complete', self.callback.bind(self, null))
358
}
359
360
// People use this property instead all the time, so support it
361
if (!self.uri && self.url) {
362
self.uri = self.url
363
delete self.url
364
}
365
366
// If there's a baseUrl, then use it as the base URL (i.e. uri must be
367
// specified as a relative path and is appended to baseUrl).
368
if (self.baseUrl) {
369
if (typeof self.baseUrl !== 'string') {
370
return self.emit('error', new Error('options.baseUrl must be a string'))
371
}
372
373
if (typeof self.uri !== 'string') {
374
return self.emit('error', new Error('options.uri must be a string when using options.baseUrl'))
375
}
376
377
if (self.uri.indexOf('//') === 0 || self.uri.indexOf('://') !== -1) {
378
return self.emit('error', new Error('options.uri must be a path when using options.baseUrl'))
379
}
380
381
// Handle all cases to make sure that there's only one slash between
382
// baseUrl and uri.
383
var baseUrlEndsWithSlash = self.baseUrl.lastIndexOf('/') === self.baseUrl.length - 1
384
var uriStartsWithSlash = self.uri.indexOf('/') === 0
385
386
if (baseUrlEndsWithSlash && uriStartsWithSlash) {
387
self.uri = self.baseUrl + self.uri.slice(1)
388
} else if (baseUrlEndsWithSlash || uriStartsWithSlash) {
389
self.uri = self.baseUrl + self.uri
390
} else if (self.uri === '') {
391
self.uri = self.baseUrl
392
} else {
393
self.uri = self.baseUrl + '/' + self.uri
394
}
395
delete self.baseUrl
396
}
397
398
// A URI is needed by this point, emit error if we haven't been able to get one
399
if (!self.uri) {
400
return self.emit('error', new Error('options.uri is a required argument'))
401
}
402
403
// If a string URI/URL was given, parse it into a URL object
404
if (typeof self.uri === 'string') {
405
self.uri = url.parse(self.uri)
406
}
407
408
// DEPRECATED: Warning for users of the old Unix Sockets URL Scheme
409
if (self.uri.protocol === 'unix:') {
410
return self.emit('error', new Error('`unix://` URL scheme is no longer supported. Please use the format `http://unix:SOCKET:PATH`'))
411
}
412
413
// Support Unix Sockets
414
if (self.uri.host === 'unix') {
415
// Get the socket & request paths from the URL
416
var unixParts = self.uri.path.split(':')
417
, host = unixParts[0]
418
, path = unixParts[1]
419
// Apply unix properties to request
420
self.socketPath = host
421
self.uri.pathname = path
422
self.uri.path = path
423
self.uri.host = host
424
self.uri.hostname = host
425
self.uri.isUnix = true
426
}
427
428
if (self.strictSSL === false) {
429
self.rejectUnauthorized = false
430
}
431
432
if (!self.uri.pathname) {self.uri.pathname = '/'}
433
434
if (!(self.uri.host || (self.uri.hostname && self.uri.port)) && !self.uri.isUnix) {
435
// Invalid URI: it may generate lot of bad errors, like 'TypeError: Cannot call method `indexOf` of undefined' in CookieJar
436
// Detect and reject it as soon as possible
437
var faultyUri = url.format(self.uri)
438
var message = 'Invalid URI "' + faultyUri + '"'
439
if (Object.keys(options).length === 0) {
440
// No option ? This can be the sign of a redirect
441
// As this is a case where the user cannot do anything (they didn't call request directly with this URL)
442
// they should be warned that it can be caused by a redirection (can save some hair)
443
message += '. This can be caused by a crappy redirection.'
444
}
445
// This error was fatal
446
return self.emit('error', new Error(message))
447
}
448
449
if (!self.hasOwnProperty('proxy')) {
450
self.proxy = getProxyFromURI(self.uri)
451
}
452
453
self.tunnel = getTunnelOption(self, options)
454
if (self.proxy) {
455
self.setupTunnel()
456
}
457
458
self._redirect.onRequest(options)
459
460
self.setHost = false
461
if (!self.hasHeader('host')) {
462
var hostHeaderName = self.originalHostHeaderName || 'host'
463
self.setHeader(hostHeaderName, self.uri.hostname)
464
if (self.uri.port) {
465
if ( !(self.uri.port === 80 && self.uri.protocol === 'http:') &&
466
!(self.uri.port === 443 && self.uri.protocol === 'https:') ) {
467
self.setHeader(hostHeaderName, self.getHeader('host') + (':' + self.uri.port) )
468
}
469
}
470
self.setHost = true
471
}
472
473
self.jar(self._jar || options.jar)
474
475
if (!self.uri.port) {
476
if (self.uri.protocol === 'http:') {self.uri.port = 80}
477
else if (self.uri.protocol === 'https:') {self.uri.port = 443}
478
}
479
480
if (self.proxy && !self.tunnel) {
481
self.port = self.proxy.port
482
self.host = self.proxy.hostname
483
} else {
484
self.port = self.uri.port
485
self.host = self.uri.hostname
486
}
487
488
if (options.form) {
489
self.form(options.form)
490
}
491
492
if (options.formData) {
493
var formData = options.formData
494
var requestForm = self.form()
495
var appendFormValue = function (key, value) {
496
if (value.hasOwnProperty('value') && value.hasOwnProperty('options')) {
497
requestForm.append(key, value.value, value.options)
498
} else {
499
requestForm.append(key, value)
500
}
501
}
502
for (var formKey in formData) {
503
if (formData.hasOwnProperty(formKey)) {
504
var formValue = formData[formKey]
505
if (formValue instanceof Array) {
506
for (var j = 0; j < formValue.length; j++) {
507
appendFormValue(formKey, formValue[j])
508
}
509
} else {
510
appendFormValue(formKey, formValue)
511
}
512
}
513
}
514
}
515
516
if (options.qs) {
517
self.qs(options.qs)
518
}
519
520
if (self.uri.path) {
521
self.path = self.uri.path
522
} else {
523
self.path = self.uri.pathname + (self.uri.search || '')
524
}
525
526
if (self.path.length === 0) {
527
self.path = '/'
528
}
529
530
// Auth must happen last in case signing is dependent on other headers
531
if (options.aws) {
532
self.aws(options.aws)
533
}
534
535
if (options.hawk) {
536
self.hawk(options.hawk)
537
}
538
539
if (options.httpSignature) {
540
self.httpSignature(options.httpSignature)
541
}
542
543
if (options.auth) {
544
if (Object.prototype.hasOwnProperty.call(options.auth, 'username')) {
545
options.auth.user = options.auth.username
546
}
547
if (Object.prototype.hasOwnProperty.call(options.auth, 'password')) {
548
options.auth.pass = options.auth.password
549
}
550
551
self.auth(
552
options.auth.user,
553
options.auth.pass,
554
options.auth.sendImmediately,
555
options.auth.bearer
556
)
557
}
558
559
if (self.gzip && !self.hasHeader('accept-encoding')) {
560
self.setHeader('accept-encoding', 'gzip')
561
}
562
563
if (self.uri.auth && !self.hasHeader('authorization')) {
564
var uriAuthPieces = self.uri.auth.split(':').map(function(item) {return self._qs.unescape(item)})
565
self.auth(uriAuthPieces[0], uriAuthPieces.slice(1).join(':'), true)
566
}
567
568
if (!self.tunnel && self.proxy && self.proxy.auth && !self.hasHeader('proxy-authorization')) {
569
var proxyAuthPieces = self.proxy.auth.split(':').map(function(item) {return self._qs.unescape(item)})
570
var authHeader = 'Basic ' + toBase64(proxyAuthPieces.join(':'))
571
self.setHeader('proxy-authorization', authHeader)
572
}
573
574
if (self.proxy && !self.tunnel) {
575
self.path = (self.uri.protocol + '//' + self.uri.host + self.path)
576
}
577
578
if (options.json) {
579
self.json(options.json)
580
}
581
if (options.multipart) {
582
self.multipart(options.multipart)
583
}
584
585
if (options.time) {
586
self.timing = true
587
self.elapsedTime = self.elapsedTime || 0
588
}
589
590
if (self.body) {
591
var length = 0
592
if (!Buffer.isBuffer(self.body)) {
593
if (Array.isArray(self.body)) {
594
for (var i = 0; i < self.body.length; i++) {
595
length += self.body[i].length
596
}
597
} else {
598
self.body = new Buffer(self.body)
599
length = self.body.length
600
}
601
} else {
602
length = self.body.length
603
}
604
if (length) {
605
if (!self.hasHeader('content-length')) {
606
self.setHeader('content-length', length)
607
}
608
} else {
609
self.emit('error', new Error('Argument error, options.body.'))
610
}
611
}
612
613
if (options.oauth) {
614
self.oauth(options.oauth)
615
} else if (self._oauth.params && self.hasHeader('authorization')) {
616
self.oauth(self._oauth.params)
617
}
618
619
var protocol = self.proxy && !self.tunnel ? self.proxy.protocol : self.uri.protocol
620
, defaultModules = {'http:':http, 'https:':https}
621
, httpModules = self.httpModules || {}
622
623
self.httpModule = httpModules[protocol] || defaultModules[protocol]
624
625
if (!self.httpModule) {
626
return self.emit('error', new Error('Invalid protocol: ' + protocol))
627
}
628
629
if (options.ca) {
630
self.ca = options.ca
631
}
632
633
if (!self.agent) {
634
if (options.agentOptions) {
635
self.agentOptions = options.agentOptions
636
}
637
638
if (options.agentClass) {
639
self.agentClass = options.agentClass
640
} else if (options.forever) {
641
self.agentClass = protocol === 'http:' ? ForeverAgent : ForeverAgent.SSL
642
} else {
643
self.agentClass = self.httpModule.Agent
644
}
645
}
646
647
if (self.pool === false) {
648
self.agent = false
649
} else {
650
self.agent = self.agent || self.getNewAgent()
651
}
652
653
self.on('pipe', function (src) {
654
if (self.ntick && self._started) {
655
self.emit('error', new Error('You cannot pipe to this stream after the outbound request has started.'))
656
}
657
self.src = src
658
if (isReadStream(src)) {
659
if (!self.hasHeader('content-type')) {
660
self.setHeader('content-type', mime.lookup(src.path))
661
}
662
} else {
663
if (src.headers) {
664
for (var i in src.headers) {
665
if (!self.hasHeader(i)) {
666
self.setHeader(i, src.headers[i])
667
}
668
}
669
}
670
if (self._json && !self.hasHeader('content-type')) {
671
self.setHeader('content-type', 'application/json')
672
}
673
if (src.method && !self.explicitMethod) {
674
self.method = src.method
675
}
676
}
677
678
// self.on('pipe', function () {
679
// console.error('You have already piped to this stream. Pipeing twice is likely to break the request.')
680
// })
681
})
682
683
defer(function () {
684
if (self._aborted) {
685
return
686
}
687
688
var end = function () {
689
if (self._form) {
690
if (!self._auth.hasAuth) {
691
self._form.pipe(self)
692
}
693
else if (self._auth.hasAuth && self._auth.sentAuth) {
694
self._form.pipe(self)
695
}
696
}
697
if (self._multipart && self._multipart.chunked) {
698
self._multipart.body.pipe(self)
699
}
700
if (self.body) {
701
if (Array.isArray(self.body)) {
702
self.body.forEach(function (part) {
703
self.write(part)
704
})
705
} else {
706
self.write(self.body)
707
}
708
self.end()
709
} else if (self.requestBodyStream) {
710
console.warn('options.requestBodyStream is deprecated, please pass the request object to stream.pipe.')
711
self.requestBodyStream.pipe(self)
712
} else if (!self.src) {
713
if (self._auth.hasAuth && !self._auth.sentAuth) {
714
self.end()
715
return
716
}
717
if (self.method !== 'GET' && typeof self.method !== 'undefined') {
718
self.setHeader('content-length', 0)
719
}
720
self.end()
721
}
722
}
723
724
if (self._form && !self.hasHeader('content-length')) {
725
// Before ending the request, we had to compute the length of the whole form, asyncly
726
self.setHeader(self._form.getHeaders())
727
self._form.getLength(function (err, length) {
728
if (!err) {
729
self.setHeader('content-length', length)
730
}
731
end()
732
})
733
} else {
734
end()
735
}
736
737
self.ntick = true
738
})
739
740
}
741
742
// Must call this when following a redirect from https to http or vice versa
743
// Attempts to keep everything as identical as possible, but update the
744
// httpModule, Tunneling agent, and/or Forever Agent in use.
745
Request.prototype._updateProtocol = function () {
746
var self = this
747
var protocol = self.uri.protocol
748
749
if (protocol === 'https:' || self.tunnel) {
750
// previously was doing http, now doing https
751
// if it's https, then we might need to tunnel now.
752
if (self.proxy) {
753
if (self.setupTunnel()) {
754
return
755
}
756
}
757
758
self.httpModule = https
759
switch (self.agentClass) {
760
case ForeverAgent:
761
self.agentClass = ForeverAgent.SSL
762
break
763
case http.Agent:
764
self.agentClass = https.Agent
765
break
766
default:
767
// nothing we can do. Just hope for the best.
768
return
769
}
770
771
// if there's an agent, we need to get a new one.
772
if (self.agent) {
773
self.agent = self.getNewAgent()
774
}
775
776
} else {
777
// previously was doing https, now doing http
778
self.httpModule = http
779
switch (self.agentClass) {
780
case ForeverAgent.SSL:
781
self.agentClass = ForeverAgent
782
break
783
case https.Agent:
784
self.agentClass = http.Agent
785
break
786
default:
787
// nothing we can do. just hope for the best
788
return
789
}
790
791
// if there's an agent, then get a new one.
792
if (self.agent) {
793
self.agent = null
794
self.agent = self.getNewAgent()
795
}
796
}
797
}
798
799
Request.prototype.getNewAgent = function () {
800
var self = this
801
var Agent = self.agentClass
802
var options = {}
803
if (self.agentOptions) {
804
for (var i in self.agentOptions) {
805
options[i] = self.agentOptions[i]
806
}
807
}
808
if (self.ca) {
809
options.ca = self.ca
810
}
811
if (self.ciphers) {
812
options.ciphers = self.ciphers
813
}
814
if (self.secureProtocol) {
815
options.secureProtocol = self.secureProtocol
816
}
817
if (self.secureOptions) {
818
options.secureOptions = self.secureOptions
819
}
820
if (typeof self.rejectUnauthorized !== 'undefined') {
821
options.rejectUnauthorized = self.rejectUnauthorized
822
}
823
824
if (self.cert && self.key) {
825
options.key = self.key
826
options.cert = self.cert
827
}
828
829
if (self.pfx) {
830
options.pfx = self.pfx
831
}
832
833
if (self.passphrase) {
834
options.passphrase = self.passphrase
835
}
836
837
var poolKey = ''
838
839
// different types of agents are in different pools
840
if (Agent !== self.httpModule.Agent) {
841
poolKey += Agent.name
842
}
843
844
// ca option is only relevant if proxy or destination are https
845
var proxy = self.proxy
846
if (typeof proxy === 'string') {
847
proxy = url.parse(proxy)
848
}
849
var isHttps = (proxy && proxy.protocol === 'https:') || this.uri.protocol === 'https:'
850
851
if (isHttps) {
852
if (options.ca) {
853
if (poolKey) {
854
poolKey += ':'
855
}
856
poolKey += options.ca
857
}
858
859
if (typeof options.rejectUnauthorized !== 'undefined') {
860
if (poolKey) {
861
poolKey += ':'
862
}
863
poolKey += options.rejectUnauthorized
864
}
865
866
if (options.cert) {
867
if (poolKey) {
868
poolKey += ':'
869
}
870
poolKey += options.cert.toString('ascii') + options.key.toString('ascii')
871
}
872
873
if (options.pfx) {
874
if (poolKey) {
875
poolKey += ':'
876
}
877
poolKey += options.pfx.toString('ascii')
878
}
879
880
if (options.ciphers) {
881
if (poolKey) {
882
poolKey += ':'
883
}
884
poolKey += options.ciphers
885
}
886
887
if (options.secureProtocol) {
888
if (poolKey) {
889
poolKey += ':'
890
}
891
poolKey += options.secureProtocol
892
}
893
894
if (options.secureOptions) {
895
if (poolKey) {
896
poolKey += ':'
897
}
898
poolKey += options.secureOptions
899
}
900
}
901
902
if (self.pool === globalPool && !poolKey && Object.keys(options).length === 0 && self.httpModule.globalAgent) {
903
// not doing anything special. Use the globalAgent
904
return self.httpModule.globalAgent
905
}
906
907
// we're using a stored agent. Make sure it's protocol-specific
908
poolKey = self.uri.protocol + poolKey
909
910
// generate a new agent for this setting if none yet exists
911
if (!self.pool[poolKey]) {
912
self.pool[poolKey] = new Agent(options)
913
// properly set maxSockets on new agents
914
if (self.pool.maxSockets) {
915
self.pool[poolKey].maxSockets = self.pool.maxSockets
916
}
917
}
918
919
return self.pool[poolKey]
920
}
921
922
Request.prototype.start = function () {
923
// start() is called once we are ready to send the outgoing HTTP request.
924
// this is usually called on the first write(), end() or on nextTick()
925
var self = this
926
927
if (self._aborted) {
928
return
929
}
930
931
self._started = true
932
self.method = self.method || 'GET'
933
self.href = self.uri.href
934
935
if (self.src && self.src.stat && self.src.stat.size && !self.hasHeader('content-length')) {
936
self.setHeader('content-length', self.src.stat.size)
937
}
938
if (self._aws) {
939
self.aws(self._aws, true)
940
}
941
942
// We have a method named auth, which is completely different from the http.request
943
// auth option. If we don't remove it, we're gonna have a bad time.
944
var reqOptions = copy(self)
945
delete reqOptions.auth
946
947
debug('make request', self.uri.href)
948
949
self.req = self.httpModule.request(reqOptions)
950
951
if (self.timing) {
952
self.startTime = new Date().getTime()
953
}
954
955
if (self.timeout && !self.timeoutTimer) {
956
var timeout = self.timeout < 0 ? 0 : self.timeout
957
self.timeoutTimer = setTimeout(function () {
958
self.abort()
959
var e = new Error('ETIMEDOUT')
960
e.code = 'ETIMEDOUT'
961
self.emit('error', e)
962
}, timeout)
963
964
// Set additional timeout on socket - in case if remote
965
// server freeze after sending headers
966
if (self.req.setTimeout) { // only works on node 0.6+
967
self.req.setTimeout(timeout, function () {
968
if (self.req) {
969
self.req.abort()
970
var e = new Error('ESOCKETTIMEDOUT')
971
e.code = 'ESOCKETTIMEDOUT'
972
self.emit('error', e)
973
}
974
})
975
}
976
}
977
978
self.req.on('response', self.onRequestResponse.bind(self))
979
self.req.on('error', self.onRequestError.bind(self))
980
self.req.on('drain', function() {
981
self.emit('drain')
982
})
983
self.req.on('socket', function(socket) {
984
self.emit('socket', socket)
985
})
986
987
self.on('end', function() {
988
if ( self.req.connection ) {
989
self.req.connection.removeListener('error', connectionErrorHandler)
990
}
991
})
992
self.emit('request', self.req)
993
}
994
995
Request.prototype.onRequestError = function (error) {
996
var self = this
997
if (self._aborted) {
998
return
999
}
1000
if (self.req && self.req._reusedSocket && error.code === 'ECONNRESET'
1001
&& self.agent.addRequestNoreuse) {
1002
self.agent = { addRequest: self.agent.addRequestNoreuse.bind(self.agent) }
1003
self.start()
1004
self.req.end()
1005
return
1006
}
1007
if (self.timeout && self.timeoutTimer) {
1008
clearTimeout(self.timeoutTimer)
1009
self.timeoutTimer = null
1010
}
1011
self.emit('error', error)
1012
}
1013
1014
Request.prototype.onRequestResponse = function (response) {
1015
var self = this
1016
debug('onRequestResponse', self.uri.href, response.statusCode, response.headers)
1017
response.on('end', function() {
1018
if (self.timing) {
1019
self.elapsedTime += (new Date().getTime() - self.startTime)
1020
debug('elapsed time', self.elapsedTime)
1021
response.elapsedTime = self.elapsedTime
1022
}
1023
debug('response end', self.uri.href, response.statusCode, response.headers)
1024
})
1025
1026
// The check on response.connection is a workaround for browserify.
1027
if (response.connection && response.connection.listeners('error').indexOf(connectionErrorHandler) === -1) {
1028
response.connection.setMaxListeners(0)
1029
response.connection.once('error', connectionErrorHandler)
1030
}
1031
if (self._aborted) {
1032
debug('aborted', self.uri.href)
1033
response.resume()
1034
return
1035
}
1036
1037
self.response = response
1038
response.request = self
1039
response.toJSON = responseToJSON
1040
1041
// XXX This is different on 0.10, because SSL is strict by default
1042
if (self.httpModule === https &&
1043
self.strictSSL && (!response.hasOwnProperty('client') ||
1044
!response.client.authorized)) {
1045
debug('strict ssl error', self.uri.href)
1046
var sslErr = response.hasOwnProperty('client') ? response.client.authorizationError : self.uri.href + ' does not support SSL'
1047
self.emit('error', new Error('SSL Error: ' + sslErr))
1048
return
1049
}
1050
1051
// Save the original host before any redirect (if it changes, we need to
1052
// remove any authorization headers). Also remember the case of the header
1053
// name because lots of broken servers expect Host instead of host and we
1054
// want the caller to be able to specify this.
1055
self.originalHost = self.getHeader('host')
1056
if (!self.originalHostHeaderName) {
1057
self.originalHostHeaderName = self.hasHeader('host')
1058
}
1059
if (self.setHost) {
1060
self.removeHeader('host')
1061
}
1062
if (self.timeout && self.timeoutTimer) {
1063
clearTimeout(self.timeoutTimer)
1064
self.timeoutTimer = null
1065
}
1066
1067
var targetCookieJar = (self._jar && self._jar.setCookie) ? self._jar : globalCookieJar
1068
var addCookie = function (cookie) {
1069
//set the cookie if it's domain in the href's domain.
1070
try {
1071
targetCookieJar.setCookie(cookie, self.uri.href, {ignoreError: true})
1072
} catch (e) {
1073
self.emit('error', e)
1074
}
1075
}
1076
1077
response.caseless = caseless(response.headers)
1078
1079
if (response.caseless.has('set-cookie') && (!self._disableCookies)) {
1080
var headerName = response.caseless.has('set-cookie')
1081
if (Array.isArray(response.headers[headerName])) {
1082
response.headers[headerName].forEach(addCookie)
1083
} else {
1084
addCookie(response.headers[headerName])
1085
}
1086
}
1087
1088
if (self._redirect.onResponse(response)) {
1089
return // Ignore the rest of the response
1090
} else {
1091
// Be a good stream and emit end when the response is finished.
1092
// Hack to emit end on close because of a core bug that never fires end
1093
response.on('close', function () {
1094
if (!self._ended) {
1095
self.response.emit('end')
1096
}
1097
})
1098
1099
response.on('end', function () {
1100
self._ended = true
1101
})
1102
1103
var responseContent
1104
if (self.gzip) {
1105
var contentEncoding = response.headers['content-encoding'] || 'identity'
1106
contentEncoding = contentEncoding.trim().toLowerCase()
1107
1108
if (contentEncoding === 'gzip') {
1109
responseContent = zlib.createGunzip()
1110
response.pipe(responseContent)
1111
} else {
1112
// Since previous versions didn't check for Content-Encoding header,
1113
// ignore any invalid values to preserve backwards-compatibility
1114
if (contentEncoding !== 'identity') {
1115
debug('ignoring unrecognized Content-Encoding ' + contentEncoding)
1116
}
1117
responseContent = response
1118
}
1119
} else {
1120
responseContent = response
1121
}
1122
1123
if (self.encoding) {
1124
if (self.dests.length !== 0) {
1125
console.error('Ignoring encoding parameter as this stream is being piped to another stream which makes the encoding option invalid.')
1126
} else if (responseContent.setEncoding) {
1127
responseContent.setEncoding(self.encoding)
1128
} else {
1129
// Should only occur on node pre-v0.9.4 (joyent/node@9b5abe5) with
1130
// zlib streams.
1131
// If/When support for 0.9.4 is dropped, this should be unnecessary.
1132
responseContent = responseContent.pipe(stringstream(self.encoding))
1133
}
1134
}
1135
1136
if (self._paused) {
1137
responseContent.pause()
1138
}
1139
1140
self.responseContent = responseContent
1141
1142
self.emit('response', response)
1143
1144
self.dests.forEach(function (dest) {
1145
self.pipeDest(dest)
1146
})
1147
1148
responseContent.on('data', function (chunk) {
1149
self._destdata = true
1150
self.emit('data', chunk)
1151
})
1152
responseContent.on('end', function (chunk) {
1153
self.emit('end', chunk)
1154
})
1155
responseContent.on('error', function (error) {
1156
self.emit('error', error)
1157
})
1158
responseContent.on('close', function () {self.emit('close')})
1159
1160
if (self.callback) {
1161
var buffer = bl()
1162
, strings = []
1163
1164
self.on('data', function (chunk) {
1165
if (Buffer.isBuffer(chunk)) {
1166
buffer.append(chunk)
1167
} else {
1168
strings.push(chunk)
1169
}
1170
})
1171
self.on('end', function () {
1172
debug('end event', self.uri.href)
1173
if (self._aborted) {
1174
debug('aborted', self.uri.href)
1175
return
1176
}
1177
1178
if (buffer.length) {
1179
debug('has body', self.uri.href, buffer.length)
1180
if (self.encoding === null) {
1181
// response.body = buffer
1182
// can't move to this until https://github.com/rvagg/bl/issues/13
1183
response.body = buffer.slice()
1184
} else {
1185
response.body = buffer.toString(self.encoding)
1186
}
1187
} else if (strings.length) {
1188
// The UTF8 BOM [0xEF,0xBB,0xBF] is converted to [0xFE,0xFF] in the JS UTC16/UCS2 representation.
1189
// Strip this value out when the encoding is set to 'utf8', as upstream consumers won't expect it and it breaks JSON.parse().
1190
if (self.encoding === 'utf8' && strings[0].length > 0 && strings[0][0] === '\uFEFF') {
1191
strings[0] = strings[0].substring(1)
1192
}
1193
response.body = strings.join('')
1194
}
1195
1196
if (self._json) {
1197
try {
1198
response.body = JSON.parse(response.body, self._jsonReviver)
1199
} catch (e) {
1200
// empty
1201
}
1202
}
1203
debug('emitting complete', self.uri.href)
1204
if (typeof response.body === 'undefined' && !self._json) {
1205
response.body = self.encoding === null ? new Buffer(0) : ''
1206
}
1207
self.emit('complete', response, response.body)
1208
})
1209
}
1210
//if no callback
1211
else {
1212
self.on('end', function () {
1213
if (self._aborted) {
1214
debug('aborted', self.uri.href)
1215
return
1216
}
1217
self.emit('complete', response)
1218
})
1219
}
1220
}
1221
debug('finish init function', self.uri.href)
1222
}
1223
1224
Request.prototype.abort = function () {
1225
var self = this
1226
self._aborted = true
1227
1228
if (self.req) {
1229
self.req.abort()
1230
}
1231
else if (self.response) {
1232
self.response.abort()
1233
}
1234
1235
self.emit('abort')
1236
}
1237
1238
Request.prototype.pipeDest = function (dest) {
1239
var self = this
1240
var response = self.response
1241
// Called after the response is received
1242
if (dest.headers && !dest.headersSent) {
1243
if (response.caseless.has('content-type')) {
1244
var ctname = response.caseless.has('content-type')
1245
if (dest.setHeader) {
1246
dest.setHeader(ctname, response.headers[ctname])
1247
}
1248
else {
1249
dest.headers[ctname] = response.headers[ctname]
1250
}
1251
}
1252
1253
if (response.caseless.has('content-length')) {
1254
var clname = response.caseless.has('content-length')
1255
if (dest.setHeader) {
1256
dest.setHeader(clname, response.headers[clname])
1257
} else {
1258
dest.headers[clname] = response.headers[clname]
1259
}
1260
}
1261
}
1262
if (dest.setHeader && !dest.headersSent) {
1263
for (var i in response.headers) {
1264
// If the response content is being decoded, the Content-Encoding header
1265
// of the response doesn't represent the piped content, so don't pass it.
1266
if (!self.gzip || i !== 'content-encoding') {
1267
dest.setHeader(i, response.headers[i])
1268
}
1269
}
1270
dest.statusCode = response.statusCode
1271
}
1272
if (self.pipefilter) {
1273
self.pipefilter(response, dest)
1274
}
1275
}
1276
1277
Request.prototype.qs = function (q, clobber) {
1278
var self = this
1279
var base
1280
if (!clobber && self.uri.query) {
1281
base = self._qs.parse(self.uri.query)
1282
} else {
1283
base = {}
1284
}
1285
1286
for (var i in q) {
1287
base[i] = q[i]
1288
}
1289
1290
if (self._qs.stringify(base) === '') {
1291
return self
1292
}
1293
1294
var qs = self._qs.stringify(base)
1295
1296
self.uri = url.parse(self.uri.href.split('?')[0] + '?' + qs)
1297
self.url = self.uri
1298
self.path = self.uri.path
1299
1300
return self
1301
}
1302
Request.prototype.form = function (form) {
1303
var self = this
1304
if (form) {
1305
self.setHeader('content-type', 'application/x-www-form-urlencoded')
1306
self.body = (typeof form === 'string')
1307
? self._qs.rfc3986(form.toString('utf8'))
1308
: self._qs.stringify(form).toString('utf8')
1309
return self
1310
}
1311
// create form-data object
1312
self._form = new FormData()
1313
self._form.on('error', function(err) {
1314
err.message = 'form-data: ' + err.message
1315
self.emit('error', err)
1316
self.abort()
1317
})
1318
return self._form
1319
}
1320
Request.prototype.multipart = function (multipart) {
1321
var self = this
1322
1323
self._multipart.onRequest(multipart)
1324
1325
if (!self._multipart.chunked) {
1326
self.body = self._multipart.body
1327
}
1328
1329
return self
1330
}
1331
Request.prototype.json = function (val) {
1332
var self = this
1333
1334
if (!self.hasHeader('accept')) {
1335
self.setHeader('accept', 'application/json')
1336
}
1337
1338
self._json = true
1339
if (typeof val === 'boolean') {
1340
if (self.body !== undefined) {
1341
if (!/^application\/x-www-form-urlencoded\b/.test(self.getHeader('content-type'))) {
1342
self.body = safeStringify(self.body)
1343
} else {
1344
self.body = self._qs.rfc3986(self.body)
1345
}
1346
if (!self.hasHeader('content-type')) {
1347
self.setHeader('content-type', 'application/json')
1348
}
1349
}
1350
} else {
1351
self.body = safeStringify(val)
1352
if (!self.hasHeader('content-type')) {
1353
self.setHeader('content-type', 'application/json')
1354
}
1355
}
1356
1357
if (typeof self.jsonReviver === 'function') {
1358
self._jsonReviver = self.jsonReviver
1359
}
1360
1361
return self
1362
}
1363
Request.prototype.getHeader = function (name, headers) {
1364
var self = this
1365
var result, re, match
1366
if (!headers) {
1367
headers = self.headers
1368
}
1369
Object.keys(headers).forEach(function (key) {
1370
if (key.length !== name.length) {
1371
return
1372
}
1373
re = new RegExp(name, 'i')
1374
match = key.match(re)
1375
if (match) {
1376
result = headers[key]
1377
}
1378
})
1379
return result
1380
}
1381
1382
Request.prototype.auth = function (user, pass, sendImmediately, bearer) {
1383
var self = this
1384
1385
self._auth.onRequest(user, pass, sendImmediately, bearer)
1386
1387
return self
1388
}
1389
Request.prototype.aws = function (opts, now) {
1390
var self = this
1391
1392
if (!now) {
1393
self._aws = opts
1394
return self
1395
}
1396
var date = new Date()
1397
self.setHeader('date', date.toUTCString())
1398
var auth =
1399
{ key: opts.key
1400
, secret: opts.secret
1401
, verb: self.method.toUpperCase()
1402
, date: date
1403
, contentType: self.getHeader('content-type') || ''
1404
, md5: self.getHeader('content-md5') || ''
1405
, amazonHeaders: aws.canonicalizeHeaders(self.headers)
1406
}
1407
var path = self.uri.path
1408
if (opts.bucket && path) {
1409
auth.resource = '/' + opts.bucket + path
1410
} else if (opts.bucket && !path) {
1411
auth.resource = '/' + opts.bucket
1412
} else if (!opts.bucket && path) {
1413
auth.resource = path
1414
} else if (!opts.bucket && !path) {
1415
auth.resource = '/'
1416
}
1417
auth.resource = aws.canonicalizeResource(auth.resource)
1418
self.setHeader('authorization', aws.authorization(auth))
1419
1420
return self
1421
}
1422
Request.prototype.httpSignature = function (opts) {
1423
var self = this
1424
httpSignature.signRequest({
1425
getHeader: function(header) {
1426
return self.getHeader(header, self.headers)
1427
},
1428
setHeader: function(header, value) {
1429
self.setHeader(header, value)
1430
},
1431
method: self.method,
1432
path: self.path
1433
}, opts)
1434
debug('httpSignature authorization', self.getHeader('authorization'))
1435
1436
return self
1437
}
1438
Request.prototype.hawk = function (opts) {
1439
var self = this
1440
self.setHeader('Authorization', hawk.client.header(self.uri, self.method, opts).field)
1441
}
1442
Request.prototype.oauth = function (_oauth) {
1443
var self = this
1444
1445
self._oauth.onRequest(_oauth)
1446
1447
return self
1448
}
1449
1450
Request.prototype.jar = function (jar) {
1451
var self = this
1452
var cookies
1453
1454
if (self._redirect.redirectsFollowed === 0) {
1455
self.originalCookieHeader = self.getHeader('cookie')
1456
}
1457
1458
if (!jar) {
1459
// disable cookies
1460
cookies = false
1461
self._disableCookies = true
1462
} else {
1463
var targetCookieJar = (jar && jar.getCookieString) ? jar : globalCookieJar
1464
var urihref = self.uri.href
1465
//fetch cookie in the Specified host
1466
if (targetCookieJar) {
1467
cookies = targetCookieJar.getCookieString(urihref)
1468
}
1469
}
1470
1471
//if need cookie and cookie is not empty
1472
if (cookies && cookies.length) {
1473
if (self.originalCookieHeader) {
1474
// Don't overwrite existing Cookie header
1475
self.setHeader('cookie', self.originalCookieHeader + '; ' + cookies)
1476
} else {
1477
self.setHeader('cookie', cookies)
1478
}
1479
}
1480
self._jar = jar
1481
return self
1482
}
1483
1484
1485
// Stream API
1486
Request.prototype.pipe = function (dest, opts) {
1487
var self = this
1488
1489
if (self.response) {
1490
if (self._destdata) {
1491
self.emit('error', new Error('You cannot pipe after data has been emitted from the response.'))
1492
} else if (self._ended) {
1493
self.emit('error', new Error('You cannot pipe after the response has been ended.'))
1494
} else {
1495
stream.Stream.prototype.pipe.call(self, dest, opts)
1496
self.pipeDest(dest)
1497
return dest
1498
}
1499
} else {
1500
self.dests.push(dest)
1501
stream.Stream.prototype.pipe.call(self, dest, opts)
1502
return dest
1503
}
1504
}
1505
Request.prototype.write = function () {
1506
var self = this
1507
if (!self._started) {
1508
self.start()
1509
}
1510
return self.req.write.apply(self.req, arguments)
1511
}
1512
Request.prototype.end = function (chunk) {
1513
var self = this
1514
if (chunk) {
1515
self.write(chunk)
1516
}
1517
if (!self._started) {
1518
self.start()
1519
}
1520
self.req.end()
1521
}
1522
Request.prototype.pause = function () {
1523
var self = this
1524
if (!self.responseContent) {
1525
self._paused = true
1526
} else {
1527
self.responseContent.pause.apply(self.responseContent, arguments)
1528
}
1529
}
1530
Request.prototype.resume = function () {
1531
var self = this
1532
if (!self.responseContent) {
1533
self._paused = false
1534
} else {
1535
self.responseContent.resume.apply(self.responseContent, arguments)
1536
}
1537
}
1538
Request.prototype.destroy = function () {
1539
var self = this
1540
if (!self._ended) {
1541
self.end()
1542
} else if (self.response) {
1543
self.response.destroy()
1544
}
1545
}
1546
1547
Request.defaultProxyHeaderWhiteList =
1548
defaultProxyHeaderWhiteList.slice()
1549
1550
Request.defaultProxyHeaderExclusiveList =
1551
defaultProxyHeaderExclusiveList.slice()
1552
1553
// Exports
1554
1555
Request.prototype.toJSON = requestToJSON
1556
module.exports = Request
1557
1558