|
| 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