Claude Skill

br-etag-cache

Add better-route 1.1 ETag and If-None-Match handling to GET or HEAD routes. Use for ETagMiddleware, strong or weak validators, custom etagResolver, WP_REST_Response preservation, comma-separated validators, wildcard matching, 304 responses, Cache-Control, proxy-stripped ETag trou

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download Lonsdale201-wp-agent-skills-better-route_br-etag-cache-8820ff3.zip · 2 KB
Part of lonsdale201/wp-agent-skills — 226 skills

Install

skills CLI npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/better-route/br-etag-cache
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart
Git git clone https://github.com/Lonsdale201/wp-agent-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lonsdale201/wp-agent-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

better-route: ETag conditional reads

Use ETags on read routes to let clients revalidate a representation. They do not prevent duplicate writes; use idempotency for that.

use BetterRoute\Middleware\Cache\ETagMiddleware;

$router->get('/catalog', $handler)
    ->publicRoute()
    ->middleware([new ETagMiddleware()]);

The default validator is a quoted SHA-1 of the JSON-encoded response body. It applies only to GET/HEAD results with status 200–299 except 204.

1.1 behavior

The middleware preserves Better Route Response and WP_REST_Response status/data. It adds the ETag through the appropriate response API instead of flattening the WordPress response.

It skips:

  • returned WP_Error;
  • non-2xx responses;
  • 204 No Content;
  • non-GET/HEAD methods.

If-None-Match accepts:

  • a single validator;
  • a comma-separated validator list;
  • weak or strong forms of the same opaque tag;
  • *.

On a match, 1.1 returns 304 with no body and preserves cache-relevant source headers: Cache-Control, Content-Location, Expires, and Vary, plus the ETag.

The middleware computes and controls the final ETag header; do not rely on an existing handler ETag remaining unchanged.

Cheap custom validators

For large responses, derive a validator from a stable version instead of hashing the full body:

use BetterRoute\Http\RequestContext;

$etag = new ETagMiddleware(
    weak: false,
    etagResolver: static function (mixed $response, RequestContext $context): string {
        return (string) get_option('myapp_catalog_version', 0);
    },
);

Return the opaque value; the middleware quotes it. A returned already-quoted or W/ value is normalized. Invalid quote/control bytes are replaced with a safe hash rather than reaching an HTTP header.

Use weak: true when byte differences may represent the same semantic representation:

new ETagMiddleware(weak: true); // W/"..."

The default JSON hash follows array order. Deeply sort associative data before returning it, or use a stable version resolver, when construction order is nondeterministic.

Cache-Control and privacy

ETag enables revalidation; it does not define freshness or sharing. Set Cache-Control separately:

return new Response($data, 200, [
    'Cache-Control' => 'public, max-age=300',
]);

Use private/no-store as appropriate for user-specific data. Never let a shared CDN cache /me or another personalized URL merely because it has an ETag.

Server response-cache scope (1.1.1)

CachingMiddleware is separate from ETag revalidation. Place authentication before its cache lookup. Default keys include the router namespace, route template, concrete request path, separate URL parameters, identity and merged request parameters. Query/body fields cannot shadow a URL ID into another target's cache entry.

Keep RequestContext::routePath as the template; the router supplies attributes['routeNamespace']. Custom key resolvers own equivalent namespace/URL/identity/tenant isolation. Test same-template routes across namespaces and different URL IDs with identical shadowing query values.

Old cache entries become cold and expire naturally. For idempotency, the same key-scope fix requires coordinated deployment; follow br-install-and-migrate rather than treating write replay like disposable read caching. Rate-limit buckets and optimistic-lock scope are unchanged.

Troubleshooting

Test both the public endpoint and the PHP/upstream origin. A reverse proxy, nginx/RunCloud rule, CDN, compression layer, or caching plugin may remove or rewrite an outbound ETag even when application-level matching still produces a correct 304.

Verify:

curl -i 'https://example.com/wp-json/myapp/v1/catalog'
curl -i 'https://example.com/wp-json/myapp/v1/catalog' \
  -H 'If-None-Match: "copied-tag"'

Also test If-None-Match: W/"copied-tag", a comma-separated list, and *.

Review checklist

  • Attach only to GET/HEAD routes.
  • Use a cheap stable resolver for large bodies.
  • Set explicit Cache-Control and correct privacy semantics.
  • Confirm WP REST response status/data/headers survive.
  • Confirm 4xx/5xx and WP_Error do not gain an ETag.
  • Confirm matched 304 has no body and retains cache headers.
  • Inspect intermediary header behavior if ETag disappears externally.

Related skills

  • Use br-idempotency or br-atomic-idempotency for write retries.
  • Use br-cors-public-client to expose ETag to browser JavaScript.

References

  • Verified source paths:
    • src/Middleware/Cache/ETagMiddleware.php
    • src/Http/Response.php
