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