http: send Content-Length when possible
This changes the behavior for http to send send a Content-Length header instead of using chunked encoding when we know the size of the body when sending the headers. Fixes: https://github.com/iojs/io.js/issues/1044 PR-URL: https://github.com/iojs/io.js/pull/1062 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Rod Vagg <rod@vagg.org> Reviewed-By: Brian White <mscdex@mscdex.net>
This commit is contained in:
parent
1640dedb3b
commit
4874182065
@ -16,6 +16,7 @@ const closeExpression = /close/i;
|
|||||||
const contentLengthExpression = /^Content-Length$/i;
|
const contentLengthExpression = /^Content-Length$/i;
|
||||||
const dateExpression = /^Date$/i;
|
const dateExpression = /^Date$/i;
|
||||||
const expectExpression = /^Expect$/i;
|
const expectExpression = /^Expect$/i;
|
||||||
|
const trailerExpression = /^Trailer$/i;
|
||||||
|
|
||||||
const automaticHeaders = {
|
const automaticHeaders = {
|
||||||
connection: true,
|
connection: true,
|
||||||
@ -56,6 +57,7 @@ function OutgoingMessage() {
|
|||||||
this.sendDate = false;
|
this.sendDate = false;
|
||||||
this._removedHeader = {};
|
this._removedHeader = {};
|
||||||
|
|
||||||
|
this._contentLength = null;
|
||||||
this._hasBody = true;
|
this._hasBody = true;
|
||||||
this._trailer = '';
|
this._trailer = '';
|
||||||
|
|
||||||
@ -185,6 +187,7 @@ OutgoingMessage.prototype._storeHeader = function(firstLine, headers) {
|
|||||||
sentTransferEncodingHeader: false,
|
sentTransferEncodingHeader: false,
|
||||||
sentDateHeader: false,
|
sentDateHeader: false,
|
||||||
sentExpect: false,
|
sentExpect: false,
|
||||||
|
sentTrailer: false,
|
||||||
messageHeader: firstLine
|
messageHeader: firstLine
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -257,16 +260,26 @@ OutgoingMessage.prototype._storeHeader = function(firstLine, headers) {
|
|||||||
|
|
||||||
if (state.sentContentLengthHeader === false &&
|
if (state.sentContentLengthHeader === false &&
|
||||||
state.sentTransferEncodingHeader === false) {
|
state.sentTransferEncodingHeader === false) {
|
||||||
if (this._hasBody && !this._removedHeader['transfer-encoding']) {
|
if (!this._hasBody) {
|
||||||
if (this.useChunkedEncodingByDefault) {
|
// Make sure we don't end the 0\r\n\r\n at the end of the message.
|
||||||
|
this.chunkedEncoding = false;
|
||||||
|
} else if (!this.useChunkedEncodingByDefault) {
|
||||||
|
this._last = true;
|
||||||
|
} else {
|
||||||
|
if (!state.sentTrailer &&
|
||||||
|
!this._removedHeader['content-length'] &&
|
||||||
|
typeof this._contentLength === 'number') {
|
||||||
|
state.messageHeader += 'Content-Length: ' + this._contentLength +
|
||||||
|
'\r\n';
|
||||||
|
} else if (!this._removedHeader['transfer-encoding']) {
|
||||||
state.messageHeader += 'Transfer-Encoding: chunked\r\n';
|
state.messageHeader += 'Transfer-Encoding: chunked\r\n';
|
||||||
this.chunkedEncoding = true;
|
this.chunkedEncoding = true;
|
||||||
} else {
|
} else {
|
||||||
this._last = true;
|
// We should only be able to get here if both Content-Length and
|
||||||
|
// Transfer-Encoding are removed by the user.
|
||||||
|
// See: test/parallel/test-http-remove-header-stays-removed.js
|
||||||
|
debug('Both Content-Length and Transfer-Encoding are removed');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Make sure we don't end the 0\r\n\r\n at the end of the message.
|
|
||||||
this.chunkedEncoding = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,6 +317,8 @@ function storeHeader(self, state, field, value) {
|
|||||||
state.sentDateHeader = true;
|
state.sentDateHeader = true;
|
||||||
} else if (expectExpression.test(field)) {
|
} else if (expectExpression.test(field)) {
|
||||||
state.sentExpect = true;
|
state.sentExpect = true;
|
||||||
|
} else if (trailerExpression.test(field)) {
|
||||||
|
state.sentTrailer = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,6 +524,14 @@ OutgoingMessage.prototype.end = function(data, encoding, callback) {
|
|||||||
this.once('finish', callback);
|
this.once('finish', callback);
|
||||||
|
|
||||||
if (!this._header) {
|
if (!this._header) {
|
||||||
|
if (data) {
|
||||||
|
if (typeof data === 'string')
|
||||||
|
this._contentLength = Buffer.byteLength(data, encoding);
|
||||||
|
else
|
||||||
|
this._contentLength = data.length;
|
||||||
|
} else {
|
||||||
|
this._contentLength = 0;
|
||||||
|
}
|
||||||
this._implicitHeader();
|
this._implicitHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ var http = require('http');
|
|||||||
var server = http.createServer(function(req, res) {
|
var server = http.createServer(function(req, res) {
|
||||||
res.setHeader('X-Date', 'foo');
|
res.setHeader('X-Date', 'foo');
|
||||||
res.setHeader('X-Connection', 'bar');
|
res.setHeader('X-Connection', 'bar');
|
||||||
res.setHeader('X-Transfer-Encoding', 'baz');
|
res.setHeader('X-Content-Length', 'baz');
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
server.listen(common.PORT);
|
server.listen(common.PORT);
|
||||||
@ -20,10 +20,10 @@ server.on('listening', function() {
|
|||||||
assert.equal(res.statusCode, 200);
|
assert.equal(res.statusCode, 200);
|
||||||
assert.equal(res.headers['x-date'], 'foo');
|
assert.equal(res.headers['x-date'], 'foo');
|
||||||
assert.equal(res.headers['x-connection'], 'bar');
|
assert.equal(res.headers['x-connection'], 'bar');
|
||||||
assert.equal(res.headers['x-transfer-encoding'], 'baz');
|
assert.equal(res.headers['x-content-length'], 'baz');
|
||||||
assert(res.headers['date']);
|
assert(res.headers['date']);
|
||||||
assert.equal(res.headers['connection'], 'keep-alive');
|
assert.equal(res.headers['connection'], 'keep-alive');
|
||||||
assert.equal(res.headers['transfer-encoding'], 'chunked');
|
assert.equal(res.headers['content-length'], '0');
|
||||||
server.close();
|
server.close();
|
||||||
agent.destroy();
|
agent.destroy();
|
||||||
});
|
});
|
||||||
|
@ -7,8 +7,8 @@ var expectedHeaders = {
|
|||||||
'GET': ['host', 'connection'],
|
'GET': ['host', 'connection'],
|
||||||
'HEAD': ['host', 'connection'],
|
'HEAD': ['host', 'connection'],
|
||||||
'OPTIONS': ['host', 'connection'],
|
'OPTIONS': ['host', 'connection'],
|
||||||
'POST': ['host', 'connection', 'transfer-encoding'],
|
'POST': ['host', 'connection', 'content-length'],
|
||||||
'PUT': ['host', 'connection', 'transfer-encoding']
|
'PUT': ['host', 'connection', 'content-length']
|
||||||
};
|
};
|
||||||
|
|
||||||
var expectedMethods = Object.keys(expectedHeaders);
|
var expectedMethods = Object.keys(expectedHeaders);
|
||||||
|
89
test/parallel/test-http-content-length.js
Normal file
89
test/parallel/test-http-content-length.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
var common = require('../common');
|
||||||
|
var assert = require('assert');
|
||||||
|
var http = require('http');
|
||||||
|
|
||||||
|
var expectedHeadersMultipleWrites = {
|
||||||
|
'connection': 'close',
|
||||||
|
'transfer-encoding': 'chunked',
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedHeadersEndWithData = {
|
||||||
|
'connection': 'close',
|
||||||
|
'content-length': 'hello world'.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedHeadersEndNoData = {
|
||||||
|
'connection': 'close',
|
||||||
|
'content-length': '0',
|
||||||
|
};
|
||||||
|
|
||||||
|
var receivedRequests = 0;
|
||||||
|
var totalRequests = 2;
|
||||||
|
|
||||||
|
var server = http.createServer(function(req, res) {
|
||||||
|
res.removeHeader('Date');
|
||||||
|
|
||||||
|
switch (req.url.substr(1)) {
|
||||||
|
case 'multiple-writes':
|
||||||
|
assert.deepEqual(req.headers, expectedHeadersMultipleWrites);
|
||||||
|
res.write('hello');
|
||||||
|
res.end('world');
|
||||||
|
break;
|
||||||
|
case 'end-with-data':
|
||||||
|
assert.deepEqual(req.headers, expectedHeadersEndWithData);
|
||||||
|
res.end('hello world');
|
||||||
|
break;
|
||||||
|
case 'empty':
|
||||||
|
assert.deepEqual(req.headers, expectedHeadersEndNoData);
|
||||||
|
res.end();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Unreachable');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedRequests++;
|
||||||
|
if (totalRequests === receivedRequests) server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(common.PORT, function() {
|
||||||
|
var req;
|
||||||
|
|
||||||
|
req = http.request({
|
||||||
|
port: common.PORT,
|
||||||
|
method: 'POST',
|
||||||
|
path: '/multiple-writes'
|
||||||
|
});
|
||||||
|
req.removeHeader('Date');
|
||||||
|
req.removeHeader('Host');
|
||||||
|
req.write('hello ');
|
||||||
|
req.end('world');
|
||||||
|
req.on('response', function(res) {
|
||||||
|
assert.deepEqual(res.headers, expectedHeadersMultipleWrites);
|
||||||
|
});
|
||||||
|
|
||||||
|
req = http.request({
|
||||||
|
port: common.PORT,
|
||||||
|
method: 'POST',
|
||||||
|
path: '/end-with-data'
|
||||||
|
});
|
||||||
|
req.removeHeader('Date');
|
||||||
|
req.removeHeader('Host');
|
||||||
|
req.end('hello world');
|
||||||
|
req.on('response', function(res) {
|
||||||
|
assert.deepEqual(res.headers, expectedHeadersEndWithData);
|
||||||
|
});
|
||||||
|
|
||||||
|
req = http.request({
|
||||||
|
port: common.PORT,
|
||||||
|
method: 'POST',
|
||||||
|
path: '/empty'
|
||||||
|
});
|
||||||
|
req.removeHeader('Date');
|
||||||
|
req.removeHeader('Host');
|
||||||
|
req.end();
|
||||||
|
req.on('response', function(res) {
|
||||||
|
assert.deepEqual(res.headers, expectedHeadersEndNoData);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -44,6 +44,7 @@ http.createServer(function(req, res) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
req.resume();
|
req.resume();
|
||||||
|
res.setHeader('Trailer', 'x-foo');
|
||||||
res.addTrailers([
|
res.addTrailers([
|
||||||
['x-fOo', 'xOxOxOx'],
|
['x-fOo', 'xOxOxOx'],
|
||||||
['x-foO', 'OxOxOxO'],
|
['x-foO', 'OxOxOxO'],
|
||||||
@ -72,6 +73,8 @@ http.createServer(function(req, res) {
|
|||||||
req.end('y b a r');
|
req.end('y b a r');
|
||||||
req.on('response', function(res) {
|
req.on('response', function(res) {
|
||||||
var expectRawHeaders = [
|
var expectRawHeaders = [
|
||||||
|
'Trailer',
|
||||||
|
'x-foo',
|
||||||
'Date',
|
'Date',
|
||||||
null,
|
null,
|
||||||
'Connection',
|
'Connection',
|
||||||
@ -80,11 +83,12 @@ http.createServer(function(req, res) {
|
|||||||
'chunked'
|
'chunked'
|
||||||
];
|
];
|
||||||
var expectHeaders = {
|
var expectHeaders = {
|
||||||
|
trailer: 'x-foo',
|
||||||
date: null,
|
date: null,
|
||||||
connection: 'close',
|
connection: 'close',
|
||||||
'transfer-encoding': 'chunked'
|
'transfer-encoding': 'chunked'
|
||||||
};
|
};
|
||||||
res.rawHeaders[1] = null;
|
res.rawHeaders[3] = null;
|
||||||
res.headers.date = null;
|
res.headers.date = null;
|
||||||
assert.deepEqual(res.rawHeaders, expectRawHeaders);
|
assert.deepEqual(res.rawHeaders, expectRawHeaders);
|
||||||
assert.deepEqual(res.headers, expectHeaders);
|
assert.deepEqual(res.headers, expectHeaders);
|
||||||
|
@ -8,6 +8,7 @@ var server = http.createServer(function(request, response) {
|
|||||||
// to the output:
|
// to the output:
|
||||||
response.removeHeader('connection');
|
response.removeHeader('connection');
|
||||||
response.removeHeader('transfer-encoding');
|
response.removeHeader('transfer-encoding');
|
||||||
|
response.removeHeader('content-length');
|
||||||
|
|
||||||
// make sure that removing and then setting still works:
|
// make sure that removing and then setting still works:
|
||||||
response.removeHeader('date');
|
response.removeHeader('date');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user