Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http: return Content-Length header for HEADs #56681

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/_http_outgoing.js
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ function _storeHeader(firstLine, headers) {
}

if (!state.contLen && !state.te) {
if (!this._hasBody) {
if (!this._hasBody && this.req.method !== 'HEAD') {
Copy link
Member

Choose a reason for hiding this comment

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

Why this?

Copy link
Author

Choose a reason for hiding this comment

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

If I'm understanding the logic correctly, the first if condition applies to those that should return neither of the Transfer-Encoding and Content-Length headers. While it's true for other requests that don't have a body, HEADs should match the 3rd condition (i.e. the !state.trailer && !this._removedContLen && typeof this._contentLength === 'number' one).

Copy link
Member

Choose a reason for hiding this comment

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

If you send a HEAD response with no body at all (e.g. because your code actually looks at the method, and skips it - not unusual I think) then what does this do?

It looks like it skips the correct case (this first one), skips the next 2 (useChunkedEncodingByDefault is true by default, _contentLength will be null) and instead sets a transfer-encoding: chunked header and potentially sends a 0\r\n\r\n end-of-chunk response in the body (as this comment suggests) even though HEAD shouldn't have a body.

I haven't actually tested this, but that's certainly how it looks. Either way we'll need a test for that case as it's a bit tricky - we definitely shouldn't accidentally send content-length: 0 which is probably wrong. Imo, if you don't write a body on HEAD, we should still hit this first case - sending no CL headers or chunking at all.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, also, if you call removeHeader for TE & CL for a HEAD request (this is what you generally do to disable all Node's default header handling) this change will now break keep-alive for HEAD requests, where previously it didn't (and it doesn't need to). It will hit the _last = true case at the end, and close the connection after this response. That's correct for GET/etc because without those headers you don't know how long the body is. It's not correct for HEAD, because you do always know how long the body is: 0 bytes.

In both these cases, I think the issue is trying to squeeze this logic into the existing cases. This logic you want here is doable, but I think it's a bit more complicated than currently implemented here.

// 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) {
Expand Down Expand Up @@ -932,7 +932,7 @@ function strictContentLength(msg) {
return (
msg.strictContentLength &&
msg._contentLength != null &&
msg._hasBody &&
(msg._hasBody || msg.req.method === 'HEAD') &&
!msg._removedContLen &&
!msg.chunkedEncoding &&
!msg.hasHeader('transfer-encoding')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';
const common = require('../common');
const http = require('http');
const assert = require('assert');

// This test is to make sure that when the HTTP server
// responds to a HEAD request with data to res.end,
Expand All @@ -18,6 +19,8 @@ server.on('listening', common.mustCall(function() {
method: 'HEAD',
path: '/'
}, common.mustCall(function(res) {
assert.notStrictEqual(res.headers['content-length'], undefined, 'Expected Content-Length header to be present');
qwerzl marked this conversation as resolved.
Show resolved Hide resolved

res.on('end', common.mustCall(function() {
server.close();
}));
Expand Down
Loading