Skip to content

Commit c82d967

Browse files
committed
Implement RFC 9213 Targeted HTTP Cache Control
This adds support for targeted Cache-Control headers (like CDN-Cache-Control) that allow cache directives to be targeted at specific caches. The implementation includes a configurable, priority-ordered list of targeted headers via proxy.config.http.cache.targeted_cache_control_headers, which is overridable per-remap rule. When a targeted header is present, it takes precedence over the standard Cache-Control header for caching decisions. Targeted headers are passed through downstream to allow proper cache hierarchy behavior. Fixes: #9113
1 parent d74c795 commit c82d967

File tree

18 files changed

+593
-12
lines changed

18 files changed

+593
-12
lines changed

doc/admin-guide/configuration/cache-basics.en.rst

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,84 @@ Traffic Server applies ``Cache-Control`` servability criteria after HTTP
234234
freshness criteria. For example, an object might be considered fresh but will
235235
not be served if its age is greater than its ``max-age``.
236236

237+
Targeted Cache Control (RFC 9213)
238+
----------------------------------
239+
240+
Traffic Server supports `RFC 9213 <https://httpwg.org/specs/rfc9213.html>`_
241+
Targeted HTTP Cache Control, which allows origin servers to provide different
242+
cache directives for different classes of caches. This is particularly useful in CDN deployments where you want to
243+
give different caching instructions to CDN caches versus browser caches.
244+
245+
For example, an origin server might send::
246+
247+
Cache-Control: max-age=60
248+
CDN-Cache-Control: max-age=3600
249+
250+
When targeted cache control is enabled (via
251+
:ts:cv:`proxy.config.http.cache.targeted_cache_control_headers`), Traffic
252+
Server will use the ``CDN-Cache-Control`` directives instead of the standard
253+
``Cache-Control`` directives for caching decisions. The browser receiving the
254+
response will see both headers and use the standard ``Cache-Control``, allowing
255+
the object to be cached for 60 seconds in the browser but 3600 seconds in the CDN.
256+
257+
Configuration
258+
~~~~~~~~~~~~~
259+
260+
To enable targeted cache control, set
261+
:ts:cv:`proxy.config.http.cache.targeted_cache_control_headers` to a
262+
comma-separated list of header names to check in priority order::
263+
264+
# In records.yaml:
265+
proxy.config.http.cache.targeted_cache_control_headers: CDN-Cache-Control
266+
267+
Or with multiple targeted headers in priority order::
268+
269+
proxy.config.http.cache.targeted_cache_control_headers: ATS-Cache-Control,CDN-Cache-Control
270+
271+
This configuration is overridable per-remap, allowing different rules for
272+
different origins::
273+
274+
# In remap.config:
275+
map / https://origin.example.com/ @plugin=conf_remap.so \
276+
@pparam=proxy.config.http.cache.targeted_cache_control_headers=CDN-Cache-Control
277+
278+
Behavior
279+
~~~~~~~~
280+
281+
- When a targeted header is found (first match in the priority list), its
282+
directives replace the standard ``Cache-Control`` directives for all caching
283+
decisions.
284+
285+
- If no targeted headers are present or they are all empty, Traffic Server falls
286+
back to the standard ``Cache-Control`` header.
287+
288+
- Targeted headers are passed through to downstream caches, allowing CDN chains
289+
to use the same directives.
290+
291+
- All standard cache control directives are supported in targeted headers:
292+
``max-age``, ``s-maxage``, ``no-cache``, ``no-store``, ``private``,
293+
``must-revalidate``, etc.
294+
295+
Use Cases
296+
~~~~~~~~~
297+
298+
**CDN with origin cache**: An origin might have its own caching layer but want
299+
CDNs to cache more aggressively::
300+
301+
Cache-Control: max-age=60
302+
CDN-Cache-Control: max-age=86400
303+
304+
**Different CDN policies**: Using multiple CDN providers with different needs::
305+
306+
Cache-Control: max-age=300
307+
CDN1-Cache-Control: max-age=3600
308+
CDN2-Cache-Control: max-age=1800
309+
310+
**Prevent CDN caching while allowing browser caching**::
311+
312+
Cache-Control: max-age=300
313+
CDN-Cache-Control: no-store
314+
237315
Revalidating HTTP Objects
238316
-------------------------
239317

