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