UNPKG

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