doc/admin-guide/files/records.yaml.en.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2479,6 +2479,31 @@ Cache Control
24792479
``Cache-Control: max-age``.
24802480
===== ======================================================================
24812481

2482+
.. ts:cv:: CONFIG proxy.config.http.cache.targeted_cache_control_headers STRING ""
2483+
:reloadable:
2484+
:overridable:
2485+
2486+
Comma-separated list of targeted cache control header names to check in priority
2487+
order before falling back to the standard ``Cache-Control`` header. This implements
2488+
`RFC 9213 <https://httpwg.org/specs/rfc9213.html>`_ Targeted HTTP Cache Control.
2489+
When empty (the default), targeted cache control is disabled and only the standard
2490+
``Cache-Control`` header is used.
2491+
2492+
Example values:
2493+
2494+
- ``CDN-Cache-Control`` - Use only CDN-Cache-Control if present
2495+
- ``ATS-Cache-Control,CDN-Cache-Control`` - Check ATS-Cache-Control first, then
2496+
CDN-Cache-Control, then fall back to Cache-Control
2497+
2498+
When a targeted header is found, its directives are used rather than those in the
2499+
standard ``Cache-Control`` header for caching decisions. The targeted headers are
2500+
passed through to downstream caches.
2501+
2502+
.. note::
2503+
2504+
This implementation uses the existing Cache-Control parser rather than the
2505+
strict RFC 8941 Structured Fields parser specified in RFC 9213.
2506+
24822507
.. ts:cv:: CONFIG proxy.config.http.cache.max_stale_age INT 604800
24832508
:reloadable:
24842509
:overridable:

doc/admin-guide/plugins/lua.en.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4394,6 +4394,7 @@ Http config constants
43944394
TS_LUA_CONFIG_NET_SOCK_NOTSENT_LOWAT
43954395
TS_LUA_CONFIG_BODY_FACTORY_RESPONSE_SUPPRESSION_MODE
43964396
TS_LUA_CONFIG_HTTP_CACHE_POST_METHOD
4397+
TS_LUA_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS
43974398
TS_LUA_CONFIG_LAST_ENTRY
43984399

43994400
:ref:`TOP <admin-plugins-ts-lua>`

doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ TSOverridableConfigKey Value Config
196196
:enumerator:`TS_CONFIG_NET_SOCK_NOTSENT_LOWAT` :ts:cv:`proxy.config.net.sock_notsent_lowat`
197197
:enumerator:`TS_CONFIG_BODY_FACTORY_RESPONSE_SUPPRESSION_MODE` :ts:cv:`proxy.config.body_factory.response_suppression_mode`
198198
:enumerator:`TS_CONFIG_HTTP_CACHE_POST_METHOD` :ts:cv:`proxy.config.http.cache.post_method`
199+
:enumerator:`TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS` :ts:cv:`proxy.config.http.cache.targeted_cache_control_headers`
199200
====================================================================== ====================================================================
200201

201202
Examples

doc/developer-guide/api/types/TSOverridableConfigKey.en.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ Enumeration Members
163163
.. enumerator:: TS_CONFIG_HTTP_NO_DNS_JUST_FORWARD_TO_PARENT
164164
.. enumerator:: TS_CONFIG_HTTP_CACHE_IGNORE_QUERY
165165
.. enumerator:: TS_CONFIG_HTTP_CACHE_POST_METHOD
166+
.. enumerator:: TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS
166167

167168

168169
Description

