The `http.Server` adds a `Transfer-Encoding: chunked` header when the response has no body
Version
22 (but maybe every version)
Platform
Microsoft Windows NT 10.0.22631.0 x64 (but maybe not platform-specific)
Subsystem
node:http
What steps will reproduce the bug?
The Transfer-Encoding: chunked header is always added when there is no response body. There seems to be no way to remove it.
However, since this header is not added when the request method is HEAD, the response headers are different when the request method is GET and HEAD, which is against the HTTP specification.
Example server code:
import { createServer } from 'node:http';
import { finished } from 'node:stream/promises';
createServer(function (req, res) {
finished(req.resume()).then(() => {
res.writeHead(200, { 'Cache-Control': 'no-store' });
res.end(); // No body
}).catch((err) => {
this.emit('error', err);
})
}).listen(8000, '0.0.0.0');
GET:
$ curl -i http://localhost:8000/
HTTP/1.1 200 OK
Cache-Control: no-store
Date: Sat, 23 Nov 2024 09:01:22 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked
HEAD:
$ curl --head -i http://localhost:8000/
HTTP/1.1 200 OK
Cache-Control: no-store
Date: Sat, 23 Nov 2024 09:01:17 GMT
Connection: keep-alive
Keep-Alive: timeout=5
A monkey patching solution I found is to always add the Content-Length: 0 header.
However, I'm not sure if this is really the intended behavior. I think it should be possible to send a response without adding a Content-Length: 0 header when there is no body.
Or, if it's intended behavior, it should be documented.
How often does it reproduce? Is there a required condition?
Always.
What is the expected behavior? Why is that the expected behavior?
I would guess that the generally expected behavior is that the Transfer-Encoding: chunked header is NOT added when there is no response body.
What do you see instead?
.
Additional information
Perhaps this issue https://github.com/denoland/deno/issues/20063 may be related.
Just to comment on this
Platform Microsoft Windows NT 10.0.22631.0 x64 (but maybe not platform-specific)
I've tested on Ubuntu and I see the same behavior, so I'd say this is not platform-specific
It seems Node.js will add Transfer-Encoding: chunked by default if you do not set Content-Length or Transfer-Encoding header. You can call res.removeHeader("Transfer-Encoding") to remove this header.
It seems Node.js will add
Transfer-Encoding: chunkedby default if you do not setContent-LengthorTransfer-Encodingheader. You can callres.removeHeader("Transfer-Encoding")to remove this header.
Example server:
import { createServer } from 'node:http';
import { finished } from 'node:stream/promises';
createServer(function (req, res) {
finished(req.resume()).then(() => {
res.removeHeader('Transfer-Encoding'); // Remove the Transfer-Encoding header
res.writeHead(200, { 'Cache-Control': 'no-store' });
res.end(); // No body
}).catch((err) => {
this.emit('error', err);
});
}).listen(8000, '0.0.0.0');
GET request:
$ curl -i http://localhost:8000/
HTTP/1.1 200 OK
Cache-Control: no-store
Date: Tue, 26 Nov 2024 16:28:39 GMT
Connection: keep-alive
Keep-Alive: timeout=5
HEAD request:
$ curl --head -i http://localhost:8000/
HTTP/1.1 200 OK
Cache-Control: no-store
Date: Tue, 26 Nov 2024 16:28:51 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Yes, it works. But I think this is still a behavior that is hard to predict and hard to expect.
Wouldn't it be better to add the Transfer-Encoding header only when a response body exists?
@theanarkh
It seems Node.js will add
Transfer-Encoding: chunkedby default if you do not setContent-LengthorTransfer-Encodingheader. You can callres.removeHeader("Transfer-Encoding")to remove this header.
HEAD request:
$ curl --head -iv http://localhost:8000/
* Host localhost:8000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8000...
* Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000
* using HTTP/1.x
> HEAD / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/8.10.1
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Cache-Control: no-store
Cache-Control: no-store
< Date: Thu, 28 Nov 2024 04:47:49 GMT
Date: Thu, 28 Nov 2024 04:47:49 GMT
< Connection: keep-alive
Connection: keep-alive
< Keep-Alive: timeout=5
Keep-Alive: timeout=5
<
* Connection #0 to host localhost left intact
GET request:
$ curl -iv http://localhost:8000/
* Host localhost:8000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8000...
* Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000
* using HTTP/1.x
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/8.10.1
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Cache-Control: no-store
Cache-Control: no-store
< Date: Thu, 28 Nov 2024 04:47:56 GMT
Date: Thu, 28 Nov 2024 04:47:56 GMT
< Connection: keep-alive
Connection: keep-alive
< Keep-Alive: timeout=5
Keep-Alive: timeout=5
* no chunk, no close, no size. Assume close to signal end
<
* abort upload
* shutting down connection #0
When I printed the details with curl, it seems that the response is not closed properly when res.removeHeader("Transfer-Encoding") is used. This seems like a solution that shouldn't be used.
Yeah i think you need to set content length header, havent had this problem