request.js (44466B)
1 'use strict' 2 3 var http = require('http') 4 var https = require('https') 5 var url = require('url') 6 var util = require('util') 7 var stream = require('stream') 8 var zlib = require('zlib') 9 var aws2 = require('aws-sign2') 10 var aws4 = require('aws4') 11 var httpSignature = require('http-signature') 12 var mime = require('mime-types') 13 var caseless = require('caseless') 14 var ForeverAgent = require('forever-agent') 15 var FormData = require('form-data') 16 var extend = require('extend') 17 var isstream = require('isstream') 18 var isTypedArray = require('is-typedarray').strict 19 var helpers = require('./lib/helpers') 20 var cookies = require('./lib/cookies') 21 var getProxyFromURI = require('./lib/getProxyFromURI') 22 var Querystring = require('./lib/querystring').Querystring 23 var Har = require('./lib/har').Har 24 var Auth = require('./lib/auth').Auth 25 var OAuth = require('./lib/oauth').OAuth 26 var hawk = require('./lib/hawk') 27 var Multipart = require('./lib/multipart').Multipart 28 var Redirect = require('./lib/redirect').Redirect 29 var Tunnel = require('./lib/tunnel').Tunnel 30 var now = require('performance-now') 31 var Buffer = require('safe-buffer').Buffer 32 33 var safeStringify = helpers.safeStringify 34 var isReadStream = helpers.isReadStream 35 var toBase64 = helpers.toBase64 36 var defer = helpers.defer 37 var copy = helpers.copy 38 var version = helpers.version 39 var globalCookieJar = cookies.jar() 40 41 var globalPool = {} 42 43 function 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 57 function 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 73 function 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 83 function 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 93 function 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 130 util.inherits(Request, stream.Stream) 131 132 // Debugging 133 Request.debug = process.env.NODE_DEBUG && /\brequest\b/.test(process.env.NODE_DEBUG) 134 function debug () { 135 if (Request.debug) { 136 console.error('REQUEST %s', util.format.apply(util, arguments)) 137 } 138 } 139 Request.prototype.debug = debug 140 141 Request.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 self.setHeader(hostHeaderName, self.uri.host) 291 // Drop :port suffix from Host header if known protocol. 292 if (self.uri.port) { 293 if ((self.uri.port === '80' && self.uri.protocol === 'http:') || 294 (self.uri.port === '443' && self.uri.protocol === 'https:')) { 295 self.setHeader(hostHeaderName, self.uri.hostname) 296 } 297 } 298 self.setHost = true 299 } 300 301 self.jar(self._jar || options.jar) 302 303 if (!self.uri.port) { 304 if (self.uri.protocol === 'http:') { self.uri.port = 80 } else if (self.uri.protocol === 'https:') { self.uri.port = 443 } 305 } 306 307 if (self.proxy && !self.tunnel) { 308 self.port = self.proxy.port 309 self.host = self.proxy.hostname 310 } else { 311 self.port = self.uri.port 312 self.host = self.uri.hostname 313 } 314 315 if (options.form) { 316 self.form(options.form) 317 } 318 319 if (options.formData) { 320 var formData = options.formData 321 var requestForm = self.form() 322 var appendFormValue = function (key, value) { 323 if (value && value.hasOwnProperty('value') && value.hasOwnProperty('options')) { 324 requestForm.append(key, value.value, value.options) 325 } else { 326 requestForm.append(key, value) 327 } 328 } 329 for (var formKey in formData) { 330 if (formData.hasOwnProperty(formKey)) { 331 var formValue = formData[formKey] 332 if (formValue instanceof Array) { 333 for (var j = 0; j < formValue.length; j++) { 334 appendFormValue(formKey, formValue[j]) 335 } 336 } else { 337 appendFormValue(formKey, formValue) 338 } 339 } 340 } 341 } 342 343 if (options.qs) { 344 self.qs(options.qs) 345 } 346 347 if (self.uri.path) { 348 self.path = self.uri.path 349 } else { 350 self.path = self.uri.pathname + (self.uri.search || '') 351 } 352 353 if (self.path.length === 0) { 354 self.path = '/' 355 } 356 357 // Auth must happen last in case signing is dependent on other headers 358 if (options.aws) { 359 self.aws(options.aws) 360 } 361 362 if (options.hawk) { 363 self.hawk(options.hawk) 364 } 365 366 if (options.httpSignature) { 367 self.httpSignature(options.httpSignature) 368 } 369 370 if (options.auth) { 371 if (Object.prototype.hasOwnProperty.call(options.auth, 'username')) { 372 options.auth.user = options.auth.username 373 } 374 if (Object.prototype.hasOwnProperty.call(options.auth, 'password')) { 375 options.auth.pass = options.auth.password 376 } 377 378 self.auth( 379 options.auth.user, 380 options.auth.pass, 381 options.auth.sendImmediately, 382 options.auth.bearer 383 ) 384 } 385 386 if (self.gzip && !self.hasHeader('accept-encoding')) { 387 self.setHeader('accept-encoding', 'gzip, deflate') 388 } 389 390 if (self.uri.auth && !self.hasHeader('authorization')) { 391 var uriAuthPieces = self.uri.auth.split(':').map(function (item) { return self._qs.unescape(item) }) 392 self.auth(uriAuthPieces[0], uriAuthPieces.slice(1).join(':'), true) 393 } 394 395 if (!self.tunnel && self.proxy && self.proxy.auth && !self.hasHeader('proxy-authorization')) { 396 var proxyAuthPieces = self.proxy.auth.split(':').map(function (item) { return self._qs.unescape(item) }) 397 var authHeader = 'Basic ' + toBase64(proxyAuthPieces.join(':')) 398 self.setHeader('proxy-authorization', authHeader) 399 } 400 401 if (self.proxy && !self.tunnel) { 402 self.path = (self.uri.protocol + '//' + self.uri.host + self.path) 403 } 404 405 if (options.json) { 406 self.json(options.json) 407 } 408 if (options.multipart) { 409 self.multipart(options.multipart) 410 } 411 412 if (options.time) { 413 self.timing = true 414 415 // NOTE: elapsedTime is deprecated in favor of .timings 416 self.elapsedTime = self.elapsedTime || 0 417 } 418 419 function setContentLength () { 420 if (isTypedArray(self.body)) { 421 self.body = Buffer.from(self.body) 422 } 423 424 if (!self.hasHeader('content-length')) { 425 var length 426 if (typeof self.body === 'string') { 427 length = Buffer.byteLength(self.body) 428 } else if (Array.isArray(self.body)) { 429 length = self.body.reduce(function (a, b) { return a + b.length }, 0) 430 } else { 431 length = self.body.length 432 } 433 434 if (length) { 435 self.setHeader('content-length', length) 436 } else { 437 self.emit('error', new Error('Argument error, options.body.')) 438 } 439 } 440 } 441 if (self.body && !isstream(self.body)) { 442 setContentLength() 443 } 444 445 if (options.oauth) { 446 self.oauth(options.oauth) 447 } else if (self._oauth.params && self.hasHeader('authorization')) { 448 self.oauth(self._oauth.params) 449 } 450 451 var protocol = self.proxy && !self.tunnel ? self.proxy.protocol : self.uri.protocol 452 var defaultModules = {'http:': http, 'https:': https} 453 var httpModules = self.httpModules || {} 454 455 self.httpModule = httpModules[protocol] || defaultModules[protocol] 456 457 if (!self.httpModule) { 458 return self.emit('error', new Error('Invalid protocol: ' + protocol)) 459 } 460 461 if (options.ca) { 462 self.ca = options.ca 463 } 464 465 if (!self.agent) { 466 if (options.agentOptions) { 467 self.agentOptions = options.agentOptions 468 } 469 470 if (options.agentClass) { 471 self.agentClass = options.agentClass 472 } else if (options.forever) { 473 var v = version() 474 // use ForeverAgent in node 0.10- only 475 if (v.major === 0 && v.minor <= 10) { 476 self.agentClass = protocol === 'http:' ? ForeverAgent : ForeverAgent.SSL 477 } else { 478 self.agentClass = self.httpModule.Agent 479 self.agentOptions = self.agentOptions || {} 480 self.agentOptions.keepAlive = true 481 } 482 } else { 483 self.agentClass = self.httpModule.Agent 484 } 485 } 486 487 if (self.pool === false) { 488 self.agent = false 489 } else { 490 self.agent = self.agent || self.getNewAgent() 491 } 492 493 self.on('pipe', function (src) { 494 if (self.ntick && self._started) { 495 self.emit('error', new Error('You cannot pipe to this stream after the outbound request has started.')) 496 } 497 self.src = src 498 if (isReadStream(src)) { 499 if (!self.hasHeader('content-type')) { 500 self.setHeader('content-type', mime.lookup(src.path)) 501 } 502 } else { 503 if (src.headers) { 504 for (var i in src.headers) { 505 if (!self.hasHeader(i)) { 506 self.setHeader(i, src.headers[i]) 507 } 508 } 509 } 510 if (self._json && !self.hasHeader('content-type')) { 511 self.setHeader('content-type', 'application/json') 512 } 513 if (src.method && !self.explicitMethod) { 514 self.method = src.method 515 } 516 } 517 518 // self.on('pipe', function () { 519 // console.error('You have already piped to this stream. Pipeing twice is likely to break the request.') 520 // }) 521 }) 522 523 defer(function () { 524 if (self._aborted) { 525 return 526 } 527 528 var end = function () { 529 if (self._form) { 530 if (!self._auth.hasAuth) { 531 self._form.pipe(self) 532 } else if (self._auth.hasAuth && self._auth.sentAuth) { 533 self._form.pipe(self) 534 } 535 } 536 if (self._multipart && self._multipart.chunked) { 537 self._multipart.body.pipe(self) 538 } 539 if (self.body) { 540 if (isstream(self.body)) { 541 self.body.pipe(self) 542 } else { 543 setContentLength() 544 if (Array.isArray(self.body)) { 545 self.body.forEach(function (part) { 546 self.write(part) 547 }) 548 } else { 549 self.write(self.body) 550 } 551 self.end() 552 } 553 } else if (self.requestBodyStream) { 554 console.warn('options.requestBodyStream is deprecated, please pass the request object to stream.pipe.') 555 self.requestBodyStream.pipe(self) 556 } else if (!self.src) { 557 if (self._auth.hasAuth && !self._auth.sentAuth) { 558 self.end() 559 return 560 } 561 if (self.method !== 'GET' && typeof self.method !== 'undefined') { 562 self.setHeader('content-length', 0) 563 } 564 self.end() 565 } 566 } 567 568 if (self._form && !self.hasHeader('content-length')) { 569 // Before ending the request, we had to compute the length of the whole form, asyncly 570 self.setHeader(self._form.getHeaders(), true) 571 self._form.getLength(function (err, length) { 572 if (!err && !isNaN(length)) { 573 self.setHeader('content-length', length) 574 } 575 end() 576 }) 577 } else { 578 end() 579 } 580 581 self.ntick = true 582 }) 583 } 584 585 Request.prototype.getNewAgent = function () { 586 var self = this 587 var Agent = self.agentClass 588 var options = {} 589 if (self.agentOptions) { 590 for (var i in self.agentOptions) { 591 options[i] = self.agentOptions[i] 592 } 593 } 594 if (self.ca) { 595 options.ca = self.ca 596 } 597 if (self.ciphers) { 598 options.ciphers = self.ciphers 599 } 600 if (self.secureProtocol) { 601 options.secureProtocol = self.secureProtocol 602 } 603 if (self.secureOptions) { 604 options.secureOptions = self.secureOptions 605 } 606 if (typeof self.rejectUnauthorized !== 'undefined') { 607 options.rejectUnauthorized = self.rejectUnauthorized 608 } 609 610 if (self.cert && self.key) { 611 options.key = self.key 612 options.cert = self.cert 613 } 614 615 if (self.pfx) { 616 options.pfx = self.pfx 617 } 618 619 if (self.passphrase) { 620 options.passphrase = self.passphrase 621 } 622 623 var poolKey = '' 624 625 // different types of agents are in different pools 626 if (Agent !== self.httpModule.Agent) { 627 poolKey += Agent.name 628 } 629 630 // ca option is only relevant if proxy or destination are https 631 var proxy = self.proxy 632 if (typeof proxy === 'string') { 633 proxy = url.parse(proxy) 634 } 635 var isHttps = (proxy && proxy.protocol === 'https:') || this.uri.protocol === 'https:' 636 637 if (isHttps) { 638 if (options.ca) { 639 if (poolKey) { 640 poolKey += ':' 641 } 642 poolKey += options.ca 643 } 644 645 if (typeof options.rejectUnauthorized !== 'undefined') { 646 if (poolKey) { 647 poolKey += ':' 648 } 649 poolKey += options.rejectUnauthorized 650 } 651 652 if (options.cert) { 653 if (poolKey) { 654 poolKey += ':' 655 } 656 poolKey += options.cert.toString('ascii') + options.key.toString('ascii') 657 } 658 659 if (options.pfx) { 660 if (poolKey) { 661 poolKey += ':' 662 } 663 poolKey += options.pfx.toString('ascii') 664 } 665 666 if (options.ciphers) { 667 if (poolKey) { 668 poolKey += ':' 669 } 670 poolKey += options.ciphers 671 } 672 673 if (options.secureProtocol) { 674 if (poolKey) { 675 poolKey += ':' 676 } 677 poolKey += options.secureProtocol 678 } 679 680 if (options.secureOptions) { 681 if (poolKey) { 682 poolKey += ':' 683 } 684 poolKey += options.secureOptions 685 } 686 } 687 688 if (self.pool === globalPool && !poolKey && Object.keys(options).length === 0 && self.httpModule.globalAgent) { 689 // not doing anything special. Use the globalAgent 690 return self.httpModule.globalAgent 691 } 692 693 // we're using a stored agent. Make sure it's protocol-specific 694 poolKey = self.uri.protocol + poolKey 695 696 // generate a new agent for this setting if none yet exists 697 if (!self.pool[poolKey]) { 698 self.pool[poolKey] = new Agent(options) 699 // properly set maxSockets on new agents 700 if (self.pool.maxSockets) { 701 self.pool[poolKey].maxSockets = self.pool.maxSockets 702 } 703 } 704 705 return self.pool[poolKey] 706 } 707 708 Request.prototype.start = function () { 709 // start() is called once we are ready to send the outgoing HTTP request. 710 // this is usually called on the first write(), end() or on nextTick() 711 var self = this 712 713 if (self.timing) { 714 // All timings will be relative to this request's startTime. In order to do this, 715 // we need to capture the wall-clock start time (via Date), immediately followed 716 // by the high-resolution timer (via now()). While these two won't be set 717 // at the _exact_ same time, they should be close enough to be able to calculate 718 // high-resolution, monotonically non-decreasing timestamps relative to startTime. 719 var startTime = new Date().getTime() 720 var startTimeNow = now() 721 } 722 723 if (self._aborted) { 724 return 725 } 726 727 self._started = true 728 self.method = self.method || 'GET' 729 self.href = self.uri.href 730 731 if (self.src && self.src.stat && self.src.stat.size && !self.hasHeader('content-length')) { 732 self.setHeader('content-length', self.src.stat.size) 733 } 734 if (self._aws) { 735 self.aws(self._aws, true) 736 } 737 738 // We have a method named auth, which is completely different from the http.request 739 // auth option. If we don't remove it, we're gonna have a bad time. 740 var reqOptions = copy(self) 741 delete reqOptions.auth 742 743 debug('make request', self.uri.href) 744 745 // node v6.8.0 now supports a `timeout` value in `http.request()`, but we 746 // should delete it for now since we handle timeouts manually for better 747 // consistency with node versions before v6.8.0 748 delete reqOptions.timeout 749 750 try { 751 self.req = self.httpModule.request(reqOptions) 752 } catch (err) { 753 self.emit('error', err) 754 return 755 } 756 757 if (self.timing) { 758 self.startTime = startTime 759 self.startTimeNow = startTimeNow 760 761 // Timing values will all be relative to startTime (by comparing to startTimeNow 762 // so we have an accurate clock) 763 self.timings = {} 764 } 765 766 var timeout 767 if (self.timeout && !self.timeoutTimer) { 768 if (self.timeout < 0) { 769 timeout = 0 770 } else if (typeof self.timeout === 'number' && isFinite(self.timeout)) { 771 timeout = self.timeout 772 } 773 } 774 775 self.req.on('response', self.onRequestResponse.bind(self)) 776 self.req.on('error', self.onRequestError.bind(self)) 777 self.req.on('drain', function () { 778 self.emit('drain') 779 }) 780 781 self.req.on('socket', function (socket) { 782 // `._connecting` was the old property which was made public in node v6.1.0 783 var isConnecting = socket._connecting || socket.connecting 784 if (self.timing) { 785 self.timings.socket = now() - self.startTimeNow 786 787 if (isConnecting) { 788 var onLookupTiming = function () { 789 self.timings.lookup = now() - self.startTimeNow 790 } 791 792 var onConnectTiming = function () { 793 self.timings.connect = now() - self.startTimeNow 794 } 795 796 socket.once('lookup', onLookupTiming) 797 socket.once('connect', onConnectTiming) 798 799 // clean up timing event listeners if needed on error 800 self.req.once('error', function () { 801 socket.removeListener('lookup', onLookupTiming) 802 socket.removeListener('connect', onConnectTiming) 803 }) 804 } 805 } 806 807 var setReqTimeout = function () { 808 // This timeout sets the amount of time to wait *between* bytes sent 809 // from the server once connected. 810 // 811 // In particular, it's useful for erroring if the server fails to send 812 // data halfway through streaming a response. 813 self.req.setTimeout(timeout, function () { 814 if (self.req) { 815 self.abort() 816 var e = new Error('ESOCKETTIMEDOUT') 817 e.code = 'ESOCKETTIMEDOUT' 818 e.connect = false 819 self.emit('error', e) 820 } 821 }) 822 } 823 if (timeout !== undefined) { 824 // Only start the connection timer if we're actually connecting a new 825 // socket, otherwise if we're already connected (because this is a 826 // keep-alive connection) do not bother. This is important since we won't 827 // get a 'connect' event for an already connected socket. 828 if (isConnecting) { 829 var onReqSockConnect = function () { 830 socket.removeListener('connect', onReqSockConnect) 831 self.clearTimeout() 832 setReqTimeout() 833 } 834 835 socket.on('connect', onReqSockConnect) 836 837 self.req.on('error', function (err) { // eslint-disable-line handle-callback-err 838 socket.removeListener('connect', onReqSockConnect) 839 }) 840 841 // Set a timeout in memory - this block will throw if the server takes more 842 // than `timeout` to write the HTTP status and headers (corresponding to 843 // the on('response') event on the client). NB: this measures wall-clock 844 // time, not the time between bytes sent by the server. 845 self.timeoutTimer = setTimeout(function () { 846 socket.removeListener('connect', onReqSockConnect) 847 self.abort() 848 var e = new Error('ETIMEDOUT') 849 e.code = 'ETIMEDOUT' 850 e.connect = true 851 self.emit('error', e) 852 }, timeout) 853 } else { 854 // We're already connected 855 setReqTimeout() 856 } 857 } 858 self.emit('socket', socket) 859 }) 860 861 self.emit('request', self.req) 862 } 863 864 Request.prototype.onRequestError = function (error) { 865 var self = this 866 if (self._aborted) { 867 return 868 } 869 if (self.req && self.req._reusedSocket && error.code === 'ECONNRESET' && 870 self.agent.addRequestNoreuse) { 871 self.agent = { addRequest: self.agent.addRequestNoreuse.bind(self.agent) } 872 self.start() 873 self.req.end() 874 return 875 } 876 self.clearTimeout() 877 self.emit('error', error) 878 } 879 880 Request.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 self.clearTimeout() 964 965 var targetCookieJar = (self._jar && self._jar.setCookie) ? self._jar : globalCookieJar 966 var addCookie = function (cookie) { 967 // set the cookie if it's domain in the href's domain. 968 try { 969 targetCookieJar.setCookie(cookie, self.uri.href, {ignoreError: true}) 970 } catch (e) { 971 self.emit('error', e) 972 } 973 } 974 975 response.caseless = caseless(response.headers) 976 977 if (response.caseless.has('set-cookie') && (!self._disableCookies)) { 978 var headerName = response.caseless.has('set-cookie') 979 if (Array.isArray(response.headers[headerName])) { 980 response.headers[headerName].forEach(addCookie) 981 } else { 982 addCookie(response.headers[headerName]) 983 } 984 } 985 986 if (self._redirect.onResponse(response)) { 987 return // Ignore the rest of the response 988 } else { 989 // Be a good stream and emit end when the response is finished. 990 // Hack to emit end on close because of a core bug that never fires end 991 response.on('close', function () { 992 if (!self._ended) { 993 self.response.emit('end') 994 } 995 }) 996 997 response.once('end', function () { 998 self._ended = true 999 }) 1000 1001 var noBody = function (code) { 1002 return ( 1003 self.method === 'HEAD' || 1004 // Informational 1005 (code >= 100 && code < 200) || 1006 // No Content 1007 code === 204 || 1008 // Not Modified 1009 code === 304 1010 ) 1011 } 1012 1013 var responseContent 1014 if (self.gzip && !noBody(response.statusCode)) { 1015 var contentEncoding = response.headers['content-encoding'] || 'identity' 1016 contentEncoding = contentEncoding.trim().toLowerCase() 1017 1018 // Be more lenient with decoding compressed responses, since (very rarely) 1019 // servers send slightly invalid gzip responses that are still accepted 1020 // by common browsers. 1021 // Always using Z_SYNC_FLUSH is what cURL does. 1022 var zlibOptions = { 1023 flush: zlib.Z_SYNC_FLUSH, 1024 finishFlush: zlib.Z_SYNC_FLUSH 1025 } 1026 1027 if (contentEncoding === 'gzip') { 1028 responseContent = zlib.createGunzip(zlibOptions) 1029 response.pipe(responseContent) 1030 } else if (contentEncoding === 'deflate') { 1031 responseContent = zlib.createInflate(zlibOptions) 1032 response.pipe(responseContent) 1033 } else { 1034 // Since previous versions didn't check for Content-Encoding header, 1035 // ignore any invalid values to preserve backwards-compatibility 1036 if (contentEncoding !== 'identity') { 1037 debug('ignoring unrecognized Content-Encoding ' + contentEncoding) 1038 } 1039 responseContent = response 1040 } 1041 } else { 1042 responseContent = response 1043 } 1044 1045 if (self.encoding) { 1046 if (self.dests.length !== 0) { 1047 console.error('Ignoring encoding parameter as this stream is being piped to another stream which makes the encoding option invalid.') 1048 } else { 1049 responseContent.setEncoding(self.encoding) 1050 } 1051 } 1052 1053 if (self._paused) { 1054 responseContent.pause() 1055 } 1056 1057 self.responseContent = responseContent 1058 1059 self.emit('response', response) 1060 1061 self.dests.forEach(function (dest) { 1062 self.pipeDest(dest) 1063 }) 1064 1065 responseContent.on('data', function (chunk) { 1066 if (self.timing && !self.responseStarted) { 1067 self.responseStartTime = (new Date()).getTime() 1068 1069 // NOTE: responseStartTime is deprecated in favor of .timings 1070 response.responseStartTime = self.responseStartTime 1071 } 1072 self._destdata = true 1073 self.emit('data', chunk) 1074 }) 1075 responseContent.once('end', function (chunk) { 1076 self.emit('end', chunk) 1077 }) 1078 responseContent.on('error', function (error) { 1079 self.emit('error', error) 1080 }) 1081 responseContent.on('close', function () { self.emit('close') }) 1082 1083 if (self.callback) { 1084 self.readResponseBody(response) 1085 } else { // if no callback 1086 self.on('end', function () { 1087 if (self._aborted) { 1088 debug('aborted', self.uri.href) 1089 return 1090 } 1091 self.emit('complete', response) 1092 }) 1093 } 1094 } 1095 debug('finish init function', self.uri.href) 1096 } 1097 1098 Request.prototype.readResponseBody = function (response) { 1099 var self = this 1100 debug("reading response's body") 1101 var buffers = [] 1102 var bufferLength = 0 1103 var strings = [] 1104 1105 self.on('data', function (chunk) { 1106 if (!Buffer.isBuffer(chunk)) { 1107 strings.push(chunk) 1108 } else if (chunk.length) { 1109 bufferLength += chunk.length 1110 buffers.push(chunk) 1111 } 1112 }) 1113 self.on('end', function () { 1114 debug('end event', self.uri.href) 1115 if (self._aborted) { 1116 debug('aborted', self.uri.href) 1117 // `buffer` is defined in the parent scope and used in a closure it exists for the life of the request. 1118 // This can lead to leaky behavior if the user retains a reference to the request object. 1119 buffers = [] 1120 bufferLength = 0 1121 return 1122 } 1123 1124 if (bufferLength) { 1125 debug('has body', self.uri.href, bufferLength) 1126 response.body = Buffer.concat(buffers, bufferLength) 1127 if (self.encoding !== null) { 1128 response.body = response.body.toString(self.encoding) 1129 } 1130 // `buffer` is defined in the parent scope and used in a closure it exists for the life of the Request. 1131 // This can lead to leaky behavior if the user retains a reference to the request object. 1132 buffers = [] 1133 bufferLength = 0 1134 } else if (strings.length) { 1135 // The UTF8 BOM [0xEF,0xBB,0xBF] is converted to [0xFE,0xFF] in the JS UTC16/UCS2 representation. 1136 // Strip this value out when the encoding is set to 'utf8', as upstream consumers won't expect it and it breaks JSON.parse(). 1137 if (self.encoding === 'utf8' && strings[0].length > 0 && strings[0][0] === '\uFEFF') { 1138 strings[0] = strings[0].substring(1) 1139 } 1140 response.body = strings.join('') 1141 } 1142 1143 if (self._json) { 1144 try { 1145 response.body = JSON.parse(response.body, self._jsonReviver) 1146 } catch (e) { 1147 debug('invalid JSON received', self.uri.href) 1148 } 1149 } 1150 debug('emitting complete', self.uri.href) 1151 if (typeof response.body === 'undefined' && !self._json) { 1152 response.body = self.encoding === null ? Buffer.alloc(0) : '' 1153 } 1154 self.emit('complete', response, response.body) 1155 }) 1156 } 1157 1158 Request.prototype.abort = function () { 1159 var self = this 1160 self._aborted = true 1161 1162 if (self.req) { 1163 self.req.abort() 1164 } else if (self.response) { 1165 self.response.destroy() 1166 } 1167 1168 self.clearTimeout() 1169 self.emit('abort') 1170 } 1171 1172 Request.prototype.pipeDest = function (dest) { 1173 var self = this 1174 var response = self.response 1175 // Called after the response is received 1176 if (dest.headers && !dest.headersSent) { 1177 if (response.caseless.has('content-type')) { 1178 var ctname = response.caseless.has('content-type') 1179 if (dest.setHeader) { 1180 dest.setHeader(ctname, response.headers[ctname]) 1181 } else { 1182 dest.headers[ctname] = response.headers[ctname] 1183 } 1184 } 1185 1186 if (response.caseless.has('content-length')) { 1187 var clname = response.caseless.has('content-length') 1188 if (dest.setHeader) { 1189 dest.setHeader(clname, response.headers[clname]) 1190 } else { 1191 dest.headers[clname] = response.headers[clname] 1192 } 1193 } 1194 } 1195 if (dest.setHeader && !dest.headersSent) { 1196 for (var i in response.headers) { 1197 // If the response content is being decoded, the Content-Encoding header 1198 // of the response doesn't represent the piped content, so don't pass it. 1199 if (!self.gzip || i !== 'content-encoding') { 1200 dest.setHeader(i, response.headers[i]) 1201 } 1202 } 1203 dest.statusCode = response.statusCode 1204 } 1205 if (self.pipefilter) { 1206 self.pipefilter(response, dest) 1207 } 1208 } 1209 1210 Request.prototype.qs = function (q, clobber) { 1211 var self = this 1212 var base 1213 if (!clobber && self.uri.query) { 1214 base = self._qs.parse(self.uri.query) 1215 } else { 1216 base = {} 1217 } 1218 1219 for (var i in q) { 1220 base[i] = q[i] 1221 } 1222 1223 var qs = self._qs.stringify(base) 1224 1225 if (qs === '') { 1226 return self 1227 } 1228 1229 self.uri = url.parse(self.uri.href.split('?')[0] + '?' + qs) 1230 self.url = self.uri 1231 self.path = self.uri.path 1232 1233 if (self.uri.host === 'unix') { 1234 self.enableUnixSocket() 1235 } 1236 1237 return self 1238 } 1239 Request.prototype.form = function (form) { 1240 var self = this 1241 if (form) { 1242 if (!/^application\/x-www-form-urlencoded\b/.test(self.getHeader('content-type'))) { 1243 self.setHeader('content-type', 'application/x-www-form-urlencoded') 1244 } 1245 self.body = (typeof form === 'string') 1246 ? self._qs.rfc3986(form.toString('utf8')) 1247 : self._qs.stringify(form).toString('utf8') 1248 return self 1249 } 1250 // create form-data object 1251 self._form = new FormData() 1252 self._form.on('error', function (err) { 1253 err.message = 'form-data: ' + err.message 1254 self.emit('error', err) 1255 self.abort() 1256 }) 1257 return self._form 1258 } 1259 Request.prototype.multipart = function (multipart) { 1260 var self = this 1261 1262 self._multipart.onRequest(multipart) 1263 1264 if (!self._multipart.chunked) { 1265 self.body = self._multipart.body 1266 } 1267 1268 return self 1269 } 1270 Request.prototype.json = function (val) { 1271 var self = this 1272 1273 if (!self.hasHeader('accept')) { 1274 self.setHeader('accept', 'application/json') 1275 } 1276 1277 if (typeof self.jsonReplacer === 'function') { 1278 self._jsonReplacer = self.jsonReplacer 1279 } 1280 1281 self._json = true 1282 if (typeof val === 'boolean') { 1283 if (self.body !== undefined) { 1284 if (!/^application\/x-www-form-urlencoded\b/.test(self.getHeader('content-type'))) { 1285 self.body = safeStringify(self.body, self._jsonReplacer) 1286 } else { 1287 self.body = self._qs.rfc3986(self.body) 1288 } 1289 if (!self.hasHeader('content-type')) { 1290 self.setHeader('content-type', 'application/json') 1291 } 1292 } 1293 } else { 1294 self.body = safeStringify(val, self._jsonReplacer) 1295 if (!self.hasHeader('content-type')) { 1296 self.setHeader('content-type', 'application/json') 1297 } 1298 } 1299 1300 if (typeof self.jsonReviver === 'function') { 1301 self._jsonReviver = self.jsonReviver 1302 } 1303 1304 return self 1305 } 1306 Request.prototype.getHeader = function (name, headers) { 1307 var self = this 1308 var result, re, match 1309 if (!headers) { 1310 headers = self.headers 1311 } 1312 Object.keys(headers).forEach(function (key) { 1313 if (key.length !== name.length) { 1314 return 1315 } 1316 re = new RegExp(name, 'i') 1317 match = key.match(re) 1318 if (match) { 1319 result = headers[key] 1320 } 1321 }) 1322 return result 1323 } 1324 Request.prototype.enableUnixSocket = function () { 1325 // Get the socket & request paths from the URL 1326 var unixParts = this.uri.path.split(':') 1327 var host = unixParts[0] 1328 var path = unixParts[1] 1329 // Apply unix properties to request 1330 this.socketPath = host 1331 this.uri.pathname = path 1332 this.uri.path = path 1333 this.uri.host = host 1334 this.uri.hostname = host 1335 this.uri.isUnix = true 1336 } 1337 1338 Request.prototype.auth = function (user, pass, sendImmediately, bearer) { 1339 var self = this 1340 1341 self._auth.onRequest(user, pass, sendImmediately, bearer) 1342 1343 return self 1344 } 1345 Request.prototype.aws = function (opts, now) { 1346 var self = this 1347 1348 if (!now) { 1349 self._aws = opts 1350 return self 1351 } 1352 1353 if (opts.sign_version === 4 || opts.sign_version === '4') { 1354 // use aws4 1355 var options = { 1356 host: self.uri.host, 1357 path: self.uri.path, 1358 method: self.method, 1359 headers: self.headers, 1360 body: self.body 1361 } 1362 if (opts.service) { 1363 options.service = opts.service 1364 } 1365 var signRes = aws4.sign(options, { 1366 accessKeyId: opts.key, 1367 secretAccessKey: opts.secret, 1368 sessionToken: opts.session 1369 }) 1370 self.setHeader('authorization', signRes.headers.Authorization) 1371 self.setHeader('x-amz-date', signRes.headers['X-Amz-Date']) 1372 if (signRes.headers['X-Amz-Security-Token']) { 1373 self.setHeader('x-amz-security-token', signRes.headers['X-Amz-Security-Token']) 1374 } 1375 } else { 1376 // default: use aws-sign2 1377 var date = new Date() 1378 self.setHeader('date', date.toUTCString()) 1379 var auth = { 1380 key: opts.key, 1381 secret: opts.secret, 1382 verb: self.method.toUpperCase(), 1383 date: date, 1384 contentType: self.getHeader('content-type') || '', 1385 md5: self.getHeader('content-md5') || '', 1386 amazonHeaders: aws2.canonicalizeHeaders(self.headers) 1387 } 1388 var path = self.uri.path 1389 if (opts.bucket && path) { 1390 auth.resource = '/' + opts.bucket + path 1391 } else if (opts.bucket && !path) { 1392 auth.resource = '/' + opts.bucket 1393 } else if (!opts.bucket && path) { 1394 auth.resource = path 1395 } else if (!opts.bucket && !path) { 1396 auth.resource = '/' 1397 } 1398 auth.resource = aws2.canonicalizeResource(auth.resource) 1399 self.setHeader('authorization', aws2.authorization(auth)) 1400 } 1401 1402 return self 1403 } 1404 Request.prototype.httpSignature = function (opts) { 1405 var self = this 1406 httpSignature.signRequest({ 1407 getHeader: function (header) { 1408 return self.getHeader(header, self.headers) 1409 }, 1410 setHeader: function (header, value) { 1411 self.setHeader(header, value) 1412 }, 1413 method: self.method, 1414 path: self.path 1415 }, opts) 1416 debug('httpSignature authorization', self.getHeader('authorization')) 1417 1418 return self 1419 } 1420 Request.prototype.hawk = function (opts) { 1421 var self = this 1422 self.setHeader('Authorization', hawk.header(self.uri, self.method, opts)) 1423 } 1424 Request.prototype.oauth = function (_oauth) { 1425 var self = this 1426 1427 self._oauth.onRequest(_oauth) 1428 1429 return self 1430 } 1431 1432 Request.prototype.jar = function (jar) { 1433 var self = this 1434 var cookies 1435 1436 if (self._redirect.redirectsFollowed === 0) { 1437 self.originalCookieHeader = self.getHeader('cookie') 1438 } 1439 1440 if (!jar) { 1441 // disable cookies 1442 cookies = false 1443 self._disableCookies = true 1444 } else { 1445 var targetCookieJar = jar.getCookieString ? jar : globalCookieJar 1446 var urihref = self.uri.href 1447 // fetch cookie in the Specified host 1448 if (targetCookieJar) { 1449 cookies = targetCookieJar.getCookieString(urihref) 1450 } 1451 } 1452 1453 // if need cookie and cookie is not empty 1454 if (cookies && cookies.length) { 1455 if (self.originalCookieHeader) { 1456 // Don't overwrite existing Cookie header 1457 self.setHeader('cookie', self.originalCookieHeader + '; ' + cookies) 1458 } else { 1459 self.setHeader('cookie', cookies) 1460 } 1461 } 1462 self._jar = jar 1463 return self 1464 } 1465 1466 // Stream API 1467 Request.prototype.pipe = function (dest, opts) { 1468 var self = this 1469 1470 if (self.response) { 1471 if (self._destdata) { 1472 self.emit('error', new Error('You cannot pipe after data has been emitted from the response.')) 1473 } else if (self._ended) { 1474 self.emit('error', new Error('You cannot pipe after the response has been ended.')) 1475 } else { 1476 stream.Stream.prototype.pipe.call(self, dest, opts) 1477 self.pipeDest(dest) 1478 return dest 1479 } 1480 } else { 1481 self.dests.push(dest) 1482 stream.Stream.prototype.pipe.call(self, dest, opts) 1483 return dest 1484 } 1485 } 1486 Request.prototype.write = function () { 1487 var self = this 1488 if (self._aborted) { return } 1489 1490 if (!self._started) { 1491 self.start() 1492 } 1493 if (self.req) { 1494 return self.req.write.apply(self.req, arguments) 1495 } 1496 } 1497 Request.prototype.end = function (chunk) { 1498 var self = this 1499 if (self._aborted) { return } 1500 1501 if (chunk) { 1502 self.write(chunk) 1503 } 1504 if (!self._started) { 1505 self.start() 1506 } 1507 if (self.req) { 1508 self.req.end() 1509 } 1510 } 1511 Request.prototype.pause = function () { 1512 var self = this 1513 if (!self.responseContent) { 1514 self._paused = true 1515 } else { 1516 self.responseContent.pause.apply(self.responseContent, arguments) 1517 } 1518 } 1519 Request.prototype.resume = function () { 1520 var self = this 1521 if (!self.responseContent) { 1522 self._paused = false 1523 } else { 1524 self.responseContent.resume.apply(self.responseContent, arguments) 1525 } 1526 } 1527 Request.prototype.destroy = function () { 1528 var self = this 1529 this.clearTimeout() 1530 if (!self._ended) { 1531 self.end() 1532 } else if (self.response) { 1533 self.response.destroy() 1534 } 1535 } 1536 1537 Request.prototype.clearTimeout = function () { 1538 if (this.timeoutTimer) { 1539 clearTimeout(this.timeoutTimer) 1540 this.timeoutTimer = null 1541 } 1542 } 1543 1544 Request.defaultProxyHeaderWhiteList = 1545 Tunnel.defaultProxyHeaderWhiteList.slice() 1546 1547 Request.defaultProxyHeaderExclusiveList = 1548 Tunnel.defaultProxyHeaderExclusiveList.slice() 1549 1550 // Exports 1551 1552 Request.prototype.toJSON = requestToJSON 1553 module.exports = Request