{"id":28546212,"url":"https://github.com/bartventer/httpcache","last_synced_at":"2025-07-07T06:31:34.060Z","repository":{"id":296870254,"uuid":"994735176","full_name":"bartventer/httpcache","owner":"bartventer","description":"Standards-compliant HTTP caching transport for Go clients (RFC 9111).","archived":false,"fork":false,"pushed_at":"2025-07-01T20:54:07.000Z","size":233,"stargazers_count":29,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-01T21:32:51.306Z","etag":null,"topics":["caching","go","golang","http","http-cache","rfc9111","roundtripper"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bartventer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-02T12:00:42.000Z","updated_at":"2025-07-01T20:42:49.000Z","dependencies_parsed_at":"2025-06-24T17:27:52.123Z","dependency_job_id":"8eac263d-5b7b-43fd-bb6f-43faed057e1b","html_url":"https://github.com/bartventer/httpcache","commit_stats":null,"previous_names":["bartventer/httpcache"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/bartventer/httpcache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bartventer%2Fhttpcache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bartventer%2Fhttpcache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bartventer%2Fhttpcache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bartventer%2Fhttpcache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bartventer","download_url":"https://codeload.github.com/bartventer/httpcache/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bartventer%2Fhttpcache/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264027579,"owners_count":23546102,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caching","go","golang","http","http-cache","rfc9111","roundtripper"],"created_at":"2025-06-09T23:09:05.704Z","updated_at":"2025-07-07T06:31:34.054Z","avatar_url":"https://github.com/bartventer.png","language":"Go","readme":"# httpcache\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/bartventer/httpcache.svg)](https://pkg.go.dev/github.com/bartventer/httpcache)\n[![Go Report Card](https://goreportcard.com/badge/github.com/bartventer/httpcache)](https://goreportcard.com/report/github.com/bartventer/httpcache)\n[![Test](https://github.com/bartventer/httpcache/actions/workflows/default.yml/badge.svg)](https://github.com/bartventer/httpcache/actions/workflows/default.yml)\n[![codecov](https://codecov.io/github/bartventer/httpcache/graph/badge.svg?token=pnpoA3t4EE)](https://codecov.io/github/bartventer/httpcache)\n\n**httpcache** is a Go package that provides a standards-compliant [http.RoundTripper](https://pkg.go.dev/net/http#RoundTripper) for transparent HTTP response caching, following [RFC 9111 (HTTP Caching)](https://www.rfc-editor.org/rfc/rfc9111).\n\n\u003e **Note:** This package is intended for use as a **private (client-side) cache**. It is **not** a shared or proxy cache. It is designed to be used with an HTTP client to cache responses from origin servers, improving performance and reducing load on those servers.\n\n## Features\n\n- **Plug-and-Play**: Just swap in as your HTTP client's transport; no extra configuration needed. [^1]\n- **RFC 9111 Compliance**: Handles validation, expiration, and revalidation ([see details](#rfc-9111-compliance-matrix)).\n- **Cache Control**: Supports all required HTTP cache control directives, as well as extensions like `stale-while-revalidate`, `stale-if-error`, and `immutable` ([view details](#field-definitions-details)).\n- **Cache Backends**: Built-in support for file system and memory caches, with the ability to implement custom backends (see [Cache Backends](#cache-backends)).\n- **Cache Maintenance API**: Optional REST endpoints for listing, retrieving, and deleting cache entries (see [Cache Maintenance API](#cache-maintenance-api-debug-only)).\n- **Extensible**: Options for logging, transport and timeouts (see [Options](#options)).\n- **Debuggable**: Adds a cache status header to every response (see [Cache Status Headers](#cache-status-headers)).\n- **Zero Dependencies**: No external dependencies, pure Go implementation.\n\n![Made with VHS](https://vhs.charm.sh/vhs-3WOBtYTZzzXggFGYRudHTV.gif)\n\n*Demonstration of HTTP caching in action. See [_examples/app](_examples/app/app.go) for code.*\n\n## Installation\n\nTo install the package, run:\n\n```bash\ngo get github.com/bartventer/httpcache\n```\n\n## Quick Start\n\nTo get started, create a new HTTP client with the `httpcache` transport, specifying a cache backend DSN. You'll need to register the desired cache backend before using it. Here's an example using the built-in file system cache:\n\n```go\npackage main\n\nimport (\n    \"log/slog\"\n    \"net/http\"\n    \"time\"\n\n    \"github.com/bartventer/httpcache\"\n    // Register the file system cache backend\n    _ \"github.com/bartventer/httpcache/store/fscache\"\n)\n\nfunc main() {\n    // Example DSN for the file system cache backend\n    dsn := \"fscache://?appname=myapp\"\n    client := \u0026http.Client{\n        Transport: httpcache.NewTransport(\n            dsn,\n            httpcache.WithSWRTimeout(10*time.Second),\n            httpcache.WithLogger(slog.Default()),\n        ),\n    }\n    // ... Use the client as usual\n}\n```\n\n\u003e **Note:** The DSN format and options depend on the cache backend you choose. Refer to the [Cache Backends](#cache-backends) section for details on available backends and their DSN formats.\n\n## Cache Backends\n\nThe following built-in cache backends are available:\n\n| Backend                                                                         | DSN Example                | Description                                                                                                                                                            |\n| ------------------------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| [`fscache`](https://pkg.go.dev/github.com/bartventer/httpcache/store/fscache)   | `fscache://?appname=myapp` | File system cache, stores responses on disk. Suitable for persistent caching across restarts. Supports context cancellation, as well as optional `AES-GCM` encryption. |\n| [`memcache`](https://pkg.go.dev/github.com/bartventer/httpcache/store/memcache) | `memcache://`              | In-memory cache, suitable for ephemeral caching. Does not persist across restarts.                                                                                     |\n\nConsult the documentation for each backend for specific configuration options and usage details.\n\n### Custom Cache Backends\n\nTo implement a custom cache backend, create a type that satisfies the [`store/driver.Conn`](https://pkg.go.dev/github.com/bartventer/httpcache/store/driver#Conn) interface, then register it using the [`store.Register`](https://pkg.go.dev/github.com/bartventer/httpcache/store#Register) function. Refer to the built-in backends for examples of how to implement this interface.\n\n### Cache Maintenance API (Debug Only)\n\nA REST API is available for cache inspection and maintenance, intended for debugging and development use only. **Do not expose these endpoints in production.**\n\n**Endpoints:**\n- `GET    /debug/httpcache`           — List cache keys (if supported)\n- `GET    /debug/httpcache/{key}`     — Retrieve a cache entry\n- `DELETE /debug/httpcache/{key}`     — Delete a cache entry\n\nAll endpoints require a `dsn` query parameter to select the cache backend.\n\n**Usage Example:**\n```go\nimport (\n    \"net/http\"\n    \"github.com/bartventer/httpcache/store/expapi\"\n)\n\nfunc main() {\n    expapi.Register()\n    http.ListenAndServe(\":8080\", nil)\n}\n```\n\nTo use a custom [ServeMux](https://pkg.go.dev/net/http#ServeMux), pass `expapi.WithServeMux(mux)` to `expapi.Register()`.\n\n## Options\n\n| Option                            | Description                                         | Default Value                   |\n| --------------------------------- | --------------------------------------------------- | ------------------------------- |\n| `WithUpstream(http.RoundTripper)` | Set a custom transport for upstream/origin requests | `http.DefaultTransport`         |\n| `WithSWRTimeout(time.Duration)`   | Set the stale-while-revalidate timeout              | `5 * time.Second`               |\n| `WithLogger(*slog.Logger)`        | Set a logger for debug output                       | `slog.New(slog.DiscardHandler)` |\n\n## Cache Status Headers\n\nThis package sets a cache status header on every response:\n\n- `X-Httpcache-Status`: The primary, detailed cache status header (always set).\n- `X-From-Cache`: (Legacy) Provided for compatibility with [`gregjones/httpcache`](https://github.com/gregjones/httpcache). Only set for cache hits/stale/revalidated responses.\n\n### Header Value Mapping\n\n| X-Httpcache-Status | X-From-Cache | Description                        |\n| ------------------ | ------------ | ---------------------------------- |\n| HIT                | 1            | Served from cache                  |\n| STALE              | 1            | Served from cache but stale        |\n| REVALIDATED        | 1            | Revalidated with origin            |\n| MISS               | *(not set)*  | Served from origin                 |\n| BYPASS             | *(not set)*  | Bypassed cache, served from origin |\n\n### Example: Stale cache hit\n\n```http\nHTTP/1.1 200 OK\nX-Httpcache-Status: STALE\nX-From-Cache: 1\nContent-Type: application/json\n```\n\n## Limitations\n\n- **Range Requests \u0026 Partial Content:**\n  This cache does **not** support HTTP range requests or partial/incomplete responses (e.g., status code 206, `Range`/`Content-Range` headers). All requests with a `Range` header are bypassed, and 206 responses are not cached. For example:\n\n  ```http\n  GET /example.txt HTTP/1.1\n  Host: example.com\n  Range: bytes=0-99\n  ```\n\n  The above request will bypass the cache and fetch the response directly from the origin server. See [RFC 9111 §3.3-3.4](https://www.rfc-editor.org/rfc/rfc9111#section-3.3) for details.\n\n\n## RFC 9111 Compliance Matrix\n\n[![RFC 9111](https://img.shields.io/badge/RFC%209111-Compliant-brightgreen)](https://www.rfc-editor.org/rfc/rfc9111)\n\n| §   | Title                                         | Requirement | Implemented | Notes                                      |\n| --- | --------------------------------------------- | :---------: | :---------: | ------------------------------------------ |\n| 1.  | Introduction                                  |     N/A     |     N/A     | Nothing to implement                       |\n| 2.  | Overview of Cache Operation                   |     N/A     |     N/A     | Nothing to implement                       |\n| 3.  | Storing Responses in Caches                   |  Required   |      ✔️      | [Details](#storing-responses-details)      |\n| 4.  | Constructing Responses from Caches            |  Required   |      ✔️      | [Details](#constructing-responses-details) |\n| 5.  | Field Definitions                             |  Required   |      ✔️      | [Details](#field-definitions-details)      |\n| 6.  | Relationship to Applications and Other Caches |     N/A     |     N/A     | Nothing to implement                       |\n| 7.  | Security Considerations                       |     N/A     |     N/A     | Nothing to implement                       |\n| 8.  | IANA Considerations                           |     N/A     |     N/A     | Nothing to implement                       |\n| 9.  | References                                    |     N/A     |     N/A     | Nothing to implement                       |\n\n**Legend for Requirements:**\n\n| Requirement | Description                                                             |\n| ----------- | ----------------------------------------------------------------------- |\n| Required    | *Must be implemented for RFC compliance*                                |\n| Optional    | *May be implemented, but not required for compliance*                   |\n| Obsolete    | *Directive is no longer relevant as per RFC 9111*                       |\n| Deprecated  | *Directive is deprecated as per RFC 9111, but can still be implemented* |\n| N/A         | *Nothing to implement or not applicable to private caches*              |\n\n\u003cdetails id=\"storing-responses-details\"\u003e\n\u003csummary\u003e\u003cstrong\u003e§3. Storing Responses in Caches (Details)\u003c/strong\u003e\u003c/summary\u003e\n\n| §    | Title                                       | Requirement | Implemented | Notes                                        |\n| ---- | ------------------------------------------- | :---------: | :---------: | -------------------------------------------- |\n| 3.1. | Storing Header and Trailer Fields           |  Required   |      ✔️      |                                              |\n| 3.2. | Updating Stored Header Fields               |  Required   |      ✔️      |                                              |\n| 3.3. | Storing Incomplete Responses                |  Optional   |      ❌      | See [Limitations](#limitations)              |\n| 3.4. | Combining Partial Content                   |  Optional   |      ❌      | See [Limitations](#limitations)              |\n| 3.5. | Storing Responses to Authenticated Requests |     N/A     |     N/A     | Not applicable to private client-side caches |\n\n\u003c/details\u003e\n\n\u003cdetails id=\"constructing-responses-details\"\u003e\n\u003csummary\u003e\u003cstrong\u003e§4. Constructing Responses from Caches (Details)\u003c/strong\u003e\u003c/summary\u003e\n\n| §    | Title                                             | Requirement | Implemented | Notes                         |\n| ---- | ------------------------------------------------- | :---------: | :---------: | ----------------------------- |\n| 4.1. | Calculating Cache Keys with the Vary Header Field |  Required   |      ✔️      |                               |\n| 4.2. | Freshness                                         |  Required   |      ✔️      | [Details](#freshness-details) |\n\n\u003cdetails id=\"freshness-details\"\u003e\n\u003csummary\u003e\u003cem\u003e§4.2. Freshness (Subsections)\u003c/em\u003e\u003c/summary\u003e\n\n| §      | Title                           | Requirement | Implemented | Notes |\n| ------ | ------------------------------- | :---------: | :---------: | ----- |\n| 4.2.1. | Calculating Freshness Lifetime  |  Required   |      ✔️      |       |\n| 4.2.2. | Calculating Heuristic Freshness |  Required   |      ✔️      |       |\n| 4.2.3. | Calculating Age                 |  Required   |      ✔️      |       |\n| 4.2.4. | Serving Stale Responses         |  Required   |      ✔️      |       |\n\n\u003c/details\u003e\n\n| §    | Title      | Requirement | Implemented | Notes                          |\n| ---- | ---------- | :---------: | ----------- | ------------------------------ |\n| 4.3. | Validation |  Required   | ✔️           | [Details](#validation-details) |\n\n\u003cdetails id=\"validation-details\"\u003e\n\u003csummary\u003e\u003cem\u003e§4.3. Validation (Subsections)\u003c/em\u003e\u003c/summary\u003e\n\n|   §    | Title                                       | Requirement | Implemented | Notes                                                                                                                                  |\n| :----: | ------------------------------------------- | :---------: | :---------: | -------------------------------------------------------------------------------------------------------------------------------------- |\n| 4.3.1. | Sending a Validation Request                |  Required   |      ✔️      |                                                                                                                                        |\n| 4.3.2. | Handling Received Validation Request        |     N/A     |     N/A     | Not applicable to private client-side caches                                                                                           |\n| 4.3.3. | Handling a Validation Response              |  Required   |      ✔️      |                                                                                                                                        |\n| 4.3.4. | Freshening Stored Responses upon Validation |  Required   |      ✔️      |                                                                                                                                        |\n| 4.3.5. | Freshening Responses with HEAD              |  Optional   |      ❌      | Pointless, rather use conditional GETs; see [RFC 9110 §13.2.1 last para](https://datatracker.ietf.org/doc/html/rfc9110#section-13.2.1) |\n\n\u003c/details\u003e\n\n| §    | Title                         | Requirement | Implemented | Notes |\n| ---- | ----------------------------- | :---------: | :---------: | ----- |\n| 4.4. | Invalidating Stored Responses |  Required   |      ✔️      |       |\n\n\u003c/details\u003e\n\n\u003cdetails id=\"field-definitions-details\"\u003e\n\u003csummary\u003e\u003cstrong\u003e§5. Field Definitions (Details)\u003c/strong\u003e\u003c/summary\u003e\n\n| §    | Title         | Requirement | Implemented | Notes                                   |\n| ---- | ------------- | :---------: | :---------: | --------------------------------------- |\n| 5.1. | Age           |  Required   |      ✔️      |                                         |\n| 5.2. | Cache-Control |  Required   |      ✔️      | [Details](#cache-control-directives)    |\n| 5.3. | Expires       |  Required   |      ✔️      |                                         |\n| 5.4. | Pragma        | Deprecated  |      ❌      | Deprecated by RFC 9111; not implemented |\n| 5.5. | Warning       |  Obsolete   |      ❌      | Obsoleted by RFC 9111; not implemented  |\n\n\u003cdetails id=\"cache-control-directives\"\u003e\n\u003csummary\u003e\u003cem\u003e§5.2. Cache-Control Directives\u003c/em\u003e\u003c/summary\u003e\n\n| §      | Title              | Requirement | Implemented | Notes                                  |\n| ------ | ------------------ | :---------: | :---------: | -------------------------------------- |\n| 5.2.1. | Request Directives |  Optional   |      ✔️      | [Details](#request-directives-details) |\n\n\u003cdetails id=\"request-directives-details\"\u003e\n\u003csummary\u003e\u003cem\u003e§5.2.1. Request Directives (Details)\u003c/em\u003e\u003c/summary\u003e\n\n| §        | Title/Directive  | Requirement | Implemented | Notes                                                          |\n| -------- | ---------------- | :---------: | :---------: | -------------------------------------------------------------- |\n| 5.2.1.1. | `max-age`        |  Optional   |      ✔️      |                                                                |\n| 5.2.1.2. | `max-stale`      |  Optional   |      ✔️      |                                                                |\n| 5.2.1.3. | `min-fresh`      |  Optional   |      ✔️      |                                                                |\n| 5.2.1.4. | `no-cache`       |  Optional   |      ✔️      |                                                                |\n| 5.2.1.5. | `no-store`       |  Optional   |      ✔️      |                                                                |\n| 5.2.1.6. | `no-transform`   |  Optional   |      ✔️      | Compliant by default - implementation never transforms content |\n| 5.2.1.7. | `only-if-cached` |  Optional   |      ✔️      |                                                                |\n\n\u003c/details\u003e\n\n| Section                    | Requirement | Implemented | Notes                                   |\n| -------------------------- | :---------: | :---------: | --------------------------------------- |\n| 5.2.2. Response Directives |  Required   |      ✔️      | [Details](#response-directives-details) |\n\n\u003cdetails id=\"response-directives-details\"\u003e\n\u003csummary\u003e\u003cem\u003e§5.2.2. Response Directives (Details)\u003c/em\u003e\u003c/summary\u003e\n\n| §         | Title/Directive    | Requirement | Implemented | Notes                                                          |\n| --------- | ------------------ | :---------: | :---------: | -------------------------------------------------------------- |\n| 5.2.2.1.  | `max-age`          |  Required   |      ✔️      |                                                                |\n| 5.2.2.2.  | `must-revalidate`  |  Required   |      ✔️      |                                                                |\n| 5.2.2.3.  | `must-understand`  |  Required   |      ✔️      |                                                                |\n| 5.2.2.4.  | `no-cache`         |  Required   |      ✔️      | Both qualified and unqualified forms supported                 |\n| 5.2.2.5.  | `no-store`         |  Required   |      ✔️      |                                                                |\n| 5.2.2.6.  | `no-transform`     |  Required   |      ✔️      | Compliant by default - implementation never transforms content |\n| 5.2.2.7.  | `private`          |     N/A     |     N/A     | Intended for shared caches; not applicable to private caches   |\n| 5.2.2.8.  | `proxy-revalidate` |     N/A     |     N/A     | Intended for shared caches; not applicable to private caches   |\n| 5.2.2.9.  | `public`           |  Optional   |      ✔️      |                                                                |\n| 5.2.2.10. | `s-maxage`         |     N/A     |     N/A     | Intended for shared caches; not applicable to private caches   |\n\n\u003c/details\u003e\n\n| §      | Title                | Requirement | Implemented | Notes                                    |\n| ------ | -------------------- | :---------: | :---------: | ---------------------------------------- |\n| 5.2.3. | Extension Directives |  Optional   | *partially* | [Details](#extension-directives-details) |\n\n\u003cdetails id=\"extension-directives-details\"\u003e\n\u003csummary\u003e\u003cem\u003e§5.2.3. Extension Directives (Details)\u003c/em\u003e\u003c/summary\u003e\n\nThe following additional cache control directives are supported, as defined in various RFCs:\n\n| Reference                                                        | Directive                | Notes                                  |\n| ---------------------------------------------------------------- | ------------------------ | -------------------------------------- |\n| [RFC 5861, §3](https://www.rfc-editor.org/rfc/rfc5861#section-3) | `stale-while-revalidate` | Only applies to responses              |\n| [RFC 5861, §4](https://www.rfc-editor.org/rfc/rfc5861#section-4) | `stale-if-error`         | Applies to both requests and responses |\n| [RFC 8246, §2](https://www.rfc-editor.org/rfc/rfc8246)           | `immutable`              | Only applies to responses              |\n\n\u003c/details\u003e\n\u003c/details\u003e\n\u003c/details\u003e\n\n## License\n\nThis project is licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). See the [LICENSE](LICENSE) file for details.\n\n## Notes\n\n[^1]: No configuration is needed beyond the cache backend DSN. Caching is handled automatically based on HTTP headers and directives. To use a custom upstream transport, pass it with the `WithUpstream` option. This lets you add `httpcache` to your existing HTTP client with minimal changes. See [Options](#options) for details.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbartventer%2Fhttpcache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbartventer%2Fhttpcache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbartventer%2Fhttpcache/lists"}