AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Http Cache

skill-kilimcininkoroglu-cli-tweaks-http-cache · by KilimcininKorOglu

>

No reviews yet
0 installs
34 views
0.0% view→install

Install

$ agentstack add skill-kilimcininkoroglu-cli-tweaks-http-cache

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-kilimcininkoroglu-cli-tweaks-http-cache)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Http Cache? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

HTTP Cache — ETag + Cache-Control

Scan the project's HTTP layer, identify static and dynamic endpoints, and implement content-hash-based ETag caching with Cache-Control headers and 304 Not Modified support.

Default behavior is scan (dry-run). Cache bugs are notoriously sinister — users see stale JS, admins see stale HTML, API responses get cached unexpectedly, and detection often takes days. Apply changes only after reviewing the scan output.

Usage

/http-cache          # Scan only — show what would change, don't modify (DEFAULT)
/http-cache scan     # Same as default
/http-cache apply    # Apply the changes from the scan

How It Works

Phase 1: Project Scan

Detect the project's language and HTTP framework:

| Language | Frameworks to detect | |------------|---------------------------------------------------------------| | Go | net/http, chi, gin, echo, fiber, gorilla/mux | | Node.js | express, fastify, koa, hapi, next.js, nuxt | | Python | flask, django, fastapi, starlette | | Rust | actix-web, axum, warp, rocket | | PHP | laravel, symfony, plain (no framework) | | Java | Spring MVC / Spring Boot, JAX-RS (Jersey), plain Servlets | | C# / .NET | ASP.NET Core (MVC, Razor Pages, Minimal API) | | Ruby | Rails, Sinatra |

