Skip to content

Commit 27478a7

Browse files
Add config file support for cache_fill (#12347)
1 parent 0c91131 commit 27478a7

File tree

6 files changed

+583
-24
lines changed

6 files changed

+583
-24
lines changed

plugins/experimental/cache_fill/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515
#
1616
#######################
1717

18-
add_atsplugin(cache_fill background_fetch.cc cache_fill.cc)
18+
add_atsplugin(cache_fill background_fetch.cc cache_fill.cc configs.cc rules.cc)
1919

2020
verify_remap_plugin(cache_fill)

plugins/experimental/cache_fill/cache_fill.cc

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "ts/remap.h"
4040
#include "ts/remap_version.h"
4141
#include "background_fetch.h"
42+
#include "configs.h"
4243

4344
static const char *
4445
getCacheLookupResultName(TSCacheLookupResult result)
@@ -108,23 +109,29 @@ cont_check_cacheable(TSHttpTxn txnp)
108109
// if a background fetch is allowed for this request
109110
//
110111
static int
111-
cont_handle_cache(TSCont /* contp ATS_UNUSED */, TSEvent event, void *edata)
112+
cont_handle_cache(TSCont contp, TSEvent event, void *edata)
112113
{
113-
TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
114-
if (TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE == event) {
115-
bool const requested = cont_check_cacheable(txnp);
116-
if (requested) // Made a background fetch request, do not cache the response
117-
{
118-
Dbg(dbg_ctl, "setting no store");
119-
TSHttpTxnCntlSet(txnp, TS_HTTP_CNTL_SERVER_NO_STORE, true);
120-
TSHttpTxnCacheLookupStatusSet(txnp, TS_CACHE_LOOKUP_MISS);
121-
}
114+
TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
115+
BgFetchConfig *config = static_cast<BgFetchConfig *>(TSContDataGet(contp));
116+
117+
if (nullptr == config) {
118+
// something seriously wrong..
119+
TSError("[%s] Can't get configurations", PLUGIN_NAME);
120+
} else if (config->bgFetchAllowed(txnp)) {
121+
if (TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE == event) {
122+
bool const requested = cont_check_cacheable(txnp);
123+
if (requested) // Made a background fetch request, do not cache the response
124+
{
125+
Dbg(dbg_ctl, "setting no store");
126+
TSHttpTxnCntlSet(txnp, TS_HTTP_CNTL_SERVER_NO_STORE, true);
127+
TSHttpTxnCacheLookupStatusSet(txnp, TS_CACHE_LOOKUP_MISS);
128+
}
122129

123-
} else {
124-
TSError("[%s] Unknown event for this plugin %d", PLUGIN_NAME, event);
125-
Dbg(dbg_ctl, "unknown event for this plugin %d", event);
130+
} else {
131+
TSError("[%s] Unknown event for this plugin %d", PLUGIN_NAME, event);
132+
Dbg(dbg_ctl, "unknown event for this plugin %d", event);
133+
}
126134
}
127-
128135
// Reenable and continue with the state machine.
129136
TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
130137
return 0;
@@ -147,19 +154,46 @@ TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
147154
// We don't have any specific "instances" here, at least not yet.
148155
//
149156
TSReturnCode
150-
TSRemapNewInstance(int /* argc ATS_UNUSED */, char ** /* argv ATS_UNUSED */, void **ih, char * /* errbuf ATS_UNUSED */,
151-
int /* errbuf_size ATS_UNUSED */)
157+
TSRemapNewInstance(int argc, char **argv, void **ih, char * /* errbuf ATS_UNUSED */, int /* errbuf_size ATS_UNUSED */)
152158
{
153-
TSCont cont = TSContCreate(cont_handle_cache, nullptr);
154-
*ih = cont;
155-
return TS_SUCCESS;
159+
TSCont cont = TSContCreate(cont_handle_cache, nullptr);
160+
BgFetchConfig *config = new BgFetchConfig(cont);
161+
bool success = true;
162+
163+
// The first two arguments are the "from" and "to" URL string. We need to
164+
// skip them, but we also require that there be an option to masquerade as
165+
// argv[0], so we increment the argument indexes by 1 rather than by 2.
166+
argc--;
167+
argv++;
168+
169+
// This is for backwards compatibility, ugly! ToDo: Remove for ATS v9.0.0 IMO.
170+
if (argc > 1 && *argv[1] != '-') {
171+
Dbg(dbg_ctl, "config file %s", argv[1]);
172+
if (!config->readConfig(argv[1])) {
173+
success = false;
174+
}
175+
} else {
176+
if (!config->parseOptions(argc, const_cast<const char **>(argv))) {
177+
success = false;
178+
}
179+
}
180+
181+
if (success) {
182+
*ih = config;
183+
184+
return TS_SUCCESS;
185+
}
186+
187+
// Something went wrong with the configuration setup.
188+
delete config;
189+
return TS_ERROR;
156190
}
157191

158192
void
159193
TSRemapDeleteInstance(void *ih)
160194
{
161-
TSCont cont = static_cast<TSCont>(ih);
162-
TSContDestroy(cont);
195+
BgFetchConfig *config = static_cast<BgFetchConfig *>(ih);
196+
delete config;
163197
}
164198

165199
///////////////////////////////////////////////////////////////////////////////
@@ -171,8 +205,8 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo * /* rri */)
171205
if (nullptr == ih) {
172206
return TSREMAP_NO_REMAP;
173207
}
174-
TSCont const cont = static_cast<TSCont>(ih);
175-
TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, cont);
208+
BgFetchConfig *config = static_cast<BgFetchConfig *>(ih);
209+
TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, config->getCont());
176210
Dbg(dbg_ctl, "TSRemapDoRemap() added hook");
177211

