Skip to content
Merged
38 changes: 38 additions & 0 deletions doc/admin-guide/plugins/cache_range_requests.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,44 @@ status code is reset back to 206, which leads to the object not being cached.

This option is useful when used with other plugins, such as Cache Promote.

Cache Complete Responses
------------------------

.. option:: --cache-complete-responses
.. option:: -r

This option causes the plugin to cache complete responses (200 OK). By default,
only 206 Partial Content responses are cached by this plugin; without this flag,
any 200 OK observed will be marked as not cacheable.

This option is intended to cover the case when an origin responds with a 200 OK
when the requested range exceeds the size of the object. For example, if an object
is 500 bytes, and the requested range is for bytes 0-5000, some origins will
respond with a 206 and a `Content-Range` header, while others may respond with a
200 OK and no `Content-Range` header. The same origin that responds with a 200 OK
when the requested range exceeds the object size will serve 206s when the range is
smaller than or within the bytes of the object.

**NOTE:** This option *should be used carefully* with full knowledge of how
cache keys are set for a given remap rule that relies on this behavior and origin
response mechanics. For example, when this option is the sole argument to
`cache_range_requests.so` and no other plugins are in use, the behavior could be
abused, especially if the origin always responds with 200 OKs. This is because
the plugin will automatically include the requested `Range` in the cache key.
This means that arbitrary ranges can be used to pollute the cache with different
combinations of ranges, which will lead to many copies of the same complete object
stored under different cache keys.

For this reason, if the plugin is instructed to cache complete responses, `Range`
request headers coming into the remap should ideally be normalized. Normalization
can be accomplished by using the slice plugin *without* the `--ref-relative` argument
which is disabled by default. The cache key plugin can also be used to tightly control
the construction of the cache key itself.

The preferred means of using this plugin option is with the following plugins:
- slice to normalize the requested ranges, *without* the `--ref-relative` option
- cachekey to control the cache key, including the `Range` header normalized by slice
- cache range requests with `--no-modify-cachekey` and `--cache-complete-responses`

Configuration examples
======================
Expand Down
19 changes: 18 additions & 1 deletion plugins/cache_range_requests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ X-CRR-IMS header support
Consider using the header_rewrite plugin to protect the parent
from using this option as an attack vector against an origin.

Object Cacheability:
Object Cacheability

Normally objects are forced into the cache by changing the status code in the
response from the upstream host from 206 to 200. The default behavior is to
perform this operation blindly without checking cacheability. Add the `-v`
Expand All @@ -95,3 +96,19 @@ Object Cacheability:

<from-url> <to-url> @plugin=cache_range_requests.so @pparam=--verify-cacheability
<from-url> <to-url> @plugin=cache_range_requests.so @pparam=-v

Caching Complete Responses

To enable caching of complete responses, that is, a 200 OK instead of a 206 Partial
Content response, add the `-r` flag to the plugin parameters. By default, complete
responses are marked as uncacheable.

Global Plugin (plugin.config):

cache_range_requests.so --cache-complete-responses
cache_range_requests.so -r

Remap Plugin (remap.config):

<from-url> <to-url> @plugin=cache_range_requests.so @pparam=--cache-complete-responses
<from-url> <to-url> @plugin=cache_range_requests.so @pparam=-r
36 changes: 24 additions & 12 deletions plugins/cache_range_requests/cache_range_requests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ struct pluginconfig {
bool consider_ims_header{false};
bool modify_cache_key{true};
bool verify_cacheability{false};
bool cache_complete_responses{false};
std::string ims_header;
};

Expand All @@ -62,6 +63,7 @@ struct txndata {
TSHttpStatus origin_status{TS_HTTP_STATUS_PARTIAL_CONTENT};
time_t ims_time{0};
bool verify_cacheability{false};
bool cache_complete_responses{false};
};

// pluginconfig struct (global plugin only)
Expand Down Expand Up @@ -104,6 +106,7 @@ create_pluginconfig(int argc, char *const argv[])
{const_cast<char *>("no-modify-cachekey"), no_argument, nullptr, 'n'},
{const_cast<char *>("ps-cachekey"), no_argument, nullptr, 'p'},
{const_cast<char *>("verify-cacheability"), no_argument, nullptr, 'v'},
{const_cast<char *>("cache-complete-responses"), no_argument, nullptr, 'r'},
{nullptr, 0, nullptr, 0},
};

Expand Down Expand Up @@ -139,6 +142,10 @@ create_pluginconfig(int argc, char *const argv[])
DEBUG_LOG("Plugin verifies whether the object in the transaction is cacheable");
pc->verify_cacheability = true;
} break;
case 'r': {
DEBUG_LOG("Plugin allows complete responses (200 OK) to be cached");
pc->cache_complete_responses = true;
} break;
default: {
} break;
}
Expand Down Expand Up @@ -268,7 +275,8 @@ range_header_check(TSHttpTxn txnp, pluginconfig *const pc)
}
}

txn_state->verify_cacheability = pc->verify_cacheability;
txn_state->verify_cacheability = pc->verify_cacheability;
txn_state->cache_complete_responses = pc->cache_complete_responses;
}

// remove the range request header.
Expand Down Expand Up @@ -381,23 +389,27 @@ handle_server_read_response(TSHttpTxn txnp, txndata *const txn_state)
txn_state->origin_status = status;
if (TS_HTTP_STATUS_PARTIAL_CONTENT == status) {
DEBUG_LOG("Got TS_HTTP_STATUS_PARTIAL_CONTENT.");

DEBUG_LOG("Set response header to TS_HTTP_STATUS_OK.");
// changing the status code from 206 to 200 forces the object into cache
TSHttpHdrStatusSet(resp_buf, resp_loc, TS_HTTP_STATUS_OK);
DEBUG_LOG("Set response header to TS_HTTP_STATUS_OK.");

// check if transaction is cacheable
bool const cacheable = TSHttpTxnIsCacheable(txnp, nullptr, resp_buf);
DEBUG_LOG("range is cacheable: %d", cacheable);
DEBUG_LOG("verify cacheability: %d", txn_state->verify_cacheability);

if (txn_state->verify_cacheability && !cacheable) {
if (txn_state->verify_cacheability && !TSHttpTxnIsCacheable(txnp, nullptr, resp_buf)) {
DEBUG_LOG("transaction is not cacheable; resetting status code to 206");
TSHttpHdrStatusSet(resp_buf, resp_loc, TS_HTTP_STATUS_PARTIAL_CONTENT);
}
} else if (TS_HTTP_STATUS_OK == status) {
DEBUG_LOG("The origin does not support range requests, disabling cache write.");
if (TS_SUCCESS != TSHttpTxnCntlSet(txnp, TS_HTTP_CNTL_SERVER_NO_STORE, true)) {
DEBUG_LOG("Unable to disable cache write for this transaction.");
bool cacheable = txn_state->cache_complete_responses;

if (cacheable && txn_state->verify_cacheability) {
DEBUG_LOG("Received a cacheable complete response from the origin; verifying cacheability");
cacheable = TSHttpTxnIsCacheable(txnp, nullptr, resp_buf);
}

// 200s are cached by default; only cache if configured to do so
if (!cacheable && TS_SUCCESS == TSHttpTxnCntlSet(txnp, TS_HTTP_CNTL_SERVER_NO_STORE, true)) {
DEBUG_LOG("Cache write has been disabled for this transaction.");
} else {
DEBUG_LOG("Allowing object to be cached.");
}
}
TSHandleMLocRelease(resp_buf, TS_NULL_MLOC, resp_loc);
Expand Down
Loading