diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md
index 786a6453161b9b..8de386118af960 100644
--- a/doc/api/deprecations.md
+++ b/doc/api/deprecations.md
@@ -556,6 +556,32 @@ The `NODE_REPL_MODE` environment variable is used to set the underlying
`replMode` of an interactive `node` session. Its default value, `magic`, is
similarly deprecated in favor of `sloppy`.
+
+### DEP0066: outgoingMessage.\_headers, outgoingMessage.\_headerNames
+
+Type: Documentation-only
+
+The `http` module `outgoingMessage._headers` and `outgoingMessage._headerNames`
+properties have been deprecated. Please instead use one of the public methods
+(e.g. `outgoingMessage.getHeader()`, `outgoingMessage.getHeaders()`,
+`outgoingMessage.getHeaderNames()`, `outgoingMessage.hasHeader()`,
+`outgoingMessage.removeHeader()`, `outgoingMessage.setHeader()`) for working
+with outgoing headers.
+
+*Note*: `outgoingMessage._headers` and `outgoingMessage._headerNames` were never
+documented as officially supported properties.
+
+
+### DEP0067: OutgoingMessage.prototype.\_renderHeaders
+
+Type: Documentation-only
+
+The `http` module `OutgoingMessage.prototype._renderHeaders()` API has been
+deprecated.
+
+*Note*: `OutgoingMessage.prototype._renderHeaders` was never documented as
+an officially supported API.
+
[alloc]: buffer.html#buffer_class_method_buffer_alloc_size_fill_encoding
[alloc_unsafe_size]: buffer.html#buffer_class_method_buffer_allocunsafe_size
[`Buffer.allocUnsafeSlow(size)`]: buffer.html#buffer_class_method_buffer_allocunsafeslow_size
diff --git a/lib/_http_client.js b/lib/_http_client.js
index e8285fe81012c7..babe772281909f 100644
--- a/lib/_http_client.js
+++ b/lib/_http_client.js
@@ -14,6 +14,7 @@ const OutgoingMessage = require('_http_outgoing').OutgoingMessage;
const Agent = require('_http_agent');
const Buffer = require('buffer').Buffer;
const urlToOptions = require('internal/url').urlToOptions;
+const outHeadersKey = require('internal/http').outHeadersKey;
// The actual list of disallowed characters in regexp form is more like:
// /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
@@ -182,7 +183,7 @@ function ClientRequest(options, cb) {
'client');
}
self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
- self._headers);
+ self[outHeadersKey]);
}
this._ended = false;
@@ -278,7 +279,7 @@ ClientRequest.prototype._implicitHeader = function _implicitHeader() {
throw new Error('Can\'t render headers after they are sent to the client');
}
this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
- this._headers);
+ this[outHeadersKey]);
};
ClientRequest.prototype.abort = function abort() {
diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js
index a095b64230e98e..373476a3c43d52 100644
--- a/lib/_http_outgoing.js
+++ b/lib/_http_outgoing.js
@@ -9,6 +9,8 @@ const Buffer = require('buffer').Buffer;
const common = require('_http_common');
const checkIsHttpToken = common._checkIsHttpToken;
const checkInvalidHeaderChar = common._checkInvalidHeaderChar;
+const outHeadersKey = require('internal/http').outHeadersKey;
+const StorageObject = require('internal/querystring').StorageObject;
const CRLF = common.CRLF;
const debug = common.debug;
@@ -18,10 +20,6 @@ var RE_FIELDS = new RegExp('^(?:Connection|Transfer-Encoding|Content-Length|' +
var RE_CONN_VALUES = /(?:^|\W)close|upgrade(?:$|\W)/ig;
var RE_TE_CHUNKED = common.chunkExpression;
-// Used to store headers returned by getHeaders()
-function OutgoingHeaders() {}
-OutgoingHeaders.prototype = Object.create(null);
-
var dateCache;
function utcDate() {
if (!dateCache) {
@@ -74,13 +72,82 @@ function OutgoingMessage() {
this.socket = null;
this.connection = null;
this._header = null;
- this._headers = null;
+ this[outHeadersKey] = null;
this._onPendingData = null;
}
util.inherits(OutgoingMessage, Stream);
+Object.defineProperty(OutgoingMessage.prototype, '_headers', {
+ get: function() {
+ return this.getHeaders();
+ },
+ set: function(val) {
+ if (val == null) {
+ this[outHeadersKey] = null;
+ } else if (typeof val === 'object') {
+ const headers = this[outHeadersKey] = {};
+ const keys = Object.keys(val);
+ for (var i = 0; i < keys.length; ++i) {
+ const name = keys[i];
+ headers[name.toLowerCase()] = [name, val[name]];
+ }
+ }
+ }
+});
+
+Object.defineProperty(OutgoingMessage.prototype, '_headerNames', {
+ get: function() {
+ const headers = this[outHeadersKey];
+ if (headers) {
+ const out = new StorageObject();
+ const keys = Object.keys(headers);
+ for (var i = 0; i < keys.length; ++i) {
+ const key = keys[i];
+ const val = headers[key][0];
+ out[key] = val;
+ }
+ return out;
+ } else {
+ return headers;
+ }
+ },
+ set: function(val) {
+ if (typeof val === 'object' && val !== null) {
+ const headers = this[outHeadersKey];
+ if (!headers)
+ return;
+ const keys = Object.keys(val);
+ for (var i = 0; i < keys.length; ++i) {
+ const header = headers[keys[i]];
+ if (header)
+ header[0] = val[keys[i]];
+ }
+ }
+ }
+});
+
+
+OutgoingMessage.prototype._renderHeaders = function _renderHeaders() {
+ if (this._header) {
+ throw new Error('Can\'t render headers after they are sent to the client');
+ }
+
+ var headersMap = this[outHeadersKey];
+ if (!headersMap) return {};
+
+ var headers = {};
+ var keys = Object.keys(headersMap);
+
+ for (var i = 0, l = keys.length; i < l; i++) {
+ var key = keys[i];
+ headers[headersMap[key][0]] = headersMap[key][1];
+ }
+ return headers;
+};
+
+
exports.OutgoingMessage = OutgoingMessage;
@@ -201,7 +268,7 @@ function _storeHeader(firstLine, headers) {
var value;
var i;
var j;
- if (headers === this._headers) {
+ if (headers === this[outHeadersKey]) {
for (key in headers) {
var entry = headers[key];
field = entry[0];
@@ -393,11 +460,11 @@ function validateHeader(msg, name, value) {
OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
validateHeader(this, name, value);
- if (!this._headers)
- this._headers = {};
+ if (!this[outHeadersKey])
+ this[outHeadersKey] = {};
const key = name.toLowerCase();
- this._headers[key] = [name, value];
+ this[outHeadersKey][key] = [name, value];
switch (key.length) {
case 10:
@@ -421,9 +488,9 @@ OutgoingMessage.prototype.getHeader = function getHeader(name) {
throw new TypeError('"name" argument must be a string');
}
- if (!this._headers) return;
+ if (!this[outHeadersKey]) return;
- var entry = this._headers[name.toLowerCase()];
+ var entry = this[outHeadersKey][name.toLowerCase()];
if (!entry)
return;
return entry[1];
@@ -432,14 +499,14 @@ OutgoingMessage.prototype.getHeader = function getHeader(name) {
// Returns an array of the names of the current outgoing headers.
OutgoingMessage.prototype.getHeaderNames = function getHeaderNames() {
- return (this._headers ? Object.keys(this._headers) : []);
+ return (this[outHeadersKey] ? Object.keys(this[outHeadersKey]) : []);
};
// Returns a shallow copy of the current outgoing headers.
OutgoingMessage.prototype.getHeaders = function getHeaders() {
- const headers = this._headers;
- const ret = new OutgoingHeaders();
+ const headers = this[outHeadersKey];
+ const ret = new StorageObject();
if (headers) {
const keys = Object.keys(headers);
for (var i = 0; i < keys.length; ++i) {
@@ -457,7 +524,7 @@ OutgoingMessage.prototype.hasHeader = function hasHeader(name) {
throw new TypeError('"name" argument must be a string');
}
- return !!(this._headers && this._headers[name.toLowerCase()]);
+ return !!(this[outHeadersKey] && this[outHeadersKey][name.toLowerCase()]);
};
@@ -491,8 +558,8 @@ OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
break;
}
- if (this._headers) {
- delete this._headers[key];
+ if (this[outHeadersKey]) {
+ delete this[outHeadersKey][key];
}
};
diff --git a/lib/_http_server.js b/lib/_http_server.js
index 082920839459e9..32da9cfe95ed69 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -13,6 +13,7 @@ const continueExpression = common.continueExpression;
const chunkExpression = common.chunkExpression;
const httpSocketSetup = common.httpSocketSetup;
const OutgoingMessage = require('_http_outgoing').OutgoingMessage;
+const outHeadersKey = require('internal/http').outHeadersKey;
const STATUS_CODES = exports.STATUS_CODES = {
100: 'Continue',
@@ -179,7 +180,7 @@ function writeHead(statusCode, reason, obj) {
this.statusCode = statusCode;
var headers;
- if (this._headers) {
+ if (this[outHeadersKey]) {
// Slow-case: when progressive API and header fields are passed.
var k;
if (obj) {
@@ -196,7 +197,7 @@ function writeHead(statusCode, reason, obj) {
}
}
// only progressive api is used
- headers = this._headers;
+ headers = this[outHeadersKey];
} else {
// only writeHead() called
headers = obj;
diff --git a/lib/internal/http.js b/lib/internal/http.js
new file mode 100644
index 00000000000000..1ba68a41a5692b
--- /dev/null
+++ b/lib/internal/http.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+ outHeadersKey: Symbol('outHeadersKey')
+};
diff --git a/node.gyp b/node.gyp
index 673a1d10effaa8..637a1934287841 100644
--- a/node.gyp
+++ b/node.gyp
@@ -85,6 +85,7 @@
'lib/internal/errors.js',
'lib/internal/freelist.js',
'lib/internal/fs.js',
+ 'lib/internal/http.js',
'lib/internal/linkedlist.js',
'lib/internal/net.js',
'lib/internal/module.js',