Skip to content

Comments

http: include Content-Length in HEAD responses for keep-alive#61919

Open
erdinccurebal wants to merge 1 commit intonodejs:mainfrom
erdinccurebal:fix/head-response-content-length
Open

http: include Content-Length in HEAD responses for keep-alive#61919
erdinccurebal wants to merge 1 commit intonodejs:mainfrom
erdinccurebal:fix/head-response-content-length

Conversation

@erdinccurebal
Copy link

Summary

HEAD responses from http.createServer do not include a Content-Length header by default when res.end() is called without explicit headers. This breaks HTTP keep-alive for HEAD requests because the client-side HTTP parser cannot determine message boundaries.

GET responses already include Content-Length: 0 by default. This change applies the same behavior to HEAD (and other bodyless) responses.

Before

GET / → headers: { 'content-length': '0', connection: 'keep-alive', ... }
HEAD / → headers: { connection: 'keep-alive', ... }  // missing content-length!

After

GET / → headers: { 'content-length': '0', connection: 'keep-alive', ... }
HEAD / → headers: { 'content-length': '0', connection: 'keep-alive', ... }

Changes

  • lib/_http_outgoing.js: In _storeHeader(), when the response has no body (!this._hasBody), also emit Content-Length header if the content length is known and was not explicitly removed by the user.
  • test/parallel/test-http-head-response-content-length-keep-alive.js: New test verifying that both GET and HEAD responses include Content-Length: 0 when res.end() is called.

Reproduction

const http = require('http');
const server = http.createServer((req, res) => res.end());
server.listen(2333);
const agent = new http.Agent({ keepAlive: true });
http.request({ port: 2333, method: 'HEAD', agent }, (res) => {
  console.log(res.headers); // no content-length → keep-alive broken
}).end();

Fixes: #28438

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: nodejs#28438
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run. labels Feb 21, 2026
Copy link
Member

@mertcanaltin mertcanaltin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Copy link
Member

@pimterry pimterry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking into this @erdinccurebal! Unfortunately, I don't think this is correct approach, sorry.

HEAD responses (spec here) are guaranteed (MUST NOT) to have no body, so there is no message framing ambiguity in the original bug. Problems with keep-alive are a client-side bug, or plausibly some other keep-alive problem on the server side, but the client doesn't need any more information or headers to know when the HEAD response ends (because it ends immediately, always).

On the flip side, I think this will potentially send wrong headers that will break some existing code.

The HEAD response headers are supposed to each exactly match the headers for an equivalent GET response, or be omitted. I.e. if the GET response content-length is non-zero, the HEAD response should be non-zero content-length, or content-length should be omitted entirely. They should only be content-length: 0 if the equivalent GET would also return that same header.

With this change, if there's any existing code that handles HEAD explicitly and skips generating the GET-equivalent body (just calls res.end()) from what I can see it seems we'd now start automatically sending content-length: 0 with that response, which is incorrect and would break things (e.g. anybody using HEAD to query a file download size before actually downloading).

I think we need to look at this on the client-side instead, and investigate parsing & keep-alive behaviour there. We do definitely need to be able to handle receiving no content-length header on HEAD responses because the spec explicitly lists that as something you might normally want to do.

We could still do this anyway, it's arguably nice UX in some cases, but I think it would be only for non-zero scenarios to avoid the bug described above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

http server respond with inappropriate default headers for HEAD method

6 participants