Skip to content

fix(state): omit Content-Type when response has no body#8218

Merged
soyuka merged 1 commit into
api-platform:4.3from
soyuka:fix/issue-2929-content-type-no-body
Jun 3, 2026
Merged

fix(state): omit Content-Type when response has no body#8218
soyuka merged 1 commit into
api-platform:4.3from
soyuka:fix/issue-2929-content-type-no-body

Conversation

@soyuka
Copy link
Copy Markdown
Member

@soyuka soyuka commented Jun 2, 2026

Fixes #2929.

Summary

Per RFC 7230 §3.3.2: "A sender MUST NOT generate a Content-Type header field in a message that does not contain a payload body."

Per RFC 7230 §3.3.3: responses with status 204 No Content, 205 Reset Content, and 304 Not Modified MUST NOT include a payload body.

HttpResponseHeadersTrait::getHeaders() was unconditionally setting Content-Type even when the operation produced no body (e.g. output: false) or when the status code mandates an empty body (DELETE → 204).

Change

Gate the Content-Type header behind:

$hasBody = $hasOutput && !\in_array($status, [
    Response::HTTP_NO_CONTENT,
    Response::HTTP_RESET_CONTENT,
    Response::HTTP_NOT_MODIFIED,
], true);

Where $hasOutput is false when output['class'] === null (i.e. output: false was passed to the operation).

Tests

Three new unit tests in RespondProcessorTest:

  • testDoesNotSetContentTypeWhenOutputIsFalse — POST with output: false + 204
  • testDoesNotSetContentTypeWhenOutputIsFalseWithCreatedStatus — POST with output: false + 201
  • testDoesNotSetContentTypeOnBodylessStatusCodes — DELETE (default 204)

Note on Symfony Response::prepare()

Symfony's Response::prepare() automatically re-adds a Content-Type header derived from the request format for all responses whose status is not 204 or 304 (see Response::isEmpty()). For the typical output: false flow (status defaults to 204 via InputOutputResourceMetadataCollectionFactory), Symfony's strip aligns with this fix and the header is fully omitted on the wire.

For the edge case where a user sets output: false together with a status that Symfony does not treat as empty (e.g. 201, 202), Symfony will re-add Content-Type. A follow-up tied to #7875 (which introduces ResponseHeaderParameter for declarative header customization) can add a kernel.response listener that honours the no-body intent regardless of status.

@soyuka soyuka force-pushed the fix/issue-2929-content-type-no-body branch from f4ffe0d to dcaa4eb Compare June 2, 2026 13:41
Per RFC 7230 §3.3.2, a sender MUST NOT generate a Content-Type field
in a message containing no payload body, and per §3.3.3, 204, 205 and
304 responses MUST NOT include a body.

HttpResponseHeadersTrait::getHeaders() now gates Content-Type behind a
$hasBody check that omits Content-Type when the operation explicitly
disables output (output: ['class' => null]) or when the resolved status
is 204, 205 or 304. Operations without configured output (e.g. the
synthetic Get built by JsonLd ContextAction) keep their Content-Type.

Fixes api-platform#2929
@soyuka soyuka force-pushed the fix/issue-2929-content-type-no-body branch from dcaa4eb to f3a64b5 Compare June 2, 2026 14:21
];

if ($hasBody) {
$headers['Content-Type'] = \sprintf('%s; charset=utf-8', $request->getMimeType($request->getRequestFormat()));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unrelated, but the charset attribute unconditionally may not be a good idea. For instance JSON, doesn't have a charset attribute and including it is not valid, and can break some strict clients.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Indeed this is to avoid breaking tests we had that before. Lets investigate this in another issue.

@soyuka soyuka merged commit 9aca842 into api-platform:4.3 Jun 3, 2026
106 of 108 checks passed
soyuka added a commit to soyuka/core that referenced this pull request Jun 3, 2026
…e it

JSON-based media types (RFC 8259, RFC 6839 `+json` suffix) do not define
a `charset` parameter, and including one can break strict clients. Restrict
`; charset=utf-8` to `text/*`, `application/xml` and
`application/xml-external-parsed-entity`, matching their IANA registrations.

Updates functional tests asserting on Content-Type accordingly.

Refs api-platform#8218
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants