pingap-plugin 0.15.0

Plugin for pingap
Documentation
# cache

HTTP response caching backed by either an in-memory [TinyUFO] store or a
file-based store, with cache-key control, stampede protection and an
IP-restricted `PURGE` method.

- **Step:** `request` (fixed)
- **Registered as:** `cache`

## Configuration

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `category` | string | — | Must be `cache`. |
| `directory` | string | memory | Empty or `memory://…` selects the memory backend; any other value is a file cache directory. |
| `namespace` | string | — | Isolates entries; with a file backend it becomes a subdirectory. |
| `headers` | string[] | — | Request headers appended to the cache key (variant caching). |
| `vary_headers` | string[] | — | Allow list for the origin's `Vary` response header: only these request headers may create cache variants. Unset honours every header the origin names. |
| `max_ttl` | duration | — | Upper bound on entry lifetime, capping upstream `Cache-Control`. |
| `max_file_size` | bytesize | `1mb` | Responses larger than this are not cached. |
| `lock` | duration | `1s` | Cache-lock window against stampedes. Any non-zero duration works; `0s` disables locking. |
| `lock_retries` | int | `2` | How many times a request that waited on the lock re-checks the cache before it gives up and fetches from upstream itself. |
| `eviction` | bool | absent | Presence of the key enables LRU eviction. Memory backend only. |
| `predictor` | bool | absent | Presence of the key enables the cacheability predictor. |
| `check_cache_control` | bool | `false` | Require a `Cache-Control` header on the response, otherwise do not store it. |
| `purge_ip_list` | string[] | `[]` | IPs / CIDRs allowed to issue `PURGE`. An entry that is neither fails configuration validation. |
| `skip` | string | — | Regex on path+query; matching requests bypass the cache entirely. |

### Backend selection

```toml
directory = ""                                   # memory, default size
directory = "memory://pingap?max_size=100mb"     # memory, absolute size
directory = "memory://pingap?max_size=20"        # memory, 20% of the budget
directory = "/opt/pingap/cache"                  # file cache
directory = "/opt/pingap/cache?inactive=1h&reading_max=1000"
```

See [pingap-cache](../../pingap-cache/README.md) for the full set of backend
query parameters.

## Example

```toml
[plugins.httpCache]
category = "cache"
directory = "/opt/pingap/cache"
namespace = "web"
headers = ["Accept-Encoding"]
max_ttl = "1h"
max_file_size = "10mb"
lock = "2s"
eviction = true
predictor = true
purge_ip_list = ["127.0.0.1", "10.0.0.0/8"]
skip = "^/api/"

[locations.web]
upstream = "web"
path = "/"
plugins = ["httpCache"]
```

Purging:

```bash
# one url (removes both the GET and the HEAD variant)
curl -X PURGE http://127.0.0.1:6188/assets/app.js
# 204 No Content       -> removed (idempotent: also 204 when nothing was cached)
# 403 Forbidden        -> your IP is not in purge_ip_list

# the whole namespace (file backend with a configured namespace only)
curl -X PURGE http://127.0.0.1:6188/*
# 200 "purged: 12, fail: 0"
# 501 Not Implemented  -> no namespace configured, or memory cache backend
```

`PURGE /*` empties everything this plugin cached: the namespace survives as a
directory in the file backend, so it is the one granularity beyond an exact url
that can be purged without an index (storage file names are hashes of the full
key — a url prefix does not map to anything on disk). The purge clears the
in-memory hot layer along with the files, and only cleans the local instance:
in a multi-instance deployment, issue the request on every node.

## Behaviour

- Only `GET`, `HEAD` and `PURGE` are handled; every other method skips the
  plugin.
- The cache key is derived from the request URI plus `namespace` plus the values
  of the `headers` you listed. `PURGE` builds the key for both `GET` and `HEAD`,
  so purging `/x` removes the entries created by either method. If `headers`
  are configured, send them on the `PURGE` request too — they are part of the
  key.
- The origin's `Vary` response header is honoured: each combination of the
  request headers it names is stored as its own variant under the same key, and
  `Vary: *` makes the response uncacheable. `vary_headers` limits which headers
  may do that, since `Vary: Cookie` or `Vary: User-Agent` would mean a variant
  per client. `PURGE` removes the primary slot; the variants behind it become
  unreachable and are reclaimed by eviction or the inactive sweep.
- `lock` makes concurrent misses for the same key wait for the first one instead
  of all hitting the origin.
- `Cache-Control: stale-while-revalidate=<seconds>` from origin is honoured;
  entries without this directive are not served stale during revalidation.
  After freshness expires, pingap serves stale content inside that window while
  one lock holder revalidates against origin in background. Responses outside
  the window wait for or perform normal revalidation. SWR requires a non-zero
  `lock`; `lock = "0s"` disables it. `max_ttl` caps freshness only, not the
  SWR window. Background revalidation passes through the normal request
  pipeline, creates an access-log entry, updates metrics, and runs request-step
  plugins again.
- Cache read/write counts are recorded into the request context and are available
  in access logs as `{:cache_lookup_time}` / `{:cache_lock_time}`.

## Usage notes

- **`eviction` needs a bounded backend.** It is only wired up when the backend
  reports a non-zero `max_size`, which the file backend does not — so `eviction`
  is memory-only, and setting it on a file cache logs an error and is ignored.
  File cache entries are reclaimed by the inactive sweep instead (`?inactive=…`).
- **One memory backend per process.** The memory cache is a process-wide
  singleton created by the first `cache` plugin that asks for it; a second
  declaration with a different `max_size` or `mode` silently reuses the first
  one. Use `namespace` to separate content, not a second `directory`.
- Each distinct `lock` duration allocates one shared lock for the lifetime of
  the process, so the number of distinct values matters, not the number of
  plugin instances.
- Pair with [`accept_encoding`]accept_encoding.md when caching by
  `Accept-Encoding`, otherwise the number of variants explodes.

[TinyUFO]: https://github.com/cloudflare/pingora/tree/main/tinyufo