include/proxy/hdrs/MIME.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ struct MIMEHdrImpl : public HdrHeapObjImpl {
325325
void check_strings(HeapCheck *heaps, int num_heaps);
326326

327327
// Cooked values
328-
void recompute_cooked_stuff(MIMEField *changing_field_or_null = nullptr);
328+
void recompute_cooked_stuff(MIMEField *changing_field_or_null = nullptr, const char *targeted_headers_str = nullptr);
329329
void recompute_accelerators_and_presence_bits();
330330

331331
// Utility

include/proxy/http/HttpConfig.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,9 @@ struct OverridableHttpConfigParams {
545545
MgmtByte cache_range_write = 0;
546546
MgmtByte allow_multi_range = 0;
547547

548+
char *targeted_cache_control_headers = nullptr; // This does not get free'd by us!
549+
size_t targeted_cache_control_headers_len = 0; // Updated when targeted headers are set.
550+
548551
MgmtByte ignore_accept_mismatch = 0;
549552
MgmtByte ignore_accept_language_mismatch = 0;
550553
MgmtByte ignore_accept_encoding_mismatch = 0;

include/ts/apidefs.h.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,7 @@ enum TSOverridableConfigKey {
908908
TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RETRY_BACKOFF_BASE,
909909
TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST,
910910
TS_CONFIG_HTTP_CACHE_POST_METHOD,
911+
TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS,
911912
TS_CONFIG_LAST_ENTRY,
912913
};
913914

plugins/lua/ts_lua_http_config.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ typedef enum {
154154
TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_RETRY_BACKOFF_BASE = TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RETRY_BACKOFF_BASE,
155155
TS_LUA_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST = TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST,
156156
TS_LUA_CONFIG_HTTP_CACHE_POST_METHOD = TS_CONFIG_HTTP_CACHE_POST_METHOD,
157+
TS_LUA_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS = TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS,
157158
TS_LUA_CONFIG_LAST_ENTRY = TS_CONFIG_LAST_ENTRY,
158159
} TSLuaOverridableConfigKey;
159160

@@ -299,6 +300,7 @@ ts_lua_var_item ts_lua_http_config_vars[] = {
299300
TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_RETRY_BACKOFF_BASE),
300301
TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST),
301302
TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CACHE_POST_METHOD),
303+
TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS),
302304
TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_LAST_ENTRY),
303305
};
304306

src/api/InkAPI.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7462,6 +7462,9 @@ _conf_to_memberp(TSOverridableConfigKey conf, OverridableHttpConfigParams *overr
74627462
case TS_CONFIG_HTTP_CACHE_POST_METHOD:
74637463
ret = _memberp_to_generic(&overridableHttpConfig->cache_post_method, conv);
74647464
break;
7465+
case TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS:
7466+
ret = _memberp_to_generic(&overridableHttpConfig->targeted_cache_control_headers, conv);
7467+
break;
74657468
case TS_CONFIG_HTTP_REQUEST_BUFFER_ENABLED:
74667469
ret = _memberp_to_generic(&overridableHttpConfig->request_buffer_enabled, conv);
74677470
break;
@@ -7748,6 +7751,15 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char
77487751
s->t_state.my_txn_conf().global_user_agent_header_size = 0;
77497752
}
77507753
break;
7754+
case TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS:
7755+
if (value && length > 0) {
7756+
s->t_state.my_txn_conf().targeted_cache_control_headers = const_cast<char *>(value); // The "core" likes non-const char*
7757+
s->t_state.my_txn_conf().targeted_cache_control_headers_len = length;
7758+
} else {
7759+
s->t_state.my_txn_conf().targeted_cache_control_headers = nullptr;
7760+
s->t_state.my_txn_conf().targeted_cache_control_headers_len = 0;
7761+
}
7762+
break;
77517763
case TS_CONFIG_BODY_FACTORY_TEMPLATE_BASE:
77527764
if (value && length > 0) {
77537765
s->t_state.my_txn_conf().body_factory_template_base = const_cast<char *>(value);
@@ -7860,6 +7872,10 @@ TSHttpTxnConfigStringGet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char
78607872
*value = sm->t_state.txn_conf->global_user_agent_header;
78617873
*length = sm->t_state.txn_conf->global_user_agent_header_size;
78627874
break;
7875+
case TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS:
7876+
*value = sm->t_state.txn_conf->targeted_cache_control_headers;
7877+
*length = sm->t_state.txn_conf->targeted_cache_control_headers_len;
7878+
break;
78637879
case TS_CONFIG_BODY_FACTORY_TEMPLATE_BASE:
78647880
*value = sm->t_state.txn_conf->body_factory_template_base;
78657881
*length = sm->t_state.txn_conf->body_factory_template_base_len;

0 commit comments

Comments
 (0)