Identify:

  1. Static endpoints — serve files from disk, embed, or public directory (HTML, CSS, JS, JSON, XML, TXT, images, fonts)
  2. Dynamic endpoints — API routes returning computed data (JSON APIs, SSE, WebSocket)
  3. Template-rendered pages — HTML generated from templates with dynamic data
  4. Sensitive endpoints — auth, admin panels, payment, banking, user PII, internal dashboards
  5. Non-deterministic endpoints — responses intentionally different on every request. Scan handler code for randomness signals: Math.random, shuffle, rand(, random.sample, random.choice, ORDER BY RAND(), MongoDB $sample, lottery/rotation logic. Caching these breaks their semantics: the browser replays the same "random" payload until max-age expires, and users only escape via hard refresh.

Phase 2: Classify Endpoints

Assign each endpoint a caching strategy:

| Type | Cache-Control | ETag | 304 Support | |-------------------------------------------------|----------------------------------------|--------|-------------| | Immutable assets (JS/CSS with hash in filename) | public, max-age=31536000, immutable | no | no | | Static files (HTML, robots.txt, sitemap, etc.) | public, max-age=3600 | yes | yes | | Landing page / index HTML | public, max-age=300 | yes | yes | | Public API responses (non-sensitive, cacheable) | public, max-age=60 (or as suitable) | yes | yes | | Non-deterministic responses (random selection, shuffle, sampling, rotation) | no-store | no | no | | Real-time API responses (live data) | no-store | no | no | | Template-rendered pages with non-sensitive user data | private, no-cache | yes | yes | | Sensitive pages (auth, admin, payment, banking) | no-store | no | no | | SSE / WebSocket streams | no-store | no | no |

Important distinctions:

  • no-cache ≠ "don't cache" — it means "cache but revalidate every use." For truly sensitive data use no-store.
  • private allows browser to cache but blocks shared caches (CDN, proxy). On shared devices this is still risky for sensitive content — prefer no-store.
  • For immutable assets, ETag is unnecessary and counterproductive; some browsers will still send conditional requests, wasting round-trips.
  • "Public and non-sensitive" does NOT imply "cacheable." A random server sample or shuffled list is public and non-sensitive, yet caching it freezes the randomness — classify by determinism first, sensitivity second.
  • One route can span multiple classes. When the same endpoint is deterministic for some query parameters and random for others (e.g. ?country=X returns a filtered list but the bare route returns a random sample), set Cache-Control per branch inside the handler — a blanket route-level header will be wrong for at least one variant.
  • stale-while-revalidate and must-revalidate are opposite refinements of max-age. stale-while-revalidate= lets a shared cache serve slightly-stale content while it revalidates in the background — add it to a public, max-age asset to hide revalidation latency. must-revalidate forbids serving stale once max-age expires, forcing a fresh revalidation — use it for content that must never be served stale (pricing, inventory, balances).

Phase 3: Implementation

ETag Generation

Compute a content hash at startup time (not per-request) for embedded/static files. SHA-256 is the safe default — the cost is paid once at startup, so hash speed rarely matters; reach for a faster non-cryptographic hash (xxHash, FNV) only if startup time is measurably affected:

| Language | Hash function | Format | |----------|-------------------------------|-------------------------------------| | Go | crypto/sha256 | fmt.Sprintf("%x", hash) | | Node.js | crypto.createHash('sha256') | '"' + hash.digest('hex') + '"' | | Python | hashlib.sha256() | f'"{h.hexdigest()}"' | | Rust | sha2::Sha256 | format!("\"{}\"", hex) | | PHP | hash_file('sha256', $path) | '"' . hash_file('sha256', $p) . '"' |

ETag value MUST be wrapped in double quotes per RFC 7232: "abc123...".

Strong vs Weak ETags:

  • Strong ETag ("abc123") — bytewise identical content guarantee.
  • Weak ETag (W/"abc123") — semantically equivalent content (e.g., same data, different whitespace). Use when:
  • Computing ETag from metadata (mtime + size) instead of content
  • Response goes through compression middleware (gzip/brotli)
  • Template-rendered pages where minor formatting differences are acceptable

Note on "startup time": This applies to long-running processes (Go, Node.js, Python servers, Rust). PHP-FPM and request-per-process models compute on demand — cache the hash via APCu or filesystem-derived metadata to avoid hashing on every request.

Conditional Request Handling

Before writing the response body, check the If-None-Match request header. The header may contain a comma-separated list of ETags or *:

If-None-Match: "abc"
If-None-Match: "abc", "def", W/"ghi"
If-None-Match: *

Parse it as a list, not an exact-match string:

func matchETag(ifNoneMatch, etag string) bool {
    if ifNoneMatch == "" {
        return false
    }
    if strings.TrimSpace(ifNoneMatch) == "*" {
        return true
    }
    for _, tag := range strings.Split(ifNoneMatch, ",") {
        tag = strings.TrimSpace(tag)
        // Compare ignoring weak prefix per RFC 7232 §2.3.2 weak comparison
        tag = strings.TrimPrefix(tag, "W/")
        candidate := strings.TrimPrefix(etag, "W/")
        if tag == candidate {
            return true
        }
    }
    return false
}

Set these headers on ALL cacheable responses (both 200 and 304):

  • ETag: "content-hash"
  • Cache-Control:
  • Vary: (see Vary section below)
  • Last-Modified: (optional but recommended for static files — improves proxy/CDN compatibility)

Last-Modified is advertised, not honored, by the manual handlers here. The handlers in this skill validate on ETag / If-None-Match only. A client sending only If-Modified-Since (some proxies, older clients) will get a full 200, not a 304, from these handlers. This is acceptable whenever an ETag is present — modern clients send If-None-Match and revalidate correctly. Add an explicit If-Modified-Since check only if you must serve date-only revalidators. http.ServeContent honors both If-None-Match and If-Modified-Since only when given a NON-zero modtime — the Go example above passes the zero value (stable ETag across restarts), so it is ETag-only too; pass a real file mtime there if you need date-based revalidation. The native framework mechanisms below handle both.

Vary Header

Set Vary whenever the response varies along an axis the cache should distinguish:

| Condition | Required Vary value | |-----------------------------------------------|-------------------------------| | Response uses gzip/brotli compression | Accept-Encoding | | Response varies by language (i18n) | Accept-Language | | Response varies by auth state | Cookie or Authorization | | Content negotiation (JSON vs HTML) | Accept |

Compression + ETag interaction: If a compression middleware (gzip/brotli) sits AFTER your handler, ETag is computed on the uncompressed body but the wire body differs by Accept-Encoding. Two safe options:

  1. Weak ETag + Vary: Accept-Encoding — declares semantic equivalence across encodings.
  2. Compute ETag after compression — strong ETag remains valid but couples handler with middleware.

Never use a strong ETag with multiple encoded variants and no Vary — this is a spec violation and can cause cache poisoning across users.

Per-Framework Patterns

Go (net/http) — Prefer http.ServeContent for static files:

The standard library already handles If-None-Match, If-Modified-Since, Range requests, and Content-Type detection. Use it instead of manual implementation when possible:

func cachedFileHandler(content fs.FS, filename, cacheControl string) http.HandlerFunc {
    data, err := fs.ReadFile(content, filename)
    if err != nil {
        return func(w http.ResponseWriter, r *http.Request) {
            http.Error(w, "not found", http.StatusNotFound)
        }
    }
    etag := fmt.Sprintf(`"%x"`, sha256.Sum256(data))
    // Use a STABLE modTime, never time.Now(): time.Now() changes on every process
    // restart, so Last-Modified would claim the content changed when it did not.
    // The zero Time makes ServeContent omit Last-Modified and rely on the ETag alone
    // (verified: ServeContent skips Last-Modified when modTime.IsZero()).
    // For files read from disk, pass the real file mtime instead.
    var modTime time.Time // zero value

    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Cache-Control", cacheControl)
        w.Header().Set("ETag", etag)
        // ServeContent handles If-None-Match, If-Modified-Since, Range, HEAD,
        // and sets Content-Type from filename.
        http.ServeContent(w, r, filename, modTime, bytes.NewReader(data))
    }
}

Go (net/http) — Manual handler when ServeContent is not suitable:

func contentETag(data []byte) string {
    return fmt.Sprintf(`"%x"`, sha256.Sum256(data))
}

func cachedHandler(data []byte, contentType, cacheControl string) http.HandlerFunc {
    etag := contentETag(data)
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", contentType)
        w.Header().Set("Cache-Control", cacheControl)
        w.Header().Set("ETag", etag)

        if matchETag(r.Header.Get("If-None-Match"), etag) {
            w.WriteHeader(http.StatusNotModified)
            return
        }
        if r.Method == http.MethodHead {
            return
        }
        w.Write(data)
    }
}

Express (Node.js) — Built-in ETag:

Express auto-generates weak ETags by default. For most cases this is enough:

app.set('etag', 'strong'); // or 'weak' (default), or false to disable

For custom static handlers with strong content-hashed ETag:

const crypto = require('crypto');
const fs = require('fs');

function etag(data) {
  return '"' + crypto.createHash('sha256').update(data).digest('hex') + '"';
}

function matchETag(header, tag) {
  if (!header) return false;
  if (header.trim() === '*') return true;
  const stripWeak = (s) => s.trim().replace(/^W\//, '');
  return header.split(',').some(t => stripWeak(t) === stripWeak(tag));
}

function cachedStatic(filePath, contentType, maxAge) {
  const data = fs.readFileSync(filePath);
  const tag = etag(data);

  return (req, res) => {
    res.set('Content-Type', contentType);
    res.set('Cache-Control', `public, max-age=${maxAge}`);
    res.set('ETag', tag);

    if (matchETag(req.get('If-None-Match'), tag)) {
      return res.status(304).end();
    }
    res.send(data);
  };
}

FastAPI (Python):

import hashlib
from typing import Optional
from fastapi import Request
from fastapi.responses import Response

def content_etag(data: bytes) -> str:
    return f'"{hashlib.sha256(data).hexdigest()}"'

def match_etag(header: Optional[str], tag: str) -> bool:
    if not header:
        return False
    if header.strip() == "*":
        return True
    strip_weak = lambda s: s.strip().removeprefix("W/")
    return any(strip_weak(t) == strip_weak(tag) for t in header.split(","))

def cached_file_response(data: bytes, media_type: str, max_age: int):
    etag = content_etag(data)

    async def handler(request: Request):
        headers = {
            "Cache-Control": f"public, max-age={max_age}",
            "ETag": etag,
        }
        if match_etag(request.headers.get("if-none-match"), etag):
            return Response(status_code=304, headers=headers)
        return Response(content=data, media_type=media_type, headers=headers)

    return handler

PHP (plain, no framework) — Weak ETag from metadata:

When computing ETag from filemtime + filesize (not full content hash), use a weak validator. mtime+size cannot guarantee bytewise identity (same-second writes with same size are theoretically possible), so a strong ETag would be a spec violation:

// Weak ETag from file metadata — fast, no hashing per request
$mtime = filemtime($filePath);
$size  = filesize($filePath);
$etag  = 'W/"' . $mtime . '-' . $size . '"';

header('ETag: ' . $etag);
header('Cache-Control: public, max-age=3600');

// Parse If-None-Match as a list
function matchETag($header, $tag) {
    if (!$header) return false;
    if (trim($header) === '*') return true;
    $stripWeak = fn($s) => preg_replace('/^W\//', '', trim($s));
    foreach (explode(',', $header) as $candidate) {
        if ($stripWeak($candidate) === $stripWeak($tag)) {
            return true;
        }
    }
    return false;
}

if (matchETag($_SERVER['HTTP_IF_NONE_MATCH'] ?? '', $etag)) {
    http_response_code(304);
    exit;
}

For strong content-based ETag in PHP, use hash_file('sha256', $filePath) and cache the result via APCu to avoid hashing on every request. Always check If-None-Match BEFORE loading/processing data to skip expensive work on cache hits.

Frameworks with native conditional-request support:

Prefer a framework's built-in conditional-request handling over a hand-written handler:

  • Spring (Java): ShallowEtagHeaderFilter auto-generates a content ETag and returns 304 on an If-None-Match match. Caveat: it still renders the response (saves bandwidth, not server CPU). For server-side savings plus If-Match / If-Unmodified-Since support, call ServletWebRequest.checkNotModified(etag, lastModified) in the handler and short-circuit before building the body.
  • ASP.NET Core (C#): Output Caching (AddOutputCache() + `app.Us

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.