http: optimize header storage and matching
This commit implements two optimizations when working with headers: * Avoid having to explicitly "render" headers and separately store the original casing for header names. * Match special header names using a single regular expression instead of testing one regular expression per header name. PR-URL: https://github.com/nodejs/node/pull/10558 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Evan Lucas <evanlucas@me.com>
This commit is contained in:
parent
ec8910bcea
commit
c00e4adbb4
@ -131,8 +131,12 @@ function ClientRequest(options, cb) {
|
|||||||
self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
|
self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
|
||||||
options.headers);
|
options.headers);
|
||||||
} else if (self.getHeader('expect')) {
|
} else if (self.getHeader('expect')) {
|
||||||
|
if (self._header) {
|
||||||
|
throw new Error('Can\'t render headers after they are sent to the ' +
|
||||||
|
'client');
|
||||||
|
}
|
||||||
self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
|
self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
|
||||||
self._renderHeaders());
|
self._headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._ended = false;
|
this._ended = false;
|
||||||
@ -224,8 +228,11 @@ ClientRequest.prototype._finish = function _finish() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ClientRequest.prototype._implicitHeader = function _implicitHeader() {
|
ClientRequest.prototype._implicitHeader = function _implicitHeader() {
|
||||||
|
if (this._header) {
|
||||||
|
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._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
|
||||||
this._renderHeaders());
|
this._headers);
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientRequest.prototype.abort = function abort() {
|
ClientRequest.prototype.abort = function abort() {
|
||||||
|
@ -14,8 +14,8 @@ const debug = require('util').debuglog('http');
|
|||||||
exports.debug = debug;
|
exports.debug = debug;
|
||||||
|
|
||||||
exports.CRLF = '\r\n';
|
exports.CRLF = '\r\n';
|
||||||
exports.chunkExpression = /chunk/i;
|
exports.chunkExpression = /(?:^|\W)chunked(?:$|\W)/i;
|
||||||
exports.continueExpression = /100-continue/i;
|
exports.continueExpression = /(?:^|\W)100-continue(?:$|\W)/i;
|
||||||
exports.methods = methods;
|
exports.methods = methods;
|
||||||
|
|
||||||
const kOnHeaders = HTTPParser.kOnHeaders | 0;
|
const kOnHeaders = HTTPParser.kOnHeaders | 0;
|
||||||
|
@ -11,18 +11,8 @@ const checkIsHttpToken = common._checkIsHttpToken;
|
|||||||
const checkInvalidHeaderChar = common._checkInvalidHeaderChar;
|
const checkInvalidHeaderChar = common._checkInvalidHeaderChar;
|
||||||
|
|
||||||
const CRLF = common.CRLF;
|
const CRLF = common.CRLF;
|
||||||
const trfrEncChunkExpression = common.chunkExpression;
|
|
||||||
const debug = common.debug;
|
const debug = common.debug;
|
||||||
|
|
||||||
const upgradeExpression = /^Upgrade$/i;
|
|
||||||
const transferEncodingExpression = /^Transfer-Encoding$/i;
|
|
||||||
const contentLengthExpression = /^Content-Length$/i;
|
|
||||||
const dateExpression = /^Date$/i;
|
|
||||||
const expectExpression = /^Expect$/i;
|
|
||||||
const trailerExpression = /^Trailer$/i;
|
|
||||||
const connectionExpression = /^Connection$/i;
|
|
||||||
const connCloseExpression = /(^|\W)close(\W|$)/i;
|
|
||||||
const connUpgradeExpression = /(^|\W)upgrade(\W|$)/i;
|
|
||||||
|
|
||||||
const automaticHeaders = {
|
const automaticHeaders = {
|
||||||
connection: true,
|
connection: true,
|
||||||
@ -31,6 +21,10 @@ const automaticHeaders = {
|
|||||||
date: true
|
date: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var RE_FIELDS = new RegExp('^(?:Connection|Transfer-Encoding|Content-Length|' +
|
||||||
|
'Date|Expect|Trailer|Upgrade)$', 'i');
|
||||||
|
var RE_CONN_VALUES = /(?:^|\W)close|upgrade(?:$|\W)/ig;
|
||||||
|
var RE_TE_CHUNKED = common.chunkExpression;
|
||||||
|
|
||||||
var dateCache;
|
var dateCache;
|
||||||
function utcDate() {
|
function utcDate() {
|
||||||
@ -83,7 +77,6 @@ function OutgoingMessage() {
|
|||||||
this.connection = null;
|
this.connection = null;
|
||||||
this._header = null;
|
this._header = null;
|
||||||
this._headers = null;
|
this._headers = null;
|
||||||
this._headerNames = {};
|
|
||||||
|
|
||||||
this._onPendingData = null;
|
this._onPendingData = null;
|
||||||
}
|
}
|
||||||
@ -198,57 +191,72 @@ function _storeHeader(firstLine, headers) {
|
|||||||
// firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
|
// firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
|
||||||
// in the case of response it is: 'HTTP/1.1 200 OK\r\n'
|
// in the case of response it is: 'HTTP/1.1 200 OK\r\n'
|
||||||
var state = {
|
var state = {
|
||||||
sentConnectionHeader: false,
|
connection: false,
|
||||||
sentConnectionUpgrade: false,
|
connUpgrade: false,
|
||||||
sentContentLengthHeader: false,
|
contLen: false,
|
||||||
sentTransferEncodingHeader: false,
|
te: false,
|
||||||
sentDateHeader: false,
|
date: false,
|
||||||
sentExpect: false,
|
expect: false,
|
||||||
sentTrailer: false,
|
trailer: false,
|
||||||
sentUpgrade: false,
|
upgrade: false,
|
||||||
messageHeader: firstLine
|
header: firstLine
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var field;
|
||||||
|
var key;
|
||||||
|
var value;
|
||||||
var i;
|
var i;
|
||||||
var j;
|
var j;
|
||||||
var field;
|
if (headers === this._headers) {
|
||||||
var value;
|
for (key in headers) {
|
||||||
if (headers instanceof Array) {
|
var entry = headers[key];
|
||||||
for (i = 0; i < headers.length; ++i) {
|
field = entry[0];
|
||||||
|
value = entry[1];
|
||||||
|
|
||||||
|
if (value instanceof Array) {
|
||||||
|
for (j = 0; j < value.length; j++) {
|
||||||
|
storeHeader(this, state, field, value[j], false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
storeHeader(this, state, field, value, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (headers instanceof Array) {
|
||||||
|
for (i = 0; i < headers.length; i++) {
|
||||||
field = headers[i][0];
|
field = headers[i][0];
|
||||||
value = headers[i][1];
|
value = headers[i][1];
|
||||||
|
|
||||||
if (value instanceof Array) {
|
if (value instanceof Array) {
|
||||||
for (j = 0; j < value.length; j++) {
|
for (j = 0; j < value.length; j++) {
|
||||||
storeHeader(this, state, field, value[j]);
|
storeHeader(this, state, field, value[j], true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
storeHeader(this, state, field, value);
|
storeHeader(this, state, field, value, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (headers) {
|
} else if (headers) {
|
||||||
var keys = Object.keys(headers);
|
var keys = Object.keys(headers);
|
||||||
for (i = 0; i < keys.length; ++i) {
|
for (i = 0; i < keys.length; i++) {
|
||||||
field = keys[i];
|
field = keys[i];
|
||||||
value = headers[field];
|
value = headers[field];
|
||||||
|
|
||||||
if (value instanceof Array) {
|
if (value instanceof Array) {
|
||||||
for (j = 0; j < value.length; j++) {
|
for (j = 0; j < value.length; j++) {
|
||||||
storeHeader(this, state, field, value[j]);
|
storeHeader(this, state, field, value[j], true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
storeHeader(this, state, field, value);
|
storeHeader(this, state, field, value, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we upgrading the connection?
|
// Are we upgrading the connection?
|
||||||
if (state.sentConnectionUpgrade && state.sentUpgrade)
|
if (state.connUpgrade && state.upgrade)
|
||||||
this.upgrading = true;
|
this.upgrading = true;
|
||||||
|
|
||||||
// Date header
|
// Date header
|
||||||
if (this.sendDate && !state.sentDateHeader) {
|
if (this.sendDate && !state.date) {
|
||||||
state.messageHeader += 'Date: ' + utcDate() + CRLF;
|
state.header += 'Date: ' + utcDate() + CRLF;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force the connection to close when the response is a 204 No Content or
|
// Force the connection to close when the response is a 204 No Content or
|
||||||
@ -274,33 +282,30 @@ function _storeHeader(firstLine, headers) {
|
|||||||
if (this._removedHeader.connection) {
|
if (this._removedHeader.connection) {
|
||||||
this._last = true;
|
this._last = true;
|
||||||
this.shouldKeepAlive = false;
|
this.shouldKeepAlive = false;
|
||||||
} else if (!state.sentConnectionHeader) {
|
} else if (!state.connection) {
|
||||||
var shouldSendKeepAlive = this.shouldKeepAlive &&
|
var shouldSendKeepAlive = this.shouldKeepAlive &&
|
||||||
(state.sentContentLengthHeader ||
|
(state.contLen || this.useChunkedEncodingByDefault || this.agent);
|
||||||
this.useChunkedEncodingByDefault ||
|
|
||||||
this.agent);
|
|
||||||
if (shouldSendKeepAlive) {
|
if (shouldSendKeepAlive) {
|
||||||
state.messageHeader += 'Connection: keep-alive\r\n';
|
state.header += 'Connection: keep-alive\r\n';
|
||||||
} else {
|
} else {
|
||||||
this._last = true;
|
this._last = true;
|
||||||
state.messageHeader += 'Connection: close\r\n';
|
state.header += 'Connection: close\r\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.sentContentLengthHeader && !state.sentTransferEncodingHeader) {
|
if (!state.contLen && !state.te) {
|
||||||
if (!this._hasBody) {
|
if (!this._hasBody) {
|
||||||
// Make sure we don't end the 0\r\n\r\n at the end of the message.
|
// Make sure we don't end the 0\r\n\r\n at the end of the message.
|
||||||
this.chunkedEncoding = false;
|
this.chunkedEncoding = false;
|
||||||
} else if (!this.useChunkedEncodingByDefault) {
|
} else if (!this.useChunkedEncodingByDefault) {
|
||||||
this._last = true;
|
this._last = true;
|
||||||
} else {
|
} else {
|
||||||
if (!state.sentTrailer &&
|
|
||||||
!this._removedHeader['content-length'] &&
|
!this._removedHeader['content-length'] &&
|
||||||
|
if (!state.trailer &&
|
||||||
typeof this._contentLength === 'number') {
|
typeof this._contentLength === 'number') {
|
||||||
state.messageHeader += 'Content-Length: ' + this._contentLength +
|
|
||||||
'\r\n';
|
|
||||||
} else if (!this._removedHeader['transfer-encoding']) {
|
} else if (!this._removedHeader['transfer-encoding']) {
|
||||||
state.messageHeader += 'Transfer-Encoding: chunked\r\n';
|
state.header += 'Content-Length: ' + this._contentLength + CRLF;
|
||||||
|
state.header += 'Transfer-Encoding: chunked\r\n';
|
||||||
this.chunkedEncoding = true;
|
this.chunkedEncoding = true;
|
||||||
} else {
|
} else {
|
||||||
// We should only be able to get here if both Content-Length and
|
// We should only be able to get here if both Content-Length and
|
||||||
@ -311,70 +316,94 @@ function _storeHeader(firstLine, headers) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._header = state.messageHeader + CRLF;
|
this._header = state.header + CRLF;
|
||||||
this._headerSent = false;
|
this._headerSent = false;
|
||||||
|
|
||||||
// wait until the first body chunk, or close(), is sent to flush,
|
// wait until the first body chunk, or close(), is sent to flush,
|
||||||
// UNLESS we're sending Expect: 100-continue.
|
// UNLESS we're sending Expect: 100-continue.
|
||||||
if (state.sentExpect) this._send('');
|
if (state.expect) this._send('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function storeHeader(self, state, field, value) {
|
function storeHeader(self, state, field, value, validate) {
|
||||||
if (!checkIsHttpToken(field)) {
|
if (validate) {
|
||||||
throw new TypeError(
|
if (!checkIsHttpToken(field)) {
|
||||||
'Header name must be a valid HTTP Token ["' + field + '"]');
|
throw new TypeError(
|
||||||
}
|
'Header name must be a valid HTTP Token ["' + field + '"]');
|
||||||
if (checkInvalidHeaderChar(value)) {
|
|
||||||
debug('Header "%s" contains invalid characters', field);
|
|
||||||
throw new TypeError('The header content contains invalid characters');
|
|
||||||
}
|
|
||||||
state.messageHeader += field + ': ' + escapeHeaderValue(value) + CRLF;
|
|
||||||
|
|
||||||
if (connectionExpression.test(field)) {
|
|
||||||
state.sentConnectionHeader = true;
|
|
||||||
if (connCloseExpression.test(value)) {
|
|
||||||
self._last = true;
|
|
||||||
} else {
|
|
||||||
self.shouldKeepAlive = true;
|
|
||||||
}
|
}
|
||||||
if (connUpgradeExpression.test(value))
|
if (value === undefined) {
|
||||||
state.sentConnectionUpgrade = true;
|
throw new Error('Header "%s" value must not be undefined', field);
|
||||||
} else if (transferEncodingExpression.test(field)) {
|
} else if (checkInvalidHeaderChar(value)) {
|
||||||
state.sentTransferEncodingHeader = true;
|
debug('Header "%s" contains invalid characters', field);
|
||||||
if (trfrEncChunkExpression.test(value)) self.chunkedEncoding = true;
|
throw new TypeError('The header content contains invalid characters');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.header += field + ': ' + escapeHeaderValue(value) + CRLF;
|
||||||
|
matchHeader(self, state, field, value);
|
||||||
|
}
|
||||||
|
|
||||||
} else if (contentLengthExpression.test(field)) {
|
function matchConnValue(self, state, value) {
|
||||||
state.sentContentLengthHeader = true;
|
var sawClose = false;
|
||||||
} else if (dateExpression.test(field)) {
|
var m = RE_CONN_VALUES.exec(value);
|
||||||
state.sentDateHeader = true;
|
while (m) {
|
||||||
} else if (expectExpression.test(field)) {
|
if (m[0].length === 5)
|
||||||
state.sentExpect = true;
|
sawClose = true;
|
||||||
} else if (trailerExpression.test(field)) {
|
else
|
||||||
state.sentTrailer = true;
|
state.connUpgrade = true;
|
||||||
} else if (upgradeExpression.test(field)) {
|
m = RE_CONN_VALUES.exec(value);
|
||||||
state.sentUpgrade = true;
|
}
|
||||||
|
if (sawClose)
|
||||||
|
self._last = true;
|
||||||
|
else
|
||||||
|
self.shouldKeepAlive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchHeader(self, state, field, value) {
|
||||||
|
var m = RE_FIELDS.exec(field);
|
||||||
|
if (!m)
|
||||||
|
return;
|
||||||
|
var len = m[0].length;
|
||||||
|
if (len === 10) {
|
||||||
|
state.connection = true;
|
||||||
|
matchConnValue(self, state, value);
|
||||||
|
} else if (len === 17) {
|
||||||
|
state.te = true;
|
||||||
|
if (RE_TE_CHUNKED.test(value)) self.chunkedEncoding = true;
|
||||||
|
} else if (len === 14) {
|
||||||
|
state.contLen = true;
|
||||||
|
} else if (len === 4) {
|
||||||
|
state.date = true;
|
||||||
|
} else if (len === 6) {
|
||||||
|
state.expect = true;
|
||||||
|
} else if (len === 7) {
|
||||||
|
var ch = m[0].charCodeAt(0);
|
||||||
|
if (ch === 85 || ch === 117)
|
||||||
|
state.upgrade = true;
|
||||||
|
else
|
||||||
|
state.trailer = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateHeader(msg, name, value) {
|
||||||
OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
|
|
||||||
if (!checkIsHttpToken(name))
|
if (!checkIsHttpToken(name))
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
'Header name must be a valid HTTP Token ["' + name + '"]');
|
'Header name must be a valid HTTP Token ["' + name + '"]');
|
||||||
if (value === undefined)
|
if (value === undefined)
|
||||||
throw new Error('"value" required in setHeader("' + name + '", value)');
|
throw new Error('"value" required in setHeader("' + name + '", value)');
|
||||||
if (this._header)
|
if (msg._header)
|
||||||
throw new Error('Can\'t set headers after they are sent.');
|
throw new Error('Can\'t set headers after they are sent.');
|
||||||
if (checkInvalidHeaderChar(value)) {
|
if (checkInvalidHeaderChar(value)) {
|
||||||
debug('Header "%s" contains invalid characters', name);
|
debug('Header "%s" contains invalid characters', name);
|
||||||
throw new TypeError('The header content contains invalid characters');
|
throw new TypeError('The header content contains invalid characters');
|
||||||
}
|
}
|
||||||
if (this._headers === null)
|
}
|
||||||
|
OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
|
||||||
|
validateHeader(this, name, value);
|
||||||
|
|
||||||
|
if (!this._headers)
|
||||||
this._headers = {};
|
this._headers = {};
|
||||||
|
|
||||||
var key = name.toLowerCase();
|
const key = name.toLowerCase();
|
||||||
this._headers[key] = value;
|
this._headers[key] = [name, value];
|
||||||
this._headerNames[key] = name;
|
|
||||||
|
|
||||||
if (automaticHeaders[key])
|
if (automaticHeaders[key])
|
||||||
this._removedHeader[key] = false;
|
this._removedHeader[key] = false;
|
||||||
@ -388,7 +417,10 @@ OutgoingMessage.prototype.getHeader = function getHeader(name) {
|
|||||||
|
|
||||||
if (!this._headers) return;
|
if (!this._headers) return;
|
||||||
|
|
||||||
return this._headers[name.toLowerCase()];
|
var entry = this._headers[name.toLowerCase()];
|
||||||
|
if (!entry)
|
||||||
|
return;
|
||||||
|
return entry[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -410,30 +442,10 @@ OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
|
|||||||
|
|
||||||
if (this._headers) {
|
if (this._headers) {
|
||||||
delete this._headers[key];
|
delete this._headers[key];
|
||||||
delete this._headerNames[key];
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
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._headers;
|
|
||||||
if (!headersMap) return {};
|
|
||||||
|
|
||||||
var headers = {};
|
|
||||||
var keys = Object.keys(headersMap);
|
|
||||||
var headerNames = this._headerNames;
|
|
||||||
|
|
||||||
for (var i = 0, l = keys.length; i < l; i++) {
|
|
||||||
var key = keys[i];
|
|
||||||
headers[headerNames[key]] = headersMap[key];
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
};
|
|
||||||
|
|
||||||
OutgoingMessage.prototype._implicitHeader = function _implicitHeader() {
|
OutgoingMessage.prototype._implicitHeader = function _implicitHeader() {
|
||||||
throw new Error('_implicitHeader() method is not implemented');
|
throw new Error('_implicitHeader() method is not implemented');
|
||||||
};
|
};
|
||||||
@ -492,6 +504,7 @@ OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
|
|||||||
this.connection.cork();
|
this.connection.cork();
|
||||||
process.nextTick(connectionCorkNT, this.connection);
|
process.nextTick(connectionCorkNT, this.connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._send(len.toString(16), 'latin1', null);
|
this._send(len.toString(16), 'latin1', null);
|
||||||
this._send(crlf_buf, null, null);
|
this._send(crlf_buf, null, null);
|
||||||
this._send(chunk, encoding, null);
|
this._send(chunk, encoding, null);
|
||||||
|
@ -161,7 +161,6 @@ ServerResponse.prototype._implicitHeader = function _implicitHeader() {
|
|||||||
|
|
||||||
ServerResponse.prototype.writeHead = writeHead;
|
ServerResponse.prototype.writeHead = writeHead;
|
||||||
function writeHead(statusCode, reason, obj) {
|
function writeHead(statusCode, reason, obj) {
|
||||||
var headers;
|
|
||||||
statusCode |= 0;
|
statusCode |= 0;
|
||||||
if (statusCode < 100 || statusCode > 999)
|
if (statusCode < 100 || statusCode > 999)
|
||||||
throw new RangeError(`Invalid status code: ${statusCode}`);
|
throw new RangeError(`Invalid status code: ${statusCode}`);
|
||||||
@ -177,17 +176,25 @@ function writeHead(statusCode, reason, obj) {
|
|||||||
}
|
}
|
||||||
this.statusCode = statusCode;
|
this.statusCode = statusCode;
|
||||||
|
|
||||||
|
var headers;
|
||||||
if (this._headers) {
|
if (this._headers) {
|
||||||
// Slow-case: when progressive API and header fields are passed.
|
// Slow-case: when progressive API and header fields are passed.
|
||||||
|
var k;
|
||||||
if (obj) {
|
if (obj) {
|
||||||
var keys = Object.keys(obj);
|
var keys = Object.keys(obj);
|
||||||
for (var i = 0; i < keys.length; i++) {
|
for (var i = 0; i < keys.length; i++) {
|
||||||
var k = keys[i];
|
k = keys[i];
|
||||||
if (k) this.setHeader(k, obj[k]);
|
if (k) this.setHeader(k, obj[k]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (k === undefined) {
|
||||||
|
if (this._header) {
|
||||||
|
throw new Error('Can\'t render headers after they are sent to the ' +
|
||||||
|
'client');
|
||||||
|
}
|
||||||
|
}
|
||||||
// only progressive api is used
|
// only progressive api is used
|
||||||
headers = this._renderHeaders();
|
headers = this._headers;
|
||||||
} else {
|
} else {
|
||||||
// only writeHead() called
|
// only writeHead() called
|
||||||
headers = obj;
|
headers = obj;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user