Skip to content

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

Description

@cyppe

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/impression → 204 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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions