performwp / performwp/perform

Fix full-page cache handling for HTTP HEAD requests

Open
#200 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area: cache area: frontend area: performance bug owner:codex priority: high status: ready
Dominant language
PHP
Stars
5
Forks
2
PR merge metrics
No merged PRs in 30d

Description

Problem

The full-page cache treats HEAD as cacheable, but a cache hit emits the cached HTML body and exits. HTTP HEAD responses must have the same headers as GET without a message body. Sending the body wastes bandwidth and can confuse monitors, crawlers, and reverse proxies that use HEAD for freshness checks.

Evidence

  • src/Modules/Cache/PageCache.php:962-966 allows both GET and HEAD requests through the cacheability gate.
  • src/Modules/Cache/PageCache.php:161-176 emits $html for both a fresh hit and a stale hit without checking the request method.
  • The existing page-cache tests do not cover fresh, stale, or lock-stale HEAD responses.

Scope

Correct HEAD behavior in the local page-cache path while preserving cache keys, HIT/STALE headers, invalidation, and GET behavior.

Implementation plan

  1. Introduce one request-method helper and use it consistently in cacheability and cache-response delivery.
  2. On fresh, stale, and stale-lock hits, send the same cache state headers as GET but return no body for HEAD; include a correct Content-Length only when it is safe to provide it.
  3. Decide and document the miss behavior so a HEAD request never writes or emits a body unexpectedly.
  4. Keep internal regeneration and preload requests on their existing GET path.
  5. Add focused PHPUnit coverage for fresh HIT, STALE, and STALE-LOCK HEAD requests and for an unaffected GET HIT.

Acceptance criteria

  • A cached HEAD request returns the expected X-Perform-Cache state and an empty body.
  • A GET request for the same cache key still returns the cached body unchanged.
  • HEAD never creates a cache entry or changes cache freshness state as an unintended side effect.
  • Fresh, stale, and lock-contention paths are all covered by regression tests.

Validation

  • composer test
  • composer phpstan
  • PHPCS on changed PHP files
  • Isolated WordPress runtime proof: warm a page with GET, then compare GET and HEAD status, headers, and response byte count.

Non-goals and risk

  • Do not broaden page-cache invalidation; issue #197 and PR #199 own lifecycle invalidation.
  • Do not alter cache keys, TTLs, Cloudflare behavior, or public settings.
  • Preserve HTTP compatibility for proxies that issue HEAD requests.

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 src/Modules/Cache/PageCache.php, especially lines 161-176 for cache-hit delivery and 962-966 for the cacheability gate. Trace fresh, stale, stale-lock, miss, and internal regeneration paths, then add focused PHPUnit coverage for HEAD and unaffected GET behavior. Done means HEAD preserves cache-state headers with no body while GET, invalidation, and cache freshness behavior remain unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.