HTTP Caching
HTTP caching is a mechanism that stores responses associated with specific requests and reuses those stored responses for subsequent identical requests. This reduces the load on upstream servers and improves the performance of your infrastructure.
HTTP Cache Middleware
Goma Gateway's HTTP Cache Middleware enables you to implement caching for your routes, ensuring faster response times and reduced server load. It follows the caching rules of RFC 9111 (HTTP Caching).
- Storage: in memory by default; in Redis, shared by all gateway instances, when Redis is configured on the gateway (
gateway.redis). - Scope: only
GETresponses on paths matching the middleware'spathsare cached.pathsis required: a cache middleware withoutpathscaches nothing. - Invalidation: entries expire after
maxTtl; an unsafe method (POST,PUT,DELETE) on the same key invalidates it (see Cache key).
Cache Status Header
The middleware adds a X-Cache-Status header to responses, indicating the cache status for each request. The possible values are:
- HIT: The response was served directly from the cache, and the request did not reach the upstream application.
- MISS: The response was fetched from the upstream application and not from the cache.
- BYPASS: The request or response did not meet the criteria for HTTP caching, so caching was bypassed.
When the status is BYPASS, an X-Goma-Cache-Reason header says which rule
declined it — for example the response is marked no-store or the request carried credentials. The same reason is logged at info level the first time a
route hits it, and at debug level afterwards, so a cache that never fills can be
diagnosed without reading the source. X-Goma-Cache carries the same status and
is not suppressed by disableCacheStatusHeader.
Middleware Configuration Options
The HTTP Cache Middleware provides the following configuration options:
-
maxTtl(integer, default=300):
The maximum time-to-live (in seconds) for cached responses. After this duration, cached responses expire and are invalidated. -
maxStale(integer, default=0):
Currently has no effect. A stale entry is served only when the request itself sendsCache-Control: max-stale=<seconds>. -
memoryLimit(string, default=64Mi):
Maximum memory used by the in-memory cache. Supported units includeKi,Mi,Gi,Ti, orK,M,G,T(e.g.,1Mifor 1 mebibyte). An invalid value falls back to the default. -
disableCacheStatusHeader(boolean, default=false):
When set totrue, prevents the middleware from adding theX-Cache-StatusandX-Goma-Cache-Reasonheaders to responses. -
cacheableStatusCodes(array of integers):
When set, only responses with one of these status codes are cached, andexcludedResponseCodesis ignored. -
excludedResponseCodes(array of strings):
Status codes or ranges for which caching is disabled, e.g.["404", "500-599"]. Used only whencacheableStatusCodesis empty. When neither is set, only2xxand3xxresponses are cached, minus a built-in list of error codes. -
includeQueryInKey(boolean, default=false):
Include the query string in the cache key. Whenfalse, requests that differ only by query share one entry. -
queryParamsToCache(array of strings):
WithincludeQueryInKey: true, only these query parameters are part of the key. Empty means all of them. -
cachePrivateResponses(boolean, default=false):
Allows responses to requests that carried credentials to be cached, keyed per caller. See What is not cached before enabling it. -
ignoreVary(array of strings):
ResponseVaryfields to disregard. Use it for a backend that advertises aVaryits bodies do not actually honour: the field is then left out of the cache key, so every caller shares one entry instead of each getting their own.
What is not cached
The cache is shared by every caller of the route, so anything that would let one caller read another's response stays out of it:
- Requests carrying credentials — an
Authorization,Proxy-AuthorizationorCookieheader — bypass the cache in both directions. SetcachePrivateResponses: trueto cache them anyway; the key then includes a fingerprint of those credentials, so each caller gets their own entry. - Responses that set a cookie, carry
WWW-Authenticate, or are markedCache-Control: private,no-storeorno-cache.no-cacheis refused rather than stored-and-revalidated because this cache has no conditional-request path, so a stored copy could never be served. - Responses with
Vary: *, or varying on more than three headers — past that, the same resource would occupy an unreasonable number of entries.
Responses that vary
A response with a Vary header is stored under a secondary key built from the
request's values for the fields it names, which is what RFC 9111 asks a cache to
do. Two callers who differ on those fields get their own entry; callers who agree
share one. The Vary is replayed on the cached response so caches downstream key
on it too.
Three kinds of field never reach the key:
Accept-Encoding— already part of the base key.Authorization,Proxy-AuthorizationandCookie— a credentialed request never reads a shared entry in the first place, and undercachePrivateResponsesthe key already includes a fingerprint of them. AVarynaming them tells the cache nothing it has not already enforced. This matters in practice:raw.githubusercontent.com, for one, answersVary: Authorization, Accept-Encodingon public files that are identical for everyone.- Anything listed in
ignoreVary.
Cache key
The cache key is the route name, host, negotiated encoding, path, and — when
enabled — the query parameters and the caller fingerprint, plus the secondary
key described above when the response varies. Responses to credentialed requests
are returned with Cache-Control: private so caches between Goma and the browser
do not store them either.
An unsafe method (POST, PUT, DELETE) invalidates the entry for the base
key and the one the same caller would have read. Entries stored for other
callers' Vary values are left to expire — they cannot be enumerated from a
single request.
Example Configuration
Below is an example configuration for the HTTP Cache Middleware in YAML format:
middlewares:
- name: httpCache
type: httpCache
paths:
- ^/store/items/(.*)$
- /store/categories/.*
- /api/stores/(.*)/items/(.*)
rule:
maxTtl: 60
memoryLimit: 500Mi # Supported units: Ki, Mi, Gi, Ti or K, M, G, T
disableCacheStatusHeader: true
cacheableStatusCodes: [200, 203, 204, 300, 301, 302, 404]
excludedResponseCodes: [] # e.g., ["404", "500-599"]
includeQueryInKey: false # Whether to include query parameters in the cache key
queryParamsToCache: [] # List of specific query parameters to include in the cache key
ignoreVary: [] # Response Vary fields to leave out of the cache key
Notes
- Paths:
pathsentries are regular expressions matched from the start of the path. See Path patterns.
For example:
^/store/items/(.*)$matches paths starting with/store/items/./store/categories/.*matches all paths under/store/categories/./api/stores/(.*)/items/(.*)matches dynamic paths under/api/stores/.
Cache only specific query params
- Query Parameters: You can choose to include or exclude query parameters in the cache key. Use
includeQueryInKeyto enable or disable this feature, andqueryParamsToCacheto specify which query parameters should be considered for caching.
middlewares:
- name: httpCache
type: httpCache
paths:
- /v1/items
rule:
maxTtl: 300
memoryLimit: 500Mi # Supported units: Ki, Mi, Gi, Ti or K, M, G, T
disableCacheStatusHeader: true
cacheableStatusCodes: [200]
excludedResponseCodes: [] # e.g., ["404", "500-599"]
includeQueryInKey: true # Whether to include query parameters in the cache key
queryParamsToCache:
- page
- limit
- category
In this example, the HTTP Cache Middleware is configured to cache responses for the /v1/items endpoint. The cache will consider only the page, limit, and category query parameters when determining the cache key. This allows for more granular caching based on these specific parameters, while ignoring any other query parameters that may be present in the request.
/v1/items?page=1&utm_source=google=> cached as/v1/items?page=1/v1/items?page=2&session_id=xyz=> cached as/v1/items?page=2/v1/items?category=electronics&page=1→ cached as/v1/items?category=electronics&page=1