1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23 24const { 25 ArrayIsArray, 26 Boolean, 27 Error, 28 NumberIsFinite, 29 ObjectAssign, 30 ObjectKeys, 31 ObjectSetPrototypeOf, 32} = primordials; 33 34const net = require('net'); 35const url = require('url'); 36const assert = require('internal/assert'); 37const { 38 _checkIsHttpToken: checkIsHttpToken, 39 debug, 40 freeParser, 41 parsers, 42 HTTPParser, 43 isLenient, 44 prepareError, 45} = require('_http_common'); 46const { OutgoingMessage } = require('_http_outgoing'); 47const Agent = require('_http_agent'); 48const { Buffer } = require('buffer'); 49const { defaultTriggerAsyncIdScope } = require('internal/async_hooks'); 50const { URL, urlToOptions, searchParamsSymbol } = require('internal/url'); 51const { kOutHeaders, kNeedDrain } = require('internal/http'); 52const { connResetException, codes } = require('internal/errors'); 53const { 54 ERR_HTTP_HEADERS_SENT, 55 ERR_INVALID_ARG_TYPE, 56 ERR_INVALID_HTTP_TOKEN, 57 ERR_INVALID_PROTOCOL, 58 ERR_UNESCAPED_CHARACTERS 59} = codes; 60const { getTimerDuration } = require('internal/timers'); 61const { 62 DTRACE_HTTP_CLIENT_REQUEST, 63 DTRACE_HTTP_CLIENT_RESPONSE 64} = require('internal/dtrace'); 65 66const INVALID_PATH_REGEX = /[^\u0021-\u00ff]/; 67 68function validateHost(host, name) { 69 if (host !== null && host !== undefined && typeof host !== 'string') { 70 throw new ERR_INVALID_ARG_TYPE(`options.${name}`, 71 ['string', 'undefined', 'null'], 72 host); 73 } 74 return host; 75} 76 77class HTTPClientAsyncResource { 78 constructor(type, req) { 79 this.type = type; 80 this.req = req; 81 } 82} 83 84let urlWarningEmitted = false; 85function ClientRequest(input, options, cb) { 86 OutgoingMessage.call(this); 87 88 if (typeof input === 'string') { 89 const urlStr = input; 90 try { 91 input = urlToOptions(new URL(urlStr)); 92 } catch (err) { 93 input = url.parse(urlStr); 94 if (!input.hostname) { 95 throw err; 96 } 97 if (!urlWarningEmitted && !process.noDeprecation) { 98 urlWarningEmitted = true; 99 process.emitWarning( 100 `The provided URL ${urlStr} is not a valid URL, and is supported ` + 101 'in the http module solely for compatibility.', 102 'DeprecationWarning', 'DEP0109'); 103 } 104 } 105 } else if (input && input[searchParamsSymbol] && 106 input[searchParamsSymbol][searchParamsSymbol]) { 107 // url.URL instance 108 input = urlToOptions(input); 109 } else { 110 cb = options; 111 options = input; 112 input = null; 113 } 114 115 if (typeof options === 'function') { 116 cb = options; 117 options = input || {}; 118 } else { 119 options = ObjectAssign(input || {}, options); 120 } 121 122 let agent = options.agent; 123 const defaultAgent = options._defaultAgent || Agent.globalAgent; 124 if (agent === false) { 125 agent = new defaultAgent.constructor(); 126 } else if (agent === null || agent === undefined) { 127 if (typeof options.createConnection !== 'function') { 128 agent = defaultAgent; 129 } 130 // Explicitly pass through this statement as agent will not be used 131 // when createConnection is provided. 132 } else if (typeof agent.addRequest !== 'function') { 133 throw new ERR_INVALID_ARG_TYPE('options.agent', 134 ['Agent-like Object', 'undefined', 'false'], 135 agent); 136 } 137 this.agent = agent; 138 139 const protocol = options.protocol || defaultAgent.protocol; 140 let expectedProtocol = defaultAgent.protocol; 141 if (this.agent && this.agent.protocol) 142 expectedProtocol = this.agent.protocol; 143 144 if (options.path) { 145 const path = String(options.path); 146 if (INVALID_PATH_REGEX.test(path)) 147 throw new ERR_UNESCAPED_CHARACTERS('Request path'); 148 } 149 150 if (protocol !== expectedProtocol) { 151 throw new ERR_INVALID_PROTOCOL(protocol, expectedProtocol); 152 } 153 154 const defaultPort = options.defaultPort || 155 (this.agent && this.agent.defaultPort); 156 157 const port = options.port = options.port || defaultPort || 80; 158 const host = options.host = validateHost(options.hostname, 'hostname') || 159 validateHost(options.host, 'host') || 'localhost'; 160 161 const setHost = (options.setHost === undefined || Boolean(options.setHost)); 162 163 this.socketPath = options.socketPath; 164 165 if (options.timeout !== undefined) 166 this.timeout = getTimerDuration(options.timeout, 'timeout'); 167 168 let method = options.method; 169 const methodIsString = (typeof method === 'string'); 170 if (method !== null && method !== undefined && !methodIsString) { 171 throw new ERR_INVALID_ARG_TYPE('options.method', 'string', method); 172 } 173 174 if (methodIsString && method) { 175 if (!checkIsHttpToken(method)) { 176 throw new ERR_INVALID_HTTP_TOKEN('Method', method); 177 } 178 method = this.method = method.toUpperCase(); 179 } else { 180 method = this.method = 'GET'; 181 } 182 183 const insecureHTTPParser = options.insecureHTTPParser; 184 if (insecureHTTPParser !== undefined && 185 typeof insecureHTTPParser !== 'boolean') { 186 throw new ERR_INVALID_ARG_TYPE( 187 'options.insecureHTTPParser', 'boolean', insecureHTTPParser); 188 } 189 this.insecureHTTPParser = insecureHTTPParser; 190 191 this.path = options.path || '/'; 192 if (cb) { 193 this.once('response', cb); 194 } 195 196 if (method === 'GET' || 197 method === 'HEAD' || 198 method === 'DELETE' || 199 method === 'OPTIONS' || 200 method === 'TRACE' || 201 method === 'CONNECT') { 202 this.useChunkedEncodingByDefault = false; 203 } else { 204 this.useChunkedEncodingByDefault = true; 205 } 206 207 this._ended = false; 208 this.res = null; 209 this.aborted = false; 210 this.timeoutCb = null; 211 this.upgradeOrConnect = false; 212 this.parser = null; 213 this.maxHeadersCount = null; 214 this.reusedSocket = false; 215 this.host = host; 216 this.protocol = protocol; 217 218 let called = false; 219 220 if (this.agent) { 221 // If there is an agent we should default to Connection:keep-alive, 222 // but only if the Agent will actually reuse the connection! 223 // If it's not a keepAlive agent, and the maxSockets==Infinity, then 224 // there's never a case where this socket will actually be reused 225 if (!this.agent.keepAlive && !NumberIsFinite(this.agent.maxSockets)) { 226 this._last = true; 227 this.shouldKeepAlive = false; 228 } else { 229 this._last = false; 230 this.shouldKeepAlive = true; 231 } 232 } 233 234 const headersArray = ArrayIsArray(options.headers); 235 if (!headersArray) { 236 if (options.headers) { 237 const keys = ObjectKeys(options.headers); 238 // Retain for(;;) loop for performance reasons 239 // Refs: https://github.com/nodejs/node/pull/30958 240 for (let i = 0; i < keys.length; i++) { 241 const key = keys[i]; 242 this.setHeader(key, options.headers[key]); 243 } 244 } 245 246 if (host && !this.getHeader('host') && setHost) { 247 let hostHeader = host; 248 249 // For the Host header, ensure that IPv6 addresses are enclosed 250 // in square brackets, as defined by URI formatting 251 // https://tools.ietf.org/html/rfc3986#section-3.2.2 252 const posColon = hostHeader.indexOf(':'); 253 if (posColon !== -1 && 254 hostHeader.includes(':', posColon + 1) && 255 hostHeader.charCodeAt(0) !== 91/* '[' */) { 256 hostHeader = `[${hostHeader}]`; 257 } 258 259 if (port && +port !== defaultPort) { 260 hostHeader += ':' + port; 261 } 262 this.setHeader('Host', hostHeader); 263 } 264 265 if (options.auth && !this.getHeader('Authorization')) { 266 this.setHeader('Authorization', 'Basic ' + 267 Buffer.from(options.auth).toString('base64')); 268 } 269 270 if (this.getHeader('expect')) { 271 if (this._header) { 272 throw new ERR_HTTP_HEADERS_SENT('render'); 273 } 274 275 this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n', 276 this[kOutHeaders]); 277 } 278 } else { 279 this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n', 280 options.headers); 281 } 282 283 const oncreate = (err, socket) => { 284 if (called) 285 return; 286 called = true; 287 if (err) { 288 process.nextTick(() => this.emit('error', err)); 289 return; 290 } 291 this.onSocket(socket); 292 this._deferToConnect(null, null, () => this._flush()); 293 }; 294 295 // initiate connection 296 if (this.agent) { 297 this.agent.addRequest(this, options); 298 } else { 299 // No agent, default to Connection:close. 300 this._last = true; 301 this.shouldKeepAlive = false; 302 if (typeof options.createConnection === 'function') { 303 const newSocket = options.createConnection(options, oncreate); 304 if (newSocket && !called) { 305 called = true; 306 this.onSocket(newSocket); 307 } else { 308 return; 309 } 310 } else { 311 debug('CLIENT use net.createConnection', options); 312 this.onSocket(net.createConnection(options)); 313 } 314 } 315 316 this._deferToConnect(null, null, () => this._flush()); 317} 318ObjectSetPrototypeOf(ClientRequest.prototype, OutgoingMessage.prototype); 319ObjectSetPrototypeOf(ClientRequest, OutgoingMessage); 320 321ClientRequest.prototype._finish = function _finish() { 322 DTRACE_HTTP_CLIENT_REQUEST(this, this.connection); 323 OutgoingMessage.prototype._finish.call(this); 324}; 325 326ClientRequest.prototype._implicitHeader = function _implicitHeader() { 327 if (this._header) { 328 throw new ERR_HTTP_HEADERS_SENT('render'); 329 } 330 this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n', 331 this[kOutHeaders]); 332}; 333 334ClientRequest.prototype.abort = function abort() { 335 if (!this.aborted) { 336 process.nextTick(emitAbortNT, this); 337 } 338 this.aborted = true; 339 340 // If we're aborting, we don't care about any more response data. 341 if (this.res) { 342 this.res._dump(); 343 } 344 345 // In the event that we don't have a socket, we will pop out of 346 // the request queue through handling in onSocket. 347 if (this.socket) { 348 // in-progress 349 this.socket.destroy(); 350 } 351}; 352 353 354function emitAbortNT(req) { 355 req.emit('abort'); 356} 357 358function ondrain() { 359 const msg = this._httpMessage; 360 if (msg && !msg.finished && msg[kNeedDrain]) { 361 msg[kNeedDrain] = false; 362 msg.emit('drain'); 363 } 364} 365 366function socketCloseListener() { 367 const socket = this; 368 const req = socket._httpMessage; 369 debug('HTTP socket close'); 370 371 // Pull through final chunk, if anything is buffered. 372 // the ondata function will handle it properly, and this 373 // is a no-op if no final chunk remains. 374 socket.read(); 375 376 // NOTE: It's important to get parser here, because it could be freed by 377 // the `socketOnData`. 378 const parser = socket.parser; 379 const res = req.res; 380 if (res) { 381 // Socket closed before we emitted 'end' below. 382 if (!res.complete) { 383 res.aborted = true; 384 res.emit('aborted'); 385 } 386 req.emit('close'); 387 if (res.readable) { 388 res.on('end', function() { 389 this.emit('close'); 390 }); 391 res.push(null); 392 } else { 393 res.emit('close'); 394 } 395 } else { 396 if (!req.socket._hadError) { 397 // This socket error fired before we started to 398 // receive a response. The error needs to 399 // fire on the request. 400 req.socket._hadError = true; 401 req.emit('error', connResetException('socket hang up')); 402 } 403 req.emit('close'); 404 } 405 406 // Too bad. That output wasn't getting written. 407 // This is pretty terrible that it doesn't raise an error. 408 // Fixed better in v0.10 409 if (req.outputData) 410 req.outputData.length = 0; 411 412 if (parser) { 413 parser.finish(); 414 freeParser(parser, req, socket); 415 } 416} 417 418function socketErrorListener(err) { 419 const socket = this; 420 const req = socket._httpMessage; 421 debug('SOCKET ERROR:', err.message, err.stack); 422 423 if (req) { 424 // For Safety. Some additional errors might fire later on 425 // and we need to make sure we don't double-fire the error event. 426 req.socket._hadError = true; 427 req.emit('error', err); 428 } 429 430 // Handle any pending data 431 socket.read(); 432 433 const parser = socket.parser; 434 if (parser) { 435 // Use process.nextTick() on v12.x since 'error' events might be 436 // emitted synchronously from e.g. a failed write() call on the socket. 437 process.nextTick(() => { 438 parser.finish(); 439 freeParser(parser, req, socket); 440 }); 441 } 442 443 // Ensure that no further data will come out of the socket 444 socket.removeListener('data', socketOnData); 445 socket.removeListener('end', socketOnEnd); 446 socket.destroy(); 447} 448 449function socketOnEnd() { 450 const socket = this; 451 const req = this._httpMessage; 452 const parser = this.parser; 453 454 if (!req.res && !req.socket._hadError) { 455 // If we don't have a response then we know that the socket 456 // ended prematurely and we need to emit an error on the request. 457 req.socket._hadError = true; 458 req.emit('error', connResetException('socket hang up')); 459 } 460 if (parser) { 461 parser.finish(); 462 freeParser(parser, req, socket); 463 } 464 socket.destroy(); 465} 466 467function socketOnData(d) { 468 const socket = this; 469 const req = this._httpMessage; 470 const parser = this.parser; 471 472 assert(parser && parser.socket === socket); 473 474 const ret = parser.execute(d); 475 if (ret instanceof Error) { 476 prepareError(ret, parser, d); 477 debug('parse error', ret); 478 freeParser(parser, req, socket); 479 socket.destroy(); 480 req.socket._hadError = true; 481 req.emit('error', ret); 482 } else if (parser.incoming && parser.incoming.upgrade) { 483 // Upgrade (if status code 101) or CONNECT 484 const bytesParsed = ret; 485 const res = parser.incoming; 486 req.res = res; 487 488 socket.removeListener('data', socketOnData); 489 socket.removeListener('end', socketOnEnd); 490 socket.removeListener('drain', ondrain); 491 parser.finish(); 492 freeParser(parser, req, socket); 493 494 const bodyHead = d.slice(bytesParsed, d.length); 495 496 const eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade'; 497 if (req.listenerCount(eventName) > 0) { 498 req.upgradeOrConnect = true; 499 500 // detach the socket 501 socket.emit('agentRemove'); 502 socket.removeListener('close', socketCloseListener); 503 socket.removeListener('error', socketErrorListener); 504 505 socket._httpMessage = null; 506 socket.readableFlowing = null; 507 508 req.emit(eventName, res, socket, bodyHead); 509 req.emit('close'); 510 } else { 511 // Requested Upgrade or used CONNECT method, but have no handler. 512 socket.destroy(); 513 } 514 } else if (parser.incoming && parser.incoming.complete && 515 // When the status code is informational (100, 102-199), 516 // the server will send a final response after this client 517 // sends a request body, so we must not free the parser. 518 // 101 (Switching Protocols) and all other status codes 519 // should be processed normally. 520 !statusIsInformational(parser.incoming.statusCode)) { 521 socket.removeListener('data', socketOnData); 522 socket.removeListener('end', socketOnEnd); 523 socket.removeListener('drain', ondrain); 524 freeParser(parser, req, socket); 525 } 526} 527 528function statusIsInformational(status) { 529 // 100 (Continue) RFC7231 Section 6.2.1 530 // 102 (Processing) RFC2518 531 // 103 (Early Hints) RFC8297 532 // 104-199 (Unassigned) 533 return (status < 200 && status >= 100 && status !== 101); 534} 535 536// client 537function parserOnIncomingClient(res, shouldKeepAlive) { 538 const socket = this.socket; 539 const req = socket._httpMessage; 540 541 debug('AGENT incoming response!'); 542 543 if (req.res) { 544 // We already have a response object, this means the server 545 // sent a double response. 546 socket.destroy(); 547 return 0; // No special treatment. 548 } 549 req.res = res; 550 551 // Skip body and treat as Upgrade. 552 if (res.upgrade) 553 return 2; 554 555 // Responses to CONNECT request is handled as Upgrade. 556 const method = req.method; 557 if (method === 'CONNECT') { 558 res.upgrade = true; 559 return 2; // Skip body and treat as Upgrade. 560 } 561 562 if (statusIsInformational(res.statusCode)) { 563 // Restart the parser, as this is a 1xx informational message. 564 req.res = null; // Clear res so that we don't hit double-responses. 565 // Maintain compatibility by sending 100-specific events 566 if (res.statusCode === 100) { 567 req.emit('continue'); 568 } 569 // Send information events to all 1xx responses except 101 Upgrade. 570 req.emit('information', { 571 statusCode: res.statusCode, 572 statusMessage: res.statusMessage, 573 httpVersion: res.httpVersion, 574 httpVersionMajor: res.httpVersionMajor, 575 httpVersionMinor: res.httpVersionMinor, 576 headers: res.headers, 577 rawHeaders: res.rawHeaders 578 }); 579 580 return 1; // Skip body but don't treat as Upgrade. 581 } 582 583 if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) { 584 // Server MUST respond with Connection:keep-alive for us to enable it. 585 // If we've been upgraded (via WebSockets) we also shouldn't try to 586 // keep the connection open. 587 req.shouldKeepAlive = false; 588 } 589 590 DTRACE_HTTP_CLIENT_RESPONSE(socket, req); 591 req.res = res; 592 res.req = req; 593 594 // Add our listener first, so that we guarantee socket cleanup 595 res.on('end', responseOnEnd); 596 req.on('prefinish', requestOnPrefinish); 597 598 // If the user did not listen for the 'response' event, then they 599 // can't possibly read the data, so we ._dump() it into the void 600 // so that the socket doesn't hang there in a paused state. 601 if (req.aborted || !req.emit('response', res)) 602 res._dump(); 603 604 if (method === 'HEAD') 605 return 1; // Skip body but don't treat as Upgrade. 606 607 return 0; // No special treatment. 608} 609 610// client 611function responseKeepAlive(req) { 612 const socket = req.socket; 613 614 debug('AGENT socket keep-alive'); 615 if (req.timeoutCb) { 616 socket.setTimeout(0, req.timeoutCb); 617 req.timeoutCb = null; 618 } 619 socket.removeListener('close', socketCloseListener); 620 socket.removeListener('error', socketErrorListener); 621 socket.removeListener('data', socketOnData); 622 socket.removeListener('end', socketOnEnd); 623 624 // TODO(ronag): Between here and emitFreeNT the socket 625 // has no 'error' handler. 626 627 // There are cases where _handle === null. Avoid those. Passing undefined to 628 // nextTick() will call getDefaultTriggerAsyncId() to retrieve the id. 629 const asyncId = socket._handle ? socket._handle.getAsyncId() : undefined; 630 // Mark this socket as available, AFTER user-added end 631 // handlers have a chance to run. 632 defaultTriggerAsyncIdScope(asyncId, process.nextTick, emitFreeNT, socket); 633} 634 635function responseOnEnd() { 636 const req = this.req; 637 638 if (req.socket && req.timeoutCb) { 639 req.socket.removeListener('timeout', emitRequestTimeout); 640 } 641 642 req._ended = true; 643 644 if (!req.shouldKeepAlive) { 645 const socket = req.socket; 646 if (socket.writable) { 647 debug('AGENT socket.destroySoon()'); 648 if (typeof socket.destroySoon === 'function') 649 socket.destroySoon(); 650 else 651 socket.end(); 652 } 653 assert(!socket.writable); 654 } else if (req.finished) { 655 // We can assume `req.finished` means all data has been written since: 656 // - `'responseOnEnd'` means we have been assigned a socket. 657 // - when we have a socket we write directly to it without buffering. 658 // - `req.finished` means `end()` has been called and no further data. 659 // can be written 660 responseKeepAlive(req); 661 } 662} 663 664function requestOnPrefinish() { 665 const req = this; 666 667 if (req.shouldKeepAlive && req._ended) 668 responseKeepAlive(req); 669} 670 671function emitFreeNT(socket) { 672 socket.emit('free'); 673} 674 675function tickOnSocket(req, socket) { 676 const parser = parsers.alloc(); 677 req.socket = socket; 678 req.connection = socket; 679 parser.initialize(HTTPParser.RESPONSE, 680 new HTTPClientAsyncResource('HTTPINCOMINGMESSAGE', req), 681 req.insecureHTTPParser === undefined ? 682 isLenient() : req.insecureHTTPParser, 683 0); 684 parser.socket = socket; 685 parser.outgoing = req; 686 req.parser = parser; 687 688 socket.parser = parser; 689 socket._httpMessage = req; 690 691 // Propagate headers limit from request object to parser 692 if (typeof req.maxHeadersCount === 'number') { 693 parser.maxHeaderPairs = req.maxHeadersCount << 1; 694 } 695 696 parser.onIncoming = parserOnIncomingClient; 697 socket.on('error', socketErrorListener); 698 socket.on('data', socketOnData); 699 socket.on('end', socketOnEnd); 700 socket.on('close', socketCloseListener); 701 socket.on('drain', ondrain); 702 703 if ( 704 req.timeout !== undefined || 705 (req.agent && req.agent.options && req.agent.options.timeout) 706 ) { 707 listenSocketTimeout(req); 708 } 709 req.emit('socket', socket); 710} 711 712function emitRequestTimeout() { 713 const req = this._httpMessage; 714 if (req) { 715 req.emit('timeout'); 716 } 717} 718 719function listenSocketTimeout(req) { 720 if (req.timeoutCb) { 721 return; 722 } 723 // Set timeoutCb so it will get cleaned up on request end. 724 req.timeoutCb = emitRequestTimeout; 725 // Delegate socket timeout event. 726 if (req.socket) { 727 req.socket.once('timeout', emitRequestTimeout); 728 } else { 729 req.on('socket', (socket) => { 730 socket.once('timeout', emitRequestTimeout); 731 }); 732 } 733} 734 735ClientRequest.prototype.onSocket = function onSocket(socket) { 736 // TODO(ronag): Between here and onSocketNT the socket 737 // has no 'error' handler. 738 process.nextTick(onSocketNT, this, socket); 739}; 740 741function onSocketNT(req, socket) { 742 if (req.aborted) { 743 // If we were aborted while waiting for a socket, skip the whole thing. 744 if (!req.agent) { 745 socket.destroy(); 746 } else { 747 socket.emit('free'); 748 } 749 } else { 750 tickOnSocket(req, socket); 751 } 752} 753 754ClientRequest.prototype._deferToConnect = _deferToConnect; 755function _deferToConnect(method, arguments_, cb) { 756 // This function is for calls that need to happen once the socket is 757 // assigned to this request and writable. It's an important promisy 758 // thing for all the socket calls that happen either now 759 // (when a socket is assigned) or in the future (when a socket gets 760 // assigned out of the pool and is eventually writable). 761 762 const callSocketMethod = () => { 763 if (method) 764 this.socket[method].apply(this.socket, arguments_); 765 766 if (typeof cb === 'function') 767 cb(); 768 }; 769 770 const onSocket = () => { 771 if (this.socket.writable) { 772 callSocketMethod(); 773 } else { 774 this.socket.once('connect', callSocketMethod); 775 } 776 }; 777 778 if (!this.socket) { 779 this.once('socket', onSocket); 780 } else { 781 onSocket(); 782 } 783} 784 785ClientRequest.prototype.setTimeout = function setTimeout(msecs, callback) { 786 if (this._ended) { 787 return this; 788 } 789 790 listenSocketTimeout(this); 791 msecs = getTimerDuration(msecs, 'msecs'); 792 if (callback) this.once('timeout', callback); 793 794 if (this.socket) { 795 setSocketTimeout(this.socket, msecs); 796 } else { 797 this.once('socket', (sock) => setSocketTimeout(sock, msecs)); 798 } 799 800 return this; 801}; 802 803function setSocketTimeout(sock, msecs) { 804 if (sock.connecting) { 805 sock.once('connect', function() { 806 sock.setTimeout(msecs); 807 }); 808 } else { 809 sock.setTimeout(msecs); 810 } 811} 812 813ClientRequest.prototype.setNoDelay = function setNoDelay(noDelay) { 814 this._deferToConnect('setNoDelay', [noDelay]); 815}; 816 817ClientRequest.prototype.setSocketKeepAlive = 818 function setSocketKeepAlive(enable, initialDelay) { 819 this._deferToConnect('setKeepAlive', [enable, initialDelay]); 820 }; 821 822ClientRequest.prototype.clearTimeout = function clearTimeout(cb) { 823 this.setTimeout(0, cb); 824}; 825 826module.exports = { 827 ClientRequest 828}; 829