Skip to content

Commit ed381f8

Browse files
committed
http: include Content-Length in HEAD responses for keep-alive
When an HTTP server responds to a HEAD request with res.end(), the response does not include a Content-Length header by default. This causes the HTTP parser on the client side to be unable to determine message boundaries, which breaks keep-alive connections for HEAD requests. GET responses already include Content-Length: 0 by default when res.end() is called without data. This change applies the same behavior to HEAD responses (and other bodyless responses) by adding Content-Length when known and not explicitly removed. Fixes: #28438
1 parent c9acf34 commit ed381f8

2 files changed

Lines changed: 41 additions & 0 deletions

File tree

lib/_http_outgoing.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,12 @@ function _storeHeader(firstLine, headers) {
493493
if (!this._hasBody) {
494494
// Make sure we don't end the 0\r\n\r\n at the end of the message.
495495
this.chunkedEncoding = false;
496+
// For HEAD responses, include Content-Length if known so that
497+
// the client can determine message boundaries for keep-alive.
498+
if (!this._removedContLen &&
499+
typeof this._contentLength === 'number') {
500+
header += 'Content-Length: ' + this._contentLength + '\r\n';
501+
}
496502
} else if (!this.useChunkedEncodingByDefault) {
497503
this._last = true;
498504
} else if (!state.trailer &&
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const http = require('http');
5+
6+
// This test ensures that HEAD responses include a Content-Length header
7+
// when the server calls res.end() without explicit headers, so that
8+
// HTTP keep-alive works correctly for HEAD requests.
9+
// Ref: https://github.com/nodejs/node/issues/28438
10+
11+
const server = http.createServer(common.mustCall(function(req, res) {
12+
res.end();
13+
}, 2));
14+
15+
server.listen(0, common.mustCall(function() {
16+
const port = this.address().port;
17+
const agent = new http.Agent({ keepAlive: true });
18+
19+
// First: verify GET response includes Content-Length
20+
const getReq = http.request({ port, method: 'GET', agent }, common.mustCall(function(res) {
21+
assert.strictEqual(res.headers['content-length'], '0');
22+
23+
// Second: verify HEAD response also includes Content-Length
24+
const headReq = http.request({ port, method: 'HEAD', agent }, common.mustCall(function(res) {
25+
assert.strictEqual(res.headers['content-length'], '0',
26+
'HEAD response should include Content-Length for keep-alive');
27+
agent.destroy();
28+
server.close();
29+
}));
30+
headReq.end();
31+
32+
res.resume();
33+
}));
34+
getReq.end();
35+
}));

0 commit comments

Comments
 (0)