amphp / amphp/http-server

Http1Driver chunk-encodes 204/304 responses and appends a terminating chunk

Open Beginner friendly
#393 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
PHP
Stars
1.3k
Forks
103
PR merge metrics
No merged PRs in 30d

Description

Version

amphp/http-server v3.4.6 (via pestphp/pest-plugin-browser), PHP 8.4, Linux.

Problem

Http1Driver::write() decides on chunked encoding with

$chunked = !$shouldClose
    && (!isset($headers["content-length"]) || $trailers !== null)
    && $protocol === "1.1"
    && $status >= HttpStatus::OK;

A 204 No Content (or 304 Not Modified) response that carries no Content-Length header is therefore sent as

HTTP/1.1 204 No Content
transfer-encoding: chunked
...

0\r\n\r\n

RFC 7230 §3.3.1 and §3.3.3: a server MUST NOT send a Transfer-Encoding header field in any response with a status code of 1xx or 204, and 204/304 responses have no message body regardless of header fields. Clients follow the RFC: Chromium treats the 204 as complete after the headers and returns the keep-alive socket to its pool with the five bytes 0\r\n\r\n still unread. The next request on that socket then fails with net::ERR_INVALID_HTTP_RESPONSE because the parser reads the stray chunk terminator before the status line.

How it was found

A Laravel app served through pest-plugin-browser's in-process server (which uses this package) fires POST /cookie-consent/impression204 early in page load; when Chromium reused that socket for the next script request, the script silently failed and the page never booted. Confirmed with a Chromium NetLog (SOCKET_BYTES_RECEIVED byte_count: 5 after the 204, then HTTP_STREAM_PARSER_READ_HEADERS net_error: -370 on the following request) and with tcpdump on the loopback interface. Setting Content-Length: 0 on the 204 makes the driver skip the body and the problem disappears.

Expected

Skip the body and never emit Transfer-Encoding for 1xx/204/304 the way HEAD is already special-cased in the same method; treating an explicit Content-Length: 0 from the handler as an opt-out is not enough because frameworks (Symfony HttpFoundation, for one) strip Content-Length from those responses on purpose.

Happy to send a PR if you agree with the shape.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in Http1Driver::write() and compare the existing HEAD special case with the response framing logic described in the issue. Verify that 1xx, 204, and 304 responses omit Transfer-Encoding and the terminating chunk while ordinary responses retain their current behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend, networking
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.