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