• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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