Files (wp-agent-skills)
  • SKILL.md 5.2 KB
    ---
    name: br-etag-cache
    description: Add better-route 1.1 ETag and If-None-Match handling to GET or HEAD routes. Use for ETagMiddleware, strong or weak validators, custom etagResolver, WP_REST_Response preservation, comma-separated validators, wildcard matching, 304 responses, Cache-Control, proxy-stripped ETag troubleshooting, reviewing conditional HTTP caching, or configuring identity/namespace/URL isolation in CachingMiddleware. The middleware skips WP_Error, 204, redirects, and non-2xx responses.
    metadata:
      wp-skills-author: "Soczó Kristóf"
      wp-skills-contact: "mailto:lonsdale201@hotmail.com"
      wp-skills-plugin: "better-route"
      wp-skills-plugin-version-tested: "1.1.1"
      wp-skills-php-min: "8.1"
      wp-skills-last-updated: "2026-09-21"
    ---
    
    # better-route: ETag conditional reads
    
    Use ETags on read routes to let clients revalidate a representation. They do not prevent duplicate writes; use idempotency for that.
    
    ```php
    use BetterRoute\Middleware\Cache\ETagMiddleware;
    
    $router->get('/catalog', $handler)
        ->publicRoute()
        ->middleware([new ETagMiddleware()]);
    ```
    
    The default validator is a quoted SHA-1 of the JSON-encoded response body. It applies only to `GET`/`HEAD` results with status 200–299 except 204.
    
    ## 1.1 behavior
    
    The middleware preserves Better Route `Response` and `WP_REST_Response` status/data. It adds the ETag through the appropriate response API instead of flattening the WordPress response.
    
    It skips:
    
    - returned `WP_Error`;
    - non-2xx responses;
    - `204 No Content`;
    - non-GET/HEAD methods.
    
    `If-None-Match` accepts:
    
    - a single validator;
    - a comma-separated validator list;
    - weak or strong forms of the same opaque tag;
    - `*`.
    
    On a match, 1.1 returns `304` with no body and preserves cache-relevant source headers: `Cache-Control`, `Content-Location`, `Expires`, and `Vary`, plus the ETag.
    
    The middleware computes and controls the final `ETag` header; do not rely on an existing handler ETag remaining unchanged.
    
    ## Cheap custom validators
    
    For large responses, derive a validator from a stable version instead of hashing the full body:
    
    ```php
    use BetterRoute\Http\RequestContext;
    
    $etag = new ETagMiddleware(
        weak: false,
        etagResolver: static function (mixed $response, RequestContext $context): string {
            return (string) get_option('myapp_catalog_version', 0);
        },
    );
    ```
    
    Return the opaque value; the middleware quotes it. A returned already-quoted or `W/` value is normalized. Invalid quote/control bytes are replaced with a safe hash rather than reaching an HTTP header.
    
    Use `weak: true` when byte differences may represent the same semantic representation:
    
    ```php
    new ETagMiddleware(weak: true); // W/"..."
    ```
    
    The default JSON hash follows array order. Deeply sort associative data before returning it, or use a stable version resolver, when construction order is nondeterministic.
    
    ## Cache-Control and privacy
    
    ETag enables revalidation; it does not define freshness or sharing. Set Cache-Control separately:
    
    ```php
    return new Response($data, 200, [
        'Cache-Control' => 'public, max-age=300',
    ]);
    ```
    
    Use `private`/`no-store` as appropriate for user-specific data. Never let a shared CDN cache `/me` or another personalized URL merely because it has an ETag.
    
    ## Server response-cache scope (1.1.1)
    
    `CachingMiddleware` is separate from ETag revalidation. Place authentication before its cache lookup. Default keys include the router namespace, route template, concrete request path, separate URL parameters, identity and merged request parameters. Query/body fields cannot shadow a URL ID into another target's cache entry.
    
    Keep `RequestContext::routePath` as the template; the router supplies `attributes['routeNamespace']`. Custom key resolvers own equivalent namespace/URL/identity/tenant isolation. Test same-template routes across namespaces and different URL IDs with identical shadowing query values.
    
    Old cache entries become cold and expire naturally. For idempotency, the same key-scope fix requires coordinated deployment; follow `br-install-and-migrate` rather than treating write replay like disposable read caching. Rate-limit buckets and optimistic-lock scope are unchanged.
    
    ## Troubleshooting
    
    Test both the public endpoint and the PHP/upstream origin. A reverse proxy, nginx/RunCloud rule, CDN, compression layer, or caching plugin may remove or rewrite an outbound ETag even when application-level matching still produces a correct 304.
    
    Verify:
    
    ```bash
    curl -i 'https://example.com/wp-json/myapp/v1/catalog'
    curl -i 'https://example.com/wp-json/myapp/v1/catalog' \
      -H 'If-None-Match: "copied-tag"'
    ```
    
    Also test `If-None-Match: W/"copied-tag"`, a comma-separated list, and `*`.
    
    ## Review checklist
    
    - Attach only to GET/HEAD routes.
    - Use a cheap stable resolver for large bodies.
    - Set explicit Cache-Control and correct privacy semantics.
    - Confirm WP REST response status/data/headers survive.
    - Confirm 4xx/5xx and WP_Error do not gain an ETag.
    - Confirm matched 304 has no body and retains cache headers.
    - Inspect intermediary header behavior if ETag disappears externally.
    
    ## Related skills
    
    - Use `br-idempotency` or `br-atomic-idempotency` for write retries.
    - Use `br-cors-public-client` to expose `ETag` to browser JavaScript.
    
    ## References
    
    - Verified source paths:
      - `src/Middleware/Cache/ETagMiddleware.php`
      - `src/Http/Response.php`
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related