From ed381f86f7c8fe088c31493f5939026bb322eec2 Mon Sep 17 00:00:00 2001 From: erdinccurebal Date: Sat, 21 Feb 2026 22:06:59 +0300 Subject: [PATCH] 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: https://github.com/nodejs/node/issues/28438 --- lib/_http_outgoing.js | 6 ++++ ...head-response-content-length-keep-alive.js | 35 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 test/parallel/test-http-head-response-content-length-keep-alive.js diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index 24aae1caca69d3..cf208b9177e2d7 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -493,6 +493,12 @@ function _storeHeader(firstLine, headers) { if (!this._hasBody) { // Make sure we don't end the 0\r\n\r\n at the end of the message. this.chunkedEncoding = false; + // For HEAD responses, include Content-Length if known so that + // the client can determine message boundaries for keep-alive. + if (!this._removedContLen && + typeof this._contentLength === 'number') { + header += 'Content-Length: ' + this._contentLength + '\r\n'; + } } else if (!this.useChunkedEncodingByDefault) { this._last = true; } else if (!state.trailer && diff --git a/test/parallel/test-http-head-response-content-length-keep-alive.js b/test/parallel/test-http-head-response-content-length-keep-alive.js new file mode 100644 index 00000000000000..7a81190221d817 --- /dev/null +++ b/test/parallel/test-http-head-response-content-length-keep-alive.js @@ -0,0 +1,35 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +// This test ensures that HEAD responses include a Content-Length header +// when the server calls res.end() without explicit headers, so that +// HTTP keep-alive works correctly for HEAD requests. +// Ref: https://github.com/nodejs/node/issues/28438 + +const server = http.createServer(common.mustCall(function(req, res) { + res.end(); +}, 2)); + +server.listen(0, common.mustCall(function() { + const port = this.address().port; + const agent = new http.Agent({ keepAlive: true }); + + // First: verify GET response includes Content-Length + const getReq = http.request({ port, method: 'GET', agent }, common.mustCall(function(res) { + assert.strictEqual(res.headers['content-length'], '0'); + + // Second: verify HEAD response also includes Content-Length + const headReq = http.request({ port, method: 'HEAD', agent }, common.mustCall(function(res) { + assert.strictEqual(res.headers['content-length'], '0', + 'HEAD response should include Content-Length for keep-alive'); + agent.destroy(); + server.close(); + })); + headReq.end(); + + res.resume(); + })); + getReq.end(); +}));