• 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  ObjectCreate,
27  ObjectDefineProperty,
28  ObjectKeys,
29  ObjectPrototypeHasOwnProperty,
30  ObjectSetPrototypeOf,
31  MathFloor,
32  Symbol,
33} = primordials;
34
35const { getDefaultHighWaterMark } = require('internal/streams/state');
36const assert = require('internal/assert');
37const EE = require('events');
38const Stream = require('stream');
39const internalUtil = require('internal/util');
40const { kOutHeaders, utcDate, kNeedDrain } = require('internal/http');
41const { Buffer } = require('buffer');
42const common = require('_http_common');
43const checkIsHttpToken = common._checkIsHttpToken;
44const checkInvalidHeaderChar = common._checkInvalidHeaderChar;
45const {
46  defaultTriggerAsyncIdScope,
47  symbols: { async_id_symbol }
48} = require('internal/async_hooks');
49const {
50  codes: {
51    ERR_HTTP_HEADERS_SENT,
52    ERR_HTTP_INVALID_HEADER_VALUE,
53    ERR_HTTP_TRAILER_INVALID,
54    ERR_INVALID_HTTP_TOKEN,
55    ERR_INVALID_ARG_TYPE,
56    ERR_INVALID_CHAR,
57    ERR_METHOD_NOT_IMPLEMENTED,
58    ERR_STREAM_CANNOT_PIPE,
59    ERR_STREAM_WRITE_AFTER_END
60  },
61  hideStackFrames
62} = require('internal/errors');
63const { validateString } = require('internal/validators');
64const { isUint8Array } = require('internal/util/types');
65
66const HIGH_WATER_MARK = getDefaultHighWaterMark();
67const { CRLF, debug } = common;
68
69const kCorked = Symbol('corked');
70
71const RE_CONN_CLOSE = /(?:^|\W)close(?:$|\W)/i;
72const RE_TE_CHUNKED = common.chunkExpression;
73
74// isCookieField performs a case-insensitive comparison of a provided string
75// against the word "cookie." As of V8 6.6 this is faster than handrolling or
76// using a case-insensitive RegExp.
77function isCookieField(s) {
78  return s.length === 6 && s.toLowerCase() === 'cookie';
79}
80
81function noopPendingOutput(amount) {}
82
83function OutgoingMessage() {
84  Stream.call(this);
85
86  // Queue that holds all currently pending data, until the response will be
87  // assigned to the socket (until it will its turn in the HTTP pipeline).
88  this.outputData = [];
89
90  // `outputSize` is an approximate measure of how much data is queued on this
91  // response. `_onPendingData` will be invoked to update similar global
92  // per-connection counter. That counter will be used to pause/unpause the
93  // TCP socket and HTTP Parser and thus handle the backpressure.
94  this.outputSize = 0;
95
96  this.writable = true;
97
98  this._last = false;
99  this.chunkedEncoding = false;
100  this.shouldKeepAlive = true;
101  this._defaultKeepAlive = true;
102  this.useChunkedEncodingByDefault = true;
103  this.sendDate = false;
104  this._removedConnection = false;
105  this._removedContLen = false;
106  this._removedTE = false;
107
108  this._contentLength = null;
109  this._hasBody = true;
110  this._trailer = '';
111  this[kNeedDrain] = false;
112
113  this.finished = false;
114  this._headerSent = false;
115  this[kCorked] = 0;
116
117  this.socket = null;
118  this.connection = null;
119  this._header = null;
120  this[kOutHeaders] = null;
121
122  this._keepAliveTimeout = 0;
123
124  this._onPendingData = noopPendingOutput;
125}
126ObjectSetPrototypeOf(OutgoingMessage.prototype, Stream.prototype);
127ObjectSetPrototypeOf(OutgoingMessage, Stream);
128
129ObjectDefineProperty(OutgoingMessage.prototype, 'writableFinished', {
130  get() {
131    return (
132      this.finished &&
133      this.outputSize === 0 &&
134      (!this.socket || this.socket.writableLength === 0)
135    );
136  }
137});
138
139ObjectDefineProperty(OutgoingMessage.prototype, 'writableObjectMode', {
140  get() {
141    return false;
142  }
143});
144
145ObjectDefineProperty(OutgoingMessage.prototype, 'writableLength', {
146  get() {
147    return this.outputSize + (this.socket ? this.socket.writableLength : 0);
148  }
149});
150
151ObjectDefineProperty(OutgoingMessage.prototype, 'writableHighWaterMark', {
152  get() {
153    return this.socket ? this.socket.writableHighWaterMark : HIGH_WATER_MARK;
154  }
155});
156
157ObjectDefineProperty(OutgoingMessage.prototype, 'writableCorked', {
158  get() {
159    const corked = this.socket ? this.socket.writableCorked : 0;
160    return corked + this[kCorked];
161  }
162});
163
164ObjectDefineProperty(OutgoingMessage.prototype, '_headers', {
165  get: internalUtil.deprecate(function() {
166    return this.getHeaders();
167  }, 'OutgoingMessage.prototype._headers is deprecated', 'DEP0066'),
168  set: internalUtil.deprecate(function(val) {
169    if (val == null) {
170      this[kOutHeaders] = null;
171    } else if (typeof val === 'object') {
172      const headers = this[kOutHeaders] = ObjectCreate(null);
173      const keys = ObjectKeys(val);
174      // Retain for(;;) loop for performance reasons
175      // Refs: https://github.com/nodejs/node/pull/30958
176      for (let i = 0; i < keys.length; ++i) {
177        const name = keys[i];
178        headers[name.toLowerCase()] = [name, val[name]];
179      }
180    }
181  }, 'OutgoingMessage.prototype._headers is deprecated', 'DEP0066')
182});
183
184ObjectDefineProperty(OutgoingMessage.prototype, '_headerNames', {
185  get: internalUtil.deprecate(function() {
186    const headers = this[kOutHeaders];
187    if (headers !== null) {
188      const out = ObjectCreate(null);
189      const keys = ObjectKeys(headers);
190      // Retain for(;;) loop for performance reasons
191      // Refs: https://github.com/nodejs/node/pull/30958
192      for (let i = 0; i < keys.length; ++i) {
193        const key = keys[i];
194        const val = headers[key][0];
195        out[key] = val;
196      }
197      return out;
198    }
199    return null;
200  }, 'OutgoingMessage.prototype._headerNames is deprecated', 'DEP0066'),
201  set: internalUtil.deprecate(function(val) {
202    if (typeof val === 'object' && val !== null) {
203      const headers = this[kOutHeaders];
204      if (!headers)
205        return;
206      const keys = ObjectKeys(val);
207      // Retain for(;;) loop for performance reasons
208      // Refs: https://github.com/nodejs/node/pull/30958
209      for (let i = 0; i < keys.length; ++i) {
210        const header = headers[keys[i]];
211        if (header)
212          header[0] = val[keys[i]];
213      }
214    }
215  }, 'OutgoingMessage.prototype._headerNames is deprecated', 'DEP0066')
216});
217
218
219OutgoingMessage.prototype._renderHeaders = function _renderHeaders() {
220  if (this._header) {
221    throw new ERR_HTTP_HEADERS_SENT('render');
222  }
223
224  const headersMap = this[kOutHeaders];
225  const headers = {};
226
227  if (headersMap !== null) {
228    const keys = ObjectKeys(headersMap);
229    // Retain for(;;) loop for performance reasons
230    // Refs: https://github.com/nodejs/node/pull/30958
231    for (let i = 0, l = keys.length; i < l; i++) {
232      const key = keys[i];
233      headers[headersMap[key][0]] = headersMap[key][1];
234    }
235  }
236  return headers;
237};
238
239OutgoingMessage.prototype.cork = function() {
240  if (this.socket) {
241    this.socket.cork();
242  } else {
243    this[kCorked]++;
244  }
245};
246
247OutgoingMessage.prototype.uncork = function() {
248  if (this.socket) {
249    this.socket.uncork();
250  } else if (this[kCorked]) {
251    this[kCorked]--;
252  }
253};
254
255OutgoingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
256
257  if (callback) {
258    this.on('timeout', callback);
259  }
260
261  if (!this.socket) {
262    this.once('socket', function socketSetTimeoutOnConnect(socket) {
263      socket.setTimeout(msecs);
264    });
265  } else {
266    this.socket.setTimeout(msecs);
267  }
268  return this;
269};
270
271
272// It's possible that the socket will be destroyed, and removed from
273// any messages, before ever calling this.  In that case, just skip
274// it, since something else is destroying this connection anyway.
275OutgoingMessage.prototype.destroy = function destroy(error) {
276  if (this.socket) {
277    this.socket.destroy(error);
278  } else {
279    this.once('socket', function socketDestroyOnConnect(socket) {
280      socket.destroy(error);
281    });
282  }
283};
284
285
286// This abstract either writing directly to the socket or buffering it.
287OutgoingMessage.prototype._send = function _send(data, encoding, callback) {
288  // This is a shameful hack to get the headers and first body chunk onto
289  // the same packet. Future versions of Node are going to take care of
290  // this at a lower level and in a more general way.
291  if (!this._headerSent) {
292    if (typeof data === 'string' &&
293        (encoding === 'utf8' || encoding === 'latin1' || !encoding)) {
294      data = this._header + data;
295    } else {
296      const header = this._header;
297      this.outputData.unshift({
298        data: header,
299        encoding: 'latin1',
300        callback: null
301      });
302      this.outputSize += header.length;
303      this._onPendingData(header.length);
304    }
305    this._headerSent = true;
306  }
307  return this._writeRaw(data, encoding, callback);
308};
309
310
311OutgoingMessage.prototype._writeRaw = _writeRaw;
312function _writeRaw(data, encoding, callback) {
313  const conn = this.connection;
314  if (conn && conn.destroyed) {
315    // The socket was destroyed. If we're still trying to write to it,
316    // then we haven't gotten the 'close' event yet.
317    return false;
318  }
319
320  if (typeof encoding === 'function') {
321    callback = encoding;
322    encoding = null;
323  }
324
325  if (conn && conn._httpMessage === this && conn.writable) {
326    // There might be pending data in the this.output buffer.
327    if (this.outputData.length) {
328      this._flushOutput(conn);
329    }
330    // Directly write to socket.
331    return conn.write(data, encoding, callback);
332  }
333  // Buffer, as long as we're not destroyed.
334  this.outputData.push({ data, encoding, callback });
335  this.outputSize += data.length;
336  this._onPendingData(data.length);
337  return this.outputSize < HIGH_WATER_MARK;
338}
339
340
341OutgoingMessage.prototype._storeHeader = _storeHeader;
342function _storeHeader(firstLine, headers) {
343  // firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
344  // in the case of response it is: 'HTTP/1.1 200 OK\r\n'
345  const state = {
346    connection: false,
347    contLen: false,
348    te: false,
349    date: false,
350    expect: false,
351    trailer: false,
352    header: firstLine
353  };
354
355  if (headers) {
356    if (headers === this[kOutHeaders]) {
357      for (const key in headers) {
358        const entry = headers[key];
359        processHeader(this, state, entry[0], entry[1], false);
360      }
361    } else if (ArrayIsArray(headers)) {
362      for (const entry of headers) {
363        processHeader(this, state, entry[0], entry[1], true);
364      }
365    } else {
366      for (const key in headers) {
367        if (ObjectPrototypeHasOwnProperty(headers, key)) {
368          processHeader(this, state, key, headers[key], true);
369        }
370      }
371    }
372  }
373
374  let { header } = state;
375
376  // Date header
377  if (this.sendDate && !state.date) {
378    header += 'Date: ' + utcDate() + CRLF;
379  }
380
381  // Force the connection to close when the response is a 204 No Content or
382  // a 304 Not Modified and the user has set a "Transfer-Encoding: chunked"
383  // header.
384  //
385  // RFC 2616 mandates that 204 and 304 responses MUST NOT have a body but
386  // node.js used to send out a zero chunk anyway to accommodate clients
387  // that don't have special handling for those responses.
388  //
389  // It was pointed out that this might confuse reverse proxies to the point
390  // of creating security liabilities, so suppress the zero chunk and force
391  // the connection to close.
392  if (this.chunkedEncoding && (this.statusCode === 204 ||
393                               this.statusCode === 304)) {
394    debug(this.statusCode + ' response should not use chunked encoding,' +
395          ' closing connection.');
396    this.chunkedEncoding = false;
397    this.shouldKeepAlive = false;
398  }
399
400  // keep-alive logic
401  if (this._removedConnection) {
402    this._last = true;
403    this.shouldKeepAlive = false;
404  } else if (!state.connection) {
405    const shouldSendKeepAlive = this.shouldKeepAlive &&
406        (state.contLen || this.useChunkedEncodingByDefault || this.agent);
407    if (shouldSendKeepAlive) {
408      header += 'Connection: keep-alive\r\n';
409      if (this._keepAliveTimeout && this._defaultKeepAlive) {
410        const timeoutSeconds = MathFloor(this._keepAliveTimeout / 1000);
411        header += `Keep-Alive: timeout=${timeoutSeconds}\r\n`;
412      }
413    } else {
414      this._last = true;
415      header += 'Connection: close\r\n';
416    }
417  }
418
419  if (!state.contLen && !state.te) {
420    if (!this._hasBody) {
421      // Make sure we don't end the 0\r\n\r\n at the end of the message.
422      this.chunkedEncoding = false;
423    } else if (!this.useChunkedEncodingByDefault) {
424      this._last = true;
425    } else if (!state.trailer &&
426               !this._removedContLen &&
427               typeof this._contentLength === 'number') {
428      header += 'Content-Length: ' + this._contentLength + CRLF;
429    } else if (!this._removedTE) {
430      header += 'Transfer-Encoding: chunked\r\n';
431      this.chunkedEncoding = true;
432    } else {
433      // We should only be able to get here if both Content-Length and
434      // Transfer-Encoding are removed by the user.
435      // See: test/parallel/test-http-remove-header-stays-removed.js
436      debug('Both Content-Length and Transfer-Encoding are removed');
437    }
438  }
439
440  // Test non-chunked message does not have trailer header set,
441  // message will be terminated by the first empty line after the
442  // header fields, regardless of the header fields present in the
443  // message, and thus cannot contain a message body or 'trailers'.
444  if (this.chunkedEncoding !== true && state.trailer) {
445    throw new ERR_HTTP_TRAILER_INVALID();
446  }
447
448  this._header = header + CRLF;
449  this._headerSent = false;
450
451  // Wait until the first body chunk, or close(), is sent to flush,
452  // UNLESS we're sending Expect: 100-continue.
453  if (state.expect) this._send('');
454}
455
456function processHeader(self, state, key, value, validate) {
457  if (validate)
458    validateHeaderName(key);
459  if (ArrayIsArray(value)) {
460    if (value.length < 2 || !isCookieField(key)) {
461      // Retain for(;;) loop for performance reasons
462      // Refs: https://github.com/nodejs/node/pull/30958
463      for (let i = 0; i < value.length; i++)
464        storeHeader(self, state, key, value[i], validate);
465      return;
466    }
467    value = value.join('; ');
468  }
469  storeHeader(self, state, key, value, validate);
470}
471
472function storeHeader(self, state, key, value, validate) {
473  if (validate)
474    validateHeaderValue(key, value);
475  state.header += key + ': ' + value + CRLF;
476  matchHeader(self, state, key, value);
477}
478
479function matchHeader(self, state, field, value) {
480  if (field.length < 4 || field.length > 17)
481    return;
482  field = field.toLowerCase();
483  switch (field) {
484    case 'connection':
485      state.connection = true;
486      self._removedConnection = false;
487      if (RE_CONN_CLOSE.test(value))
488        self._last = true;
489      else
490        self.shouldKeepAlive = true;
491      break;
492    case 'transfer-encoding':
493      state.te = true;
494      self._removedTE = false;
495      if (RE_TE_CHUNKED.test(value)) self.chunkedEncoding = true;
496      break;
497    case 'content-length':
498      state.contLen = true;
499      self._removedContLen = false;
500      break;
501    case 'date':
502    case 'expect':
503    case 'trailer':
504      state[field] = true;
505      break;
506    case 'keep-alive':
507      self._defaultKeepAlive = false;
508      break;
509  }
510}
511
512const validateHeaderName = hideStackFrames((name) => {
513  if (typeof name !== 'string' || !name || !checkIsHttpToken(name)) {
514    throw new ERR_INVALID_HTTP_TOKEN('Header name', name);
515  }
516});
517
518const validateHeaderValue = hideStackFrames((name, value) => {
519  if (value === undefined) {
520    throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
521  }
522  if (checkInvalidHeaderChar(value)) {
523    debug('Header "%s" contains invalid characters', name);
524    throw new ERR_INVALID_CHAR('header content', name);
525  }
526});
527
528OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
529  if (this._header) {
530    throw new ERR_HTTP_HEADERS_SENT('set');
531  }
532  validateHeaderName(name);
533  validateHeaderValue(name, value);
534
535  let headers = this[kOutHeaders];
536  if (headers === null)
537    this[kOutHeaders] = headers = ObjectCreate(null);
538
539  headers[name.toLowerCase()] = [name, value];
540};
541
542
543OutgoingMessage.prototype.getHeader = function getHeader(name) {
544  validateString(name, 'name');
545
546  const headers = this[kOutHeaders];
547  if (headers === null)
548    return;
549
550  const entry = headers[name.toLowerCase()];
551  return entry && entry[1];
552};
553
554
555// Returns an array of the names of the current outgoing headers.
556OutgoingMessage.prototype.getHeaderNames = function getHeaderNames() {
557  return this[kOutHeaders] !== null ? ObjectKeys(this[kOutHeaders]) : [];
558};
559
560
561// Returns a shallow copy of the current outgoing headers.
562OutgoingMessage.prototype.getHeaders = function getHeaders() {
563  const headers = this[kOutHeaders];
564  const ret = ObjectCreate(null);
565  if (headers) {
566    const keys = ObjectKeys(headers);
567    // Retain for(;;) loop for performance reasons
568    // Refs: https://github.com/nodejs/node/pull/30958
569    for (let i = 0; i < keys.length; ++i) {
570      const key = keys[i];
571      const val = headers[key][1];
572      ret[key] = val;
573    }
574  }
575  return ret;
576};
577
578
579OutgoingMessage.prototype.hasHeader = function hasHeader(name) {
580  validateString(name, 'name');
581  return this[kOutHeaders] !== null &&
582    !!this[kOutHeaders][name.toLowerCase()];
583};
584
585
586OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
587  validateString(name, 'name');
588
589  if (this._header) {
590    throw new ERR_HTTP_HEADERS_SENT('remove');
591  }
592
593  const key = name.toLowerCase();
594
595  switch (key) {
596    case 'connection':
597      this._removedConnection = true;
598      break;
599    case 'content-length':
600      this._removedContLen = true;
601      break;
602    case 'transfer-encoding':
603      this._removedTE = true;
604      break;
605    case 'date':
606      this.sendDate = false;
607      break;
608  }
609
610  if (this[kOutHeaders] !== null) {
611    delete this[kOutHeaders][key];
612  }
613};
614
615
616OutgoingMessage.prototype._implicitHeader = function _implicitHeader() {
617  this.emit('error', new ERR_METHOD_NOT_IMPLEMENTED('_implicitHeader()'));
618};
619
620ObjectDefineProperty(OutgoingMessage.prototype, 'headersSent', {
621  configurable: true,
622  enumerable: true,
623  get: function() { return !!this._header; }
624});
625
626ObjectDefineProperty(OutgoingMessage.prototype, 'writableEnded', {
627  get: function() { return this.finished; }
628});
629
630
631const crlf_buf = Buffer.from('\r\n');
632OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
633  const ret = write_(this, chunk, encoding, callback, false);
634  if (!ret)
635    this[kNeedDrain] = true;
636  return ret;
637};
638
639function write_(msg, chunk, encoding, callback, fromEnd) {
640  if (msg.finished) {
641    const err = new ERR_STREAM_WRITE_AFTER_END();
642    const triggerAsyncId = msg.socket ? msg.socket[async_id_symbol] : undefined;
643    defaultTriggerAsyncIdScope(triggerAsyncId,
644                               process.nextTick,
645                               writeAfterEndNT,
646                               msg,
647                               err,
648                               callback);
649
650    return true;
651  }
652
653  if (!msg._header) {
654    msg._implicitHeader();
655  }
656
657  if (!msg._hasBody) {
658    debug('This type of response MUST NOT have a body. ' +
659          'Ignoring write() calls.');
660    if (callback) process.nextTick(callback);
661    return true;
662  }
663
664  if (!fromEnd && typeof chunk !== 'string' && !isUint8Array(chunk)) {
665    throw new ERR_INVALID_ARG_TYPE('first argument',
666                                   ['string', 'Buffer', 'Uint8Array'], chunk);
667  }
668
669  if (!fromEnd && msg.connection && !msg.connection.writableCorked) {
670    msg.connection.cork();
671    process.nextTick(connectionCorkNT, msg, msg.connection);
672  }
673
674  let ret;
675  if (msg.chunkedEncoding && chunk.length !== 0) {
676    let len;
677    if (typeof chunk === 'string')
678      len = Buffer.byteLength(chunk, encoding);
679    else
680      len = chunk.length;
681
682    msg._send(len.toString(16), 'latin1', null);
683    msg._send(crlf_buf, null, null);
684    msg._send(chunk, encoding, null);
685    ret = msg._send(crlf_buf, null, callback);
686  } else {
687    ret = msg._send(chunk, encoding, callback);
688  }
689
690  debug('write ret = ' + ret);
691  return ret;
692}
693
694
695function writeAfterEndNT(msg, err, callback) {
696  msg.emit('error', err);
697  if (callback) callback(err);
698}
699
700
701function connectionCorkNT(conn) {
702  conn.uncork();
703}
704
705
706OutgoingMessage.prototype.addTrailers = function addTrailers(headers) {
707  this._trailer = '';
708  const keys = ObjectKeys(headers);
709  const isArray = ArrayIsArray(headers);
710  // Retain for(;;) loop for performance reasons
711  // Refs: https://github.com/nodejs/node/pull/30958
712  for (let i = 0, l = keys.length; i < l; i++) {
713    let field, value;
714    const key = keys[i];
715    if (isArray) {
716      field = headers[key][0];
717      value = headers[key][1];
718    } else {
719      field = key;
720      value = headers[key];
721    }
722    if (typeof field !== 'string' || !field || !checkIsHttpToken(field)) {
723      throw new ERR_INVALID_HTTP_TOKEN('Trailer name', field);
724    }
725    if (checkInvalidHeaderChar(value)) {
726      debug('Trailer "%s" contains invalid characters', field);
727      throw new ERR_INVALID_CHAR('trailer content', field);
728    }
729    this._trailer += field + ': ' + value + CRLF;
730  }
731};
732
733function onFinish(outmsg) {
734  if (outmsg && outmsg.socket && outmsg.socket._hadError) return;
735  outmsg.emit('finish');
736}
737
738OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
739  if (typeof chunk === 'function') {
740    callback = chunk;
741    chunk = null;
742  } else if (typeof encoding === 'function') {
743    callback = encoding;
744    encoding = null;
745  }
746
747  if (this.finished) {
748    return this;
749  }
750
751  if (this.socket) {
752    this.socket.cork();
753  }
754
755  if (chunk) {
756    if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
757      throw new ERR_INVALID_ARG_TYPE(
758        'chunk', ['string', 'Buffer', 'Uint8Array'], chunk);
759    }
760    if (!this._header) {
761      if (typeof chunk === 'string')
762        this._contentLength = Buffer.byteLength(chunk, encoding);
763      else
764        this._contentLength = chunk.length;
765    }
766    write_(this, chunk, encoding, null, true);
767  } else if (!this._header) {
768    this._contentLength = 0;
769    this._implicitHeader();
770  }
771
772  if (typeof callback === 'function')
773    this.once('finish', callback);
774
775  const finish = onFinish.bind(undefined, this);
776
777  if (this._hasBody && this.chunkedEncoding) {
778    this._send('0\r\n' + this._trailer + '\r\n', 'latin1', finish);
779  } else {
780    // Force a flush, HACK.
781    this._send('', 'latin1', finish);
782  }
783
784  if (this.socket) {
785    // Fully uncork connection on end().
786    this.socket._writableState.corked = 1;
787    this.socket.uncork();
788  }
789  this[kCorked] = 0;
790
791  this.finished = true;
792
793  // There is the first message on the outgoing queue, and we've sent
794  // everything to the socket.
795  debug('outgoing message end.');
796  if (this.outputData.length === 0 &&
797      this.connection &&
798      this.connection._httpMessage === this) {
799    this._finish();
800  }
801
802  return this;
803};
804
805
806OutgoingMessage.prototype._finish = function _finish() {
807  assert(this.connection);
808  this.emit('prefinish');
809};
810
811
812// This logic is probably a bit confusing. Let me explain a bit:
813//
814// In both HTTP servers and clients it is possible to queue up several
815// outgoing messages. This is easiest to imagine in the case of a client.
816// Take the following situation:
817//
818//    req1 = client.request('GET', '/');
819//    req2 = client.request('POST', '/');
820//
821// When the user does
822//
823//   req2.write('hello world\n');
824//
825// it's possible that the first request has not been completely flushed to
826// the socket yet. Thus the outgoing messages need to be prepared to queue
827// up data internally before sending it on further to the socket's queue.
828//
829// This function, outgoingFlush(), is called by both the Server and Client
830// to attempt to flush any pending messages out to the socket.
831OutgoingMessage.prototype._flush = function _flush() {
832  const socket = this.socket;
833
834  if (socket && socket.writable) {
835    // There might be remaining data in this.output; write it out
836    const ret = this._flushOutput(socket);
837
838    if (this.finished) {
839      // This is a queue to the server or client to bring in the next this.
840      this._finish();
841    } else if (ret && this[kNeedDrain]) {
842      this[kNeedDrain] = false;
843      this.emit('drain');
844    }
845  }
846};
847
848OutgoingMessage.prototype._flushOutput = function _flushOutput(socket) {
849  while (this[kCorked]) {
850    this[kCorked]--;
851    socket.cork();
852  }
853
854  const outputLength = this.outputData.length;
855  if (outputLength <= 0)
856    return undefined;
857
858  const outputData = this.outputData;
859  socket.cork();
860  let ret;
861  // Retain for(;;) loop for performance reasons
862  // Refs: https://github.com/nodejs/node/pull/30958
863  for (let i = 0; i < outputLength; i++) {
864    const { data, encoding, callback } = outputData[i];
865    ret = socket.write(data, encoding, callback);
866  }
867  socket.uncork();
868
869  this.outputData = [];
870  this._onPendingData(-this.outputSize);
871  this.outputSize = 0;
872
873  return ret;
874};
875
876
877OutgoingMessage.prototype.flushHeaders = function flushHeaders() {
878  if (!this._header) {
879    this._implicitHeader();
880  }
881
882  // Force-flush the headers.
883  this._send('');
884};
885
886OutgoingMessage.prototype.flush = internalUtil.deprecate(function() {
887  this.flushHeaders();
888}, 'OutgoingMessage.flush is deprecated. Use flushHeaders instead.', 'DEP0001');
889
890OutgoingMessage.prototype.pipe = function pipe() {
891  // OutgoingMessage should be write-only. Piping from it is disabled.
892  this.emit('error', new ERR_STREAM_CANNOT_PIPE());
893};
894
895OutgoingMessage.prototype[EE.captureRejectionSymbol] =
896function(err, event) {
897  this.destroy(err);
898};
899
900module.exports = {
901  OutgoingMessage
902};
903