178212
return TSREMAP_NO_REMAP;
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/** @file
2+
3+
Plugin to perform background fetches of certain content that would
4+
otherwise not be cached. For example, Range: requests / responses.
5+
6+
@section license License
7+
8+
Licensed to the Apache Software Foundation (ASF) under one
9+
or more contributor license agreements. See the NOTICE file
10+
distributed with this work for additional information
11+
regarding copyright ownership. The ASF licenses this file
12+
to you under the Apache License, Version 2.0 (the
13+
"License"); you may not use this file except in compliance
14+
with the License. You may obtain a copy of the License at
15+
16+
http://www.apache.org/licenses/LICENSE-2.0
17+
18+
Unless required by applicable law or agreed to in writing, software
19+
distributed under the License is distributed on an "AS IS" BASIS,
20+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21+
See the License for the specific language governing permissions and
22+
limitations under the License.
23+
*/
24+
25+
#include <getopt.h>
26+
#include <cstdio>
27+
#include <memory.h>
28+
29+
#include "configs.h"
30+
#include "background_fetch.h"
31+
32+
#include <swoc/TextView.h>
33+
#include <swoc/swoc_file.h>
34+
#include <tsutil/ts_bw_format.h>
35+
36+
using namespace swoc::literals;
37+
38+
// Parse the command line options. This got a little wonky, since we decided to have different
39+
// syntax for remap vs global plugin initialization, and the global BG fetch state :-/. Clean up
40+
// later...
41+
bool
42+
BgFetchConfig::parseOptions(int argc, const char *argv[])
43+
{
44+
static const struct option longopt[] = {
45+
{const_cast<char *>("log"), required_argument, nullptr, 'l' },
46+
{const_cast<char *>("config"), required_argument, nullptr, 'c' },
47+
{const_cast<char *>("allow-304"), no_argument, nullptr, 'a' },
48+
{nullptr, no_argument, nullptr, '\0'}
49+
};
50+
51+
while (true) {
52+
int opt = getopt_long(argc, const_cast<char *const *>(argv), "lc", longopt, nullptr);
53+
54+
if (opt == -1) {
55+
break;
56+
}
57+
58+
switch (opt) {
59+
case 'l':
60+
Dbg(dbg_ctl, "option: log file specified: %s", optarg);
61+
_log_file = optarg;
62+
break;
63+
case 'c':
64+
Dbg(dbg_ctl, "option: config file '%s'", optarg);
65+
if (!readConfig(optarg)) {
66+
// Error messages are written in the parser
67+
return false;
68+
}
69+
break;
70+
case 'a':
71+
Dbg(dbg_ctl, "option: --allow-304 set");
72+
_allow_304 = true;
73+
break;
74+
default:
75+
TSError("[%s] invalid plugin option: %c", PLUGIN_NAME, opt);
76+
return false;
77+
break;
78+
}
79+
}
80+
81+
return true;
82+
}
83+
84+
// Read a config file, populate the linked list (chain the BgFetchRule's)
85+
bool
86+
BgFetchConfig::readConfig(const char *config_file)
87+
{
88+
if (nullptr == config_file) {
89+
TSError("[%s] invalid config file", PLUGIN_NAME);
90+
return false;
91+
}
92+
93+
swoc::file::path path(config_file);
94+
95+
Dbg(dbg_ctl, "trying to open config file in this path: %s", config_file);
96+
97+
if (!path.is_absolute()) {
98+
path = swoc::file::path(TSConfigDirGet()) / path;
99+
}
100+
Dbg(dbg_ctl, "chosen config file is at: %s", path.c_str());
101+
102+
std::error_code ec;
103+
auto content = swoc::file::load(path, ec);
104+
if (ec) {
105+
swoc::bwprint(ts::bw_dbg, "[{}] invalid config file: {} {}", PLUGIN_NAME, path, ec);
106+
TSError("%s", ts::bw_dbg.c_str());
107+
Dbg(dbg_ctl, "%s", ts::bw_dbg.c_str());
108+
return false;
109+
}
110+
111+
swoc::TextView text{content};
112+
while (text) {
113+
auto line = text.take_prefix_at('\n').ltrim_if(&isspace);
114+
115+
if (line.empty() || line.front() == '#') {
116+
continue;
117+
}
118+
119+
auto cfg_type = line.take_prefix_if(&isspace);
120+
if (cfg_type.empty()) {
121+
continue;
122+
}
123+
124+
Dbg(dbg_ctl, "setting background_fetch exclusion criterion based on string: %.*s", int(cfg_type.size()), cfg_type.data());
125+
126+
bool exclude = false;
127+
if (0 == strcasecmp(cfg_type, "exclude")) {
128+
exclude = true;
129+
} else if (0 != strcasecmp(cfg_type, "include")) {
130+
swoc::bwprint(ts::bw_dbg, "[{}] invalid specifier {}, skipping config line", PLUGIN_NAME, cfg_type);
131+
TSError("%s", ts::bw_dbg.c_str());
132+
continue;
133+
}
134+
135+
if (auto cfg_name = line.take_prefix_if(&isspace); !cfg_name.empty()) {
136+
if (auto cfg_value = line.take_prefix_if(&isspace); !cfg_value.empty()) {
137+
if ("Client-IP"_tv == cfg_name) {
138+
swoc::IPRange r;
139+
// '*' is special - match any address. Signalled by empty range.
140+
if (cfg_value.size() != 1 || cfg_value.front() == '*') {
141+
if (!r.load(cfg_value)) { // assume if it loads, it's not empty.
142+
TSError("[%s] invalid IP address range %.*s, skipping config value", PLUGIN_NAME, int(cfg_value.size()),
143+
cfg_value.data());
144+
continue;
145+
}
146+
}
147+
_rules.emplace_back(exclude, r);
148+
swoc::bwprint(ts::bw_dbg, "adding background_fetch address range rule {} for {}: {}", exclude, cfg_name, cfg_value);
149+
Dbg(dbg_ctl, "%s", ts::bw_dbg.c_str());
150+
} else if ("Content-Length"_tv == cfg_name) {
151+
BgFetchRule::size_cmp_type::OP op;
152+
if (cfg_value[0] == '<') {
153+
op = BgFetchRule::size_cmp_type::LESS_THAN_OR_EQUAL;
154+
} else if (cfg_value[0] == '>') {
155+
op = BgFetchRule::size_cmp_type::LESS_THAN_OR_EQUAL;
156+
} else {
157+
TSError("[%s] invalid Content-Length condition %.*s, skipping config value", PLUGIN_NAME, int(cfg_value.size()),
158+
cfg_value.data());
159+
continue;
160+
}
161+
++cfg_value; // Drop leading character.
162+
swoc::TextView parsed;
163+
auto n = swoc::svtou(cfg_value, &parsed);
164+
if (parsed.size() != cfg_value.size()) {
165+
TSError("[%s] invalid Content-Length size value %.*s, skipping config value", PLUGIN_NAME, int(cfg_value.size()),
166+
cfg_value.data());
167+
continue;
168+
}
169+
_rules.emplace_back(exclude, op, size_t(n));
170+
171+
swoc::bwprint(ts::bw_dbg, "adding background_fetch content length rule {} for {}: {}", exclude, cfg_name, cfg_value);
172+
Dbg(dbg_ctl, "%s", ts::bw_dbg.c_str());
173+
} else {
174+
_rules.emplace_back(exclude, cfg_name, cfg_value);
175+
swoc::bwprint(ts::bw_dbg, "adding background_fetch field compare rule {} for {}: {}", exclude, cfg_name, cfg_value);
176+
Dbg(dbg_ctl, "%s", ts::bw_dbg.c_str());
177+
}
178+
} else {
179+
TSError("[%s] invalid value %.*s, skipping config line", PLUGIN_NAME, int(cfg_name.size()), cfg_name.data());
180+
}
181+
}
182+
}
183+
184+
Dbg(dbg_ctl, "Done parsing config");
185+
186+
return true;
187+
}
188+
189+
///////////////////////////////////////////////////////////////////////////
190+
// Check the configuration (either per remap, or global), and decide if
191+
// this request is allowed to trigger a background fetch.
192+
//
193+
bool
194+
BgFetchConfig::bgFetchAllowed(TSHttpTxn txnp) const
195+
{
196+
Dbg(dbg_ctl, "Testing: request is internal?");
197+
if (TSHttpTxnIsInternal(txnp)) {
198+
return false;
199+
}
200+
201+
bool allow_bg_fetch = true;
202+
203+
// We could do this recursively, but following the linked list is probably more efficient.
204+
for (auto const &r : _rules) {
205+
if (r.check_field_configured(txnp)) {
206+
Dbg(dbg_ctl, "found %s rule match", r._exclude ? "exclude" : "include");
207+
allow_bg_fetch = !r._exclude;
208+
break;
209+
}
210+
}
211+
212+
return allow_bg_fetch;
213+
}

0 commit comments

Comments
 (0)