diff --git a/plugin/include/txn_box/Config.h b/plugin/include/txn_box/Config.h index 79da92b..01ed8ce 100644 --- a/plugin/include/txn_box/Config.h +++ b/plugin/include/txn_box/Config.h @@ -131,6 +131,9 @@ class Config { ~Config(); + swoc::Errata load_file_glob(swoc::TextView pattern, swoc::TextView cfg_key); + swoc::Errata load_file(swoc::file::path const& cfg_path, swoc::TextView cfg_key); + /** Parse YAML from @a node to initialize @a this configuration. * * @param root Root node. @@ -421,6 +424,29 @@ class Config { * @see Extractor::validate */ swoc::Rv validate(Extractor::Spec &spec); + + // Configuration file tracking. + class FileInfo { + using self_type = FileInfo; + public: + using key_type = swoc::TextView; + + FileInfo(key_type key) : _path(key) {} + + // IntrusiveHashMap support. + static key_type key_of(self_type *self) { return self->_path; } + static bool equal(key_type lhs, key_type rhs) { return lhs == rhs; } + static size_t hash_of(key_type key) { return std::hash()(key); } + static self_type *& next_ptr(self_type * self) { return self->_next; } + static self_type *& prev_ptr(self_type * self) { return self->_prev; } + + protected: + key_type _path; ///< Absolute path to file. + self_type * _next = nullptr; + self_type * _prev = nullptr; + }; + + swoc::IntrusiveHashMap _cfg_files; }; inline Hook Config::current_hook() const { return _hook; } diff --git a/plugin/include/txn_box/Directive.h b/plugin/include/txn_box/Directive.h index 5db6a9c..59f387d 100644 --- a/plugin/include/txn_box/Directive.h +++ b/plugin/include/txn_box/Directive.h @@ -21,16 +21,14 @@ class Context; -/** Base class for directives. - * - */ +/// Base class for directives. class Directive { using self_type = Directive; ///< Self reference type. friend Config; friend Context; public: - /// Import global value for convienence. + /// Import global value for convenience. static constexpr swoc::TextView DO_KEY = Global::DO_KEY; /// Generic handle for all directives. @@ -45,6 +43,15 @@ class Directive { */ using InstanceLoader = std::function (Config& cfg, YAML::Node drtv_node, swoc::TextView const& name, swoc::TextView const& arg, YAML::Node key_value)>; + /** Functor to do config level initialization. + * + * @param cfg Configuration object. + * + * This is called once per directive class when the @c Config instance is initialized. This should + * perform any initialization needed for the directive as a type, rather than as an instance used + * in the configuration. The most common use is if the directive needs space in a @c Context - + * that space must be reserved during the invocation of this functor. + */ using CfgInitializer = std::function; /** Information about a directive type. @@ -81,6 +88,13 @@ class Directive { */ virtual swoc::Errata invoke(Context &ctx) = 0; + /** Configuration initializer. + * + * @param Config& Configuration object. + * @return Errors, if any. + * + * Default implementation that does nothing. Override as needed. + */ static swoc::Errata cfg_init(Config&) { return {}; } protected: diff --git a/plugin/include/txn_box/common.h b/plugin/include/txn_box/common.h index 8412700..d5a72f2 100644 --- a/plugin/include/txn_box/common.h +++ b/plugin/include/txn_box/common.h @@ -20,6 +20,8 @@ #include #include +constexpr swoc::TextView DEBUG_TAG = "txn_box"; + // Forward declares class Config; class Context; @@ -31,6 +33,10 @@ template < typename ... Args > swoc::Errata Error(std::string_view const& fmt, A return std::move(swoc::Errata().note_v(swoc::Severity::ERROR, fmt, std::forward_as_tuple(args...))); } +template < typename ... Args > swoc::Errata Warning(std::string_view const& fmt, Args && ... args) { + return std::move(swoc::Errata().note_v(swoc::Severity::WARN, fmt, std::forward_as_tuple(args...))); +} + /// Separate a name and argument for a directive or extractor. extern swoc::Rv parse_arg(swoc::TextView & key); diff --git a/plugin/include/txn_box/ts_util.h b/plugin/include/txn_box/ts_util.h index bc22949..1d29321 100644 --- a/plugin/include/txn_box/ts_util.h +++ b/plugin/include/txn_box/ts_util.h @@ -28,6 +28,22 @@ extern std::array::value> TS_Hook; namespace ts { +template < typename ... Args > +void DebugMsg(swoc::TextView fmt, Args && ... args) { + swoc::LocalBufferWriter<1024> w; + auto arg_pack = std::forward_as_tuple(args...); + w.print_v(fmt, arg_pack); + if (! w.error()) { + TSDebug("txn_box", "%.*s", w.size(), w.data()); + } + // Do it the hard way. + std::vector buff; + buff.resize(w.extent()); + swoc::FixedBufferWriter fw(buff.data(), buff.size()); + fw.print_v(fmt, arg_pack); + TSDebug("txn_box", "%.*s", fw.size(), fw.data()); +} + /** Hold a string allocated from TS core. * This provides both a view of the string and clean up when destructed. */ @@ -120,14 +136,9 @@ class URL : public HeapObject { */ self_type & host_set(swoc::TextView const& host); - in_port_t port_get() { - return TSUrlPortGet(_buff, _loc); - } + in_port_t port_get(); - self_type & port_set(in_port_t port) { - TSUrlPortSet(_buff, _loc, port); - return *this; - } + self_type & port_set(in_port_t port); self_type & path_set(swoc::TextView path) { TSUrlPathSet(_buff, _loc, path.data(), path.size()); @@ -265,8 +276,9 @@ class HttpRequest : public HttpHeader { */ URL url(); - swoc::TextView method() const { int length; auto text = TSHttpHdrMethodGet(_buff, _loc, &length); return { text, static_cast(length) }; } + swoc::TextView method() const; swoc::TextView host() const; + in_port_t port() const; /** Set the @a host for the request. * @@ -275,10 +287,19 @@ class HttpRequest : public HttpHeader { * * This will update the request as little as possible. If the URL does not contain a host * then it is unmodified and the @c Host field is set to @a host. If the URL has a host and - * there is no @c Host field, only the URL is updated. + * there is no @c Host field, only the URL is updated. The port is not updated. */ bool host_set(swoc::TextView const& host); + /** Set the port. + * + * @param port Port in host order. + * @return If the port was updated. + * + * This updates the URL and @c Host field as needed, making as few changes as possible. + */ + bool port_set(in_port_t port); + bool scheme_set(swoc::TextView const& scheme); }; @@ -449,6 +470,8 @@ inline bool HeapObject::is_valid() const { return _buff != nullptr && _loc != nu inline URL::URL(TSMBuffer buff, TSMLoc loc) : super_type(buff, loc) {} +inline in_port_t URL::port_get() { return TSUrlPortGet(_buff, _loc); } + inline swoc::TextView URL::scheme() const { int length; auto text = TSUrlSchemeGet(_buff, _loc, &length); return { text, static_cast(length) }; } inline swoc::TextView URL::path() const { int length; auto text = TSUrlPathGet(_buff, _loc, &length); return { text, static_cast(length) }; } @@ -462,6 +485,11 @@ inline URL &URL::host_set(swoc::TextView const& host) { return *this; } +inline auto URL::port_set(in_port_t port) -> self_type & { + TSUrlPortSet(_buff, _loc, port); + return *this; +} + inline URL &URL::scheme_set(swoc::TextView const& scheme) { if (this->is_valid()) { TSUrlSchemeSet(_buff, _loc, scheme.data(), scheme.size()); diff --git a/plugin/src/Features.cc b/plugin/src/Features.cc index 34641c2..adb5b9e 100644 --- a/plugin/src/Features.cc +++ b/plugin/src/Features.cc @@ -323,14 +323,7 @@ Feature Ex_ua_req_host::extract(Context &ctx, Spec const&) { FeatureView zret; zret._direct_p = true; if ( auto hdr { ctx.creq_hdr() } ; hdr.is_valid()) { - if ( ts::URL url { hdr.url() } ; url.is_valid()) { - zret = url.host(); - if (zret.data() == nullptr) { // not in the URL, look in the HOST field. - if ( auto field { hdr.field(ts::HTTP_FIELD_HOST) } ; field.is_valid()) { - zret = field.value(); - } - } - } + zret = hdr.host(); } return zret; } @@ -451,6 +444,20 @@ ts::HttpHeader Ex_ua_req_field::hdr(Context & ctx) const { return ctx.creq_hdr(); } // ----- +class Ex_proxy_req_field : public ExHttpField { +public: + static constexpr TextView NAME { "proxy-req-field" }; + +protected: + TextView const& key() const override; + ts::HttpHeader hdr(Context & ctx) const override; +}; + +TextView const& Ex_proxy_req_field::key() const { return NAME; } +ts::HttpHeader Ex_proxy_req_field::hdr(Context & ctx) const { + return ctx.preq_hdr(); +} +// ----- class Ex_proxy_rsp_field : public ExHttpField { public: static constexpr TextView NAME { "proxy-rsp-field" }; @@ -898,6 +905,7 @@ Ex_proxy_req_host proxy_req_host; Ex_proxy_req_path proxy_req_path; Ex_proxy_req_url proxy_req_url; Ex_proxy_req_scheme proxy_req_scheme; +Ex_proxy_req_field proxy_req_field; Ex_proxy_rsp_status proxy_rsp_status; Ex_proxy_rsp_field proxy_rsp_field; @@ -946,6 +954,7 @@ Ex_remainder_feature ex_remainder_feature; Extractor::define(Ex_proxy_req_url::NAME, &proxy_req_url); Extractor::define(Ex_proxy_req_path::NAME, &proxy_req_path); Extractor::define(Ex_proxy_req_scheme::NAME, &proxy_req_scheme); + Extractor::define(Ex_proxy_req_field::NAME, &proxy_req_field); Extractor::define(Ex_proxy_rsp_status::NAME, &proxy_rsp_status); Extractor::define(Ex_proxy_rsp_field::NAME, &proxy_rsp_field); diff --git a/plugin/src/Machinery.cc b/plugin/src/Machinery.cc index d78ab1a..360e4f0 100644 --- a/plugin/src/Machinery.cc +++ b/plugin/src/Machinery.cc @@ -34,6 +34,57 @@ using namespace swoc::literals; /* ------------------------------------------------------------------------------------ */ Feature Generic::extract() const { return NIL_FEATURE; } /* ------------------------------------------------------------------------------------ */ +class Do_ua_req_url_host : public Directive { + using super_type = Directive; + using self_type = Do_ua_req_url_host; +public: + static const std::string KEY; + static const HookMask HOOKS; ///< Valid hooks for directive. + + /** Construct with feature expression.. + * + * @param expr Feature expression. + */ + Do_ua_req_url_host(Expr && expr); + + Errata invoke(Context &ctx) override; + static Rv load( Config& cfg, YAML::Node drtv_node, swoc::TextView const& name + , swoc::TextView const& arg, YAML::Node key_value); +protected: + Expr _expr; ///< Feature expression. +}; + +const std::string Do_ua_req_url_host::KEY {"ua-req-url-host" }; +const HookMask Do_ua_req_url_host::HOOKS {MaskFor({Hook::PREQ, Hook::PRE_REMAP, Hook::POST_REMAP}) }; + +Do_ua_req_url_host::Do_ua_req_url_host(Expr && expr) : _expr(std::move(expr)) {} + +Errata Do_ua_req_url_host::invoke(Context &ctx) { + if (auto hdr{ctx.creq_hdr()}; hdr.is_valid()) { + if (auto url{hdr.url()}; url.is_valid()) { + auto value = ctx.extract(_expr); + if (auto host = std::get_if(&value); nullptr != host) { + url.host_set(*host); + } + } + } + return {}; +} + +swoc::Rv Do_ua_req_url_host::load(Config& cfg, YAML::Node drtv_node, swoc::TextView const& name, swoc::TextView const& arg, YAML::Node key_value) { + auto && [ expr, errata ] { cfg.parse_expr(key_value) }; + if (! errata.is_ok()) { + errata.info(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark()); + return std::move(errata); + } + if (! expr.result_type().can_satisfy(STRING)) { + return Error(R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), STRING); + } + return Handle(new self_type{std::move(expr)}); +} + +// --- + class Do_proxy_req_url_host : public Directive { using super_type = Directive; using self_type = Do_proxy_req_url_host; @@ -41,26 +92,95 @@ class Do_proxy_req_url_host : public Directive { static const std::string KEY; static const HookMask HOOKS; ///< Valid hooks for directive. - explicit Do_proxy_req_url_host(TextView text) : _host(text) {} + /** Construct with feature expression.. + * + * @param expr Feature expression. + */ + Do_proxy_req_url_host(Expr && expr); Errata invoke(Context &ctx) override; static Rv load( Config& cfg, YAML::Node drtv_node, swoc::TextView const& name , swoc::TextView const& arg, YAML::Node key_value); - std::string _host; +protected: + Expr _expr; ///< Host feature expression. }; const std::string Do_proxy_req_url_host::KEY {"proxy-req-url-host" }; const HookMask Do_proxy_req_url_host::HOOKS {MaskFor({Hook::PREQ, Hook::PRE_REMAP, Hook::POST_REMAP}) }; +Do_proxy_req_url_host::Do_proxy_req_url_host(Expr && expr) : _expr(std::move(expr)) {} + Errata Do_proxy_req_url_host::invoke(Context &ctx) { - Errata zret; - return zret; + if (auto hdr{ctx.preq_hdr()}; hdr.is_valid()) { + if (auto url{hdr.url()}; url.is_valid()) { + auto value = ctx.extract(_expr); + if (auto host = std::get_if(&value); nullptr != host) { + url.host_set(*host); + } + } + } + return {}; } swoc::Rv Do_proxy_req_url_host::load(Config& cfg, YAML::Node drtv_node, swoc::TextView const& name, swoc::TextView const& arg, YAML::Node key_value) { - return { Handle{new self_type{key_value.Scalar()}}, {} }; + auto && [ expr, errata ] { cfg.parse_expr(key_value) }; + if (! errata.is_ok()) { + errata.info(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark()); + return std::move(errata); + } + if (! expr.result_type().can_satisfy(STRING)) { + return Error(R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), STRING); + } + return Handle(new self_type{std::move(expr)}); } +// --- + +class Do_remap_req_url_host : public Directive { + using super_type = Directive; + using self_type = Do_remap_req_url_host; +public: + static const std::string KEY; + static const HookMask HOOKS; ///< Valid hooks for directive. + + /** Construct with feature expression.. + * + * @param expr Feature expression. + */ + Do_remap_req_url_host(Expr && expr); + + Errata invoke(Context &ctx) override; + static Rv load( Config& cfg, YAML::Node drtv_node, swoc::TextView const& name + , swoc::TextView const& arg, YAML::Node key_value); +protected: + Expr _expr; ///< Host feature expression. +}; + +const std::string Do_remap_req_url_host::KEY {"remap-url-host" }; +const HookMask Do_remap_req_url_host::HOOKS {MaskFor({Hook::PREQ, Hook::PRE_REMAP, Hook::POST_REMAP}) }; + +Do_remap_req_url_host::Do_remap_req_url_host(Expr && expr) : _expr(std::move(expr)) {} + +Errata Do_remap_req_url_host::invoke(Context &ctx) { + auto value = ctx.extract(_expr); + if (auto host = std::get_if(&value); nullptr != host) { + ts::URL(ctx._remap_info->requestBufp, ctx._remap_info->requestUrl).host_set(*host); + ctx._remap_status = TSREMAP_DID_REMAP; + } + return {}; +} + +swoc::Rv Do_remap_req_url_host::load(Config& cfg, YAML::Node drtv_node, swoc::TextView const& name, swoc::TextView const& arg, YAML::Node key_value) { + auto && [ expr, errata ] { cfg.parse_expr(key_value) }; + if (! errata.is_ok()) { + errata.info(R"(While parsing "{}" directive at {}.)", KEY, drtv_node.Mark()); + return std::move(errata); + } + if (! expr.result_type().can_satisfy(STRING)) { + return Error(R"(Value for "{}" directive at {} must be a {}.)", KEY, drtv_node.Mark(), STRING); + } + return Handle(new self_type{std::move(expr)}); +} /* ------------------------------------------------------------------------------------ */ /** Set the host for the request. * This updates both the URL and the "Host" field, if appropriate. @@ -72,11 +192,11 @@ class Do_ua_req_host : public Directive { static const std::string KEY; ///< Directive name. static const HookMask HOOKS; ///< Valid hooks for directive. - /** Construct with feature extractor @a fmt. + /** Construct with feature expression.. * - * @param fmt Feature for host. + * @param expr Feature expression. */ - Do_ua_req_host(Expr && fmt); + Do_ua_req_host(Expr && expr); /** Invoke directive. * @@ -98,18 +218,20 @@ class Do_ua_req_host : public Directive { , swoc::TextView const& arg, YAML::Node key_value); protected: - Expr _fmt; ///< Host feature. + Expr _expr; ///< Host feature. }; const std::string Do_ua_req_host::KEY {"ua-req-host" }; const HookMask Do_ua_req_host::HOOKS {MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::POST_REMAP}) }; -Do_ua_req_host::Do_ua_req_host(Expr &&fmt) : _fmt(std::move(fmt)) {} +Do_ua_req_host::Do_ua_req_host(Expr &&expr) : _expr(std::move(expr)) {} Errata Do_ua_req_host::invoke(Context &ctx) { - TextView host{std::get(ctx.extract(_fmt))}; if (auto hdr{ctx.creq_hdr()}; hdr.is_valid()) { - hdr.host_set(host); + auto value = ctx.extract(_expr); + if (auto host = std::get_if(&value); nullptr != host) { + hdr.host_set(*host); + } } return {}; } diff --git a/plugin/src/ip_space.cc b/plugin/src/ip_space.cc index 2f5faa3..72588e4 100644 --- a/plugin/src/ip_space.cc +++ b/plugin/src/ip_space.cc @@ -243,12 +243,15 @@ class Do_ip_space_define : public Directive { int _line_no = 0; ///< For debugging name conflicts. + /// YAML key names. + ///@{ static const std::string NAME_TAG; static const std::string PATH_TAG; static const std::string COLUMNS_TAG; static const std::string DURATION_TAG; static const std::string TYPE_TAG; static const std::string VALUES_TAG; + ///@} SpaceHandle acquire_space() { std::shared_lock lock(_space_mutex); diff --git a/plugin/src/ts_util.cc b/plugin/src/ts_util.cc index 62a6981..876b826 100644 --- a/plugin/src/ts_util.cc +++ b/plugin/src/ts_util.cc @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include @@ -16,16 +16,8 @@ #include #include #include -#include - -#include "txn_box/Directive.h" -#include "txn_box/Extractor.h" -#include "txn_box/Modifier.h" -#include "txn_box/Config.h" -#include "txn_box/Context.h" #include "txn_box/ts_util.h" -#include "txn_box/yaml_util.h" using swoc::TextView; using swoc::Errata; @@ -224,23 +216,90 @@ ts::URL ts::HttpRequest::url() { return {}; } +TextView ts::HttpRequest::host() const { + auto url { const_cast(this)->url() }; + if (auto host = url.host() ; ! host.empty()) { + return host; + } + if (auto field = this->field(HTTP_FIELD_HOST) ; field.is_valid()) { + auto value = field.value(); + TextView host_token, port_token, rest_token; + if (swoc::IPEndpoint::tokenize(value, &host_token, &port_token)) { + return host_token; + } + } + return {}; +} + +in_port_t ts::HttpRequest::port() const { + auto url { const_cast(this)->url() }; + if (auto port = url.port_get() ; port != 0) { + return port; + } + if (auto field = this->field(HTTP_FIELD_HOST) ; field.is_valid()) { + auto value = field.value(); + TextView host_token, port_token, rest_token; + if (swoc::IPEndpoint::tokenize(value, &host_token, &port_token)) { + return swoc::svtoi(port_token); + } + } + return 0; +} + bool ts::HttpRequest::host_set(swoc::TextView const &host) { auto url{this->url()}; + bool force_host_p = true; if (!url.host().empty()) { url.host_set(host); - if (auto field{this->field(HTTP_FIELD_HOST)}; field.is_valid()) { + force_host_p = false; + } + auto field{this->field(HTTP_FIELD_HOST)}; + if (field.is_valid()) { + auto text = field.value(); + TextView host_token, port_token, rest_token; + if (swoc::IPEndpoint::tokenize(text, &host_token, &port_token)) { + size_t n = host.size() + 1 + port_token.size(); + swoc::FixedBufferWriter w{static_cast(alloca(n)), n}; + w.print("{}:{}", host, port_token); + field.assign(w.view()); + } else { // It's messed up, do the best we can by setting it to a valid value. field.assign(host); } - } else { - this->field_obtain(HTTP_FIELD_HOST).assign(host); + } else if (force_host_p) { + this->field_create(HTTP_FIELD_HOST).assign(host); } return true; -}; +} + +bool ts::HttpRequest::port_set(in_port_t port) { + auto url{this->url()}; + if (!url.host().empty()) { + url.port_set(port); + } + auto field{this->field(HTTP_FIELD_HOST)}; + if (field.is_valid()) { + auto text = field.value(); + TextView host_token, port_token, rest_token; + if (swoc::IPEndpoint::tokenize(text, &host_token, &port_token)) { + size_t n = host_token.size() + 1 + std::numeric_limits::max_digits10; + swoc::FixedBufferWriter w{static_cast(alloca(n)), n}; + w.print("{}:{}", host_token, port); + field.assign(w.view()); + } + } + return true; +} bool ts::HttpRequest::scheme_set(swoc::TextView const &scheme) { this->url().scheme_set(scheme); return true; -}; +} + +swoc::TextView HttpRequest::method() const { int length; + char const *text; + text = TSHttpHdrMethodGet(_buff, _loc, &length); + return {text, static_cast(length) }; +} ts::HttpRequest ts::HttpTxn::creq_hdr() { TSMBuffer buff; diff --git a/plugin/src/txn_box.cc b/plugin/src/txn_box.cc index 2b1dffa..a508dd1 100644 --- a/plugin/src/txn_box.cc +++ b/plugin/src/txn_box.cc @@ -8,6 +8,8 @@ #include #include #include + +#include #include #include @@ -35,12 +37,13 @@ using namespace swoc::literals; /* ------------------------------------------------------------------------------------ */ Global G; +extern std::string glob_to_rxp(TextView glob); const std::string Config::ROOT_KEY { "txn_box" }; Hook Convert_TS_Event_To_TxB_Hook(TSEvent ev) { static const std::map table{ - {TS_EVENT_HTTP_READ_REQUEST_HDR, Hook::CREQ} + {TS_EVENT_HTTP_READ_REQUEST_HDR, Hook::CREQ} , {TS_EVENT_HTTP_SEND_REQUEST_HDR, Hook::PREQ} , {TS_EVENT_HTTP_READ_RESPONSE_HDR, Hook::URSP} , {TS_EVENT_HTTP_SEND_RESPONSE_HDR, Hook::PRSP} @@ -54,9 +57,55 @@ Hook Convert_TS_Event_To_TxB_Hook(TSEvent ev) { } namespace { + std::shared_ptr Plugin_Config; + } // namespace /* ------------------------------------------------------------------------------------ */ +Errata Config::load_file(swoc::file::path const& cfg_path, TextView cfg_key) { + if (auto spot = _cfg_files.find(cfg_path.view()) ; spot != _cfg_files.end() ) { + ts::DebugMsg("Skipping {} - already loaded", cfg_path); + return {}; + } + auto fi = _arena.make(this->localize(cfg_path.view())); + _cfg_files.insert(fi); + // Try loading and parsing the file. + auto &&[root, yaml_errata ]{yaml_load(cfg_path)}; + if (!yaml_errata.is_ok()) { + yaml_errata.info(R"(While loading file "{}".)", cfg_path); + return std::move(yaml_errata); + } + + // Process the YAML data. + auto errata = Plugin_Config->parse_yaml(root, cfg_key); + if (!errata.is_ok()) { + errata.info(R"(While parsing key "{}" in configuration file "{}".)", cfg_key, cfg_path); + return std::move(errata); + } + + return {}; +} +/* ------------------------------------------------------------------------------------ */ +Errata Config::load_file_glob(TextView pattern, swoc::TextView cfg_key) { + int flags = 0; + glob_t files; + auto err_f = [](char const* path, int err) -> int { return 0; }; + swoc::file::path abs_pattern = ts::make_absolute(pattern); + int result = glob(abs_pattern.c_str(), flags, err_f, &files); + if (result == GLOB_NOMATCH) { + return Warning(R"(The pattern "{}" did not match any files.)", abs_pattern); + } + for ( size_t idx = 0 ; idx < files.gl_pathc ; ++idx) { + auto errata = this->load_file(swoc::file::path(files.gl_pathv[idx]), cfg_key); + if (! errata.is_ok()) { + errata.info(R"(While processing pattern "{}".)", pattern); + return std::move(errata); + } + } + globfree(&files); + return {}; +} +/* ------------------------------------------------------------------------------------ */ void Global::reserve_txn_arg() { if (G.TxnArgIdx < 0) { auto && [ idx, errata ] { ts::HttpTxn::reserve_arg(Config::ROOT_KEY, "Transaction Box") }; @@ -92,7 +141,7 @@ TxnBoxInit(int argc, char const *argv[]) { , "solidwallofcode@verizonmedia.com"}; Plugin_Config.reset(new Config); - TextView cfg_path { "txn_box.yaml" }; +// swoc::file::path cfg_path { "txn_box.yaml" }; TextView cfg_key { Config::ROOT_KEY }; int opt; int idx; @@ -101,7 +150,11 @@ TxnBoxInit(int argc, char const *argv[]) { switch (opt) { case ':':errata.error("'{}' requires a value", argv[optind - 1]); break; - case 'c': cfg_path.assign(argv[optind-1], strlen(argv[optind-1])); + case 'c': + errata = Plugin_Config->load_file_glob({argv[optind-1], strlen(argv[optind-1])}, cfg_key); + if (!errata.is_ok()) { + return std::move(errata); + } break; case 'k': cfg_key.assign(argv[optind-1], strlen(argv[optind-1])); break; @@ -110,24 +163,6 @@ TxnBoxInit(int argc, char const *argv[]) { } } - if (!errata.is_ok()) { - return std::move(errata); - } - - // Try loading and parsing the file. - auto &&[root, yaml_errata ]{yaml_load(cfg_path)}; - if (!yaml_errata.is_ok()) { - yaml_errata.info(R"(While loading file "{}".)", cfg_path); - return std::move(yaml_errata); - } - - // Process the YAML data. - errata = Plugin_Config->parse_yaml(root, cfg_key); - if (!errata.is_ok()) { - errata.info(R"(While parsing key "{}" in configuration file "{}".)", cfg_key, cfg_path); - return std::move(errata); - } - if (TSPluginRegister(&info) == TS_SUCCESS) { auto& post_load_directives = Plugin_Config->hook_directives(Hook::POST_LOAD); if (post_load_directives.size() > 0) { diff --git a/test/autest/gold_tests/autest-site/txn_box.test.ext b/test/autest/gold_tests/autest-site/txn_box.test.ext index e57b1b0..28c2eee 100755 --- a/test/autest/gold_tests/autest-site/txn_box.test.ext +++ b/test/autest/gold_tests/autest-site/txn_box.test.ext @@ -51,3 +51,63 @@ def ConfigureTsForTxnBox(run, ts, server, txn_box_config=None, txn_box_key='meta ########################################################################## ExtendTestRun(ConfigureTsForTxnBox, name="ConfigureTsForTxnBox") + + +def TxnBoxTestRun(self, text, replay_path, **kw): + """ + Set up a standard test run for TxnBox + + Args: + text: Description for test run. + replay_path: Path to the replay file. + Keywords + replay: The replay file. + """ + + config_key = kw.get("config_key") + config_path = kw.get("config_path", replay_path) + run = self.AddTestRun(text) + + ts = self.MakeATSProcess("ts") + run.Variables.TS = ts; + pv_client = run.AddVerifierClientProcess( + "pv-client", ts, replay_path, + http_ports=[ts.Variables.port]) + + pv_server = run.AddVerifierServerProcess("pv-server", replay_path) + + # Put the txn_box.so into the sandbox. + plugin_dir = ts.Env['PROXY_CONFIG_PLUGIN_PLUGIN_DIR'] + from os.path import dirname + git_root = dirname(dirname(dirname(ts.TestRoot))) + txn_box_lib = os.path.join(git_root, "lib", "txn_box.so") + ts.Setup.Copy(txn_box_lib, plugin_dir, CopyLogic.SoftFiles) + + # Configure txn_box in Traffic Server. + txn_box_command = 'txn_box.so ' + + if config_key: + txn_box_command += ' --key {}'.format(config_key) + + if config_path: + ts.Setup.Copy(config_path, ts.Variables.CONFIGDIR) + txn_box_command += ' --config {}'.format(os.path.basename(config_path)) + + ts.Disk.plugin_config.AddLine(txn_box_command) + + # Configure Traffic Server to use the DNS process. + dns = self.MakeDNServer("dns", ip='127.0.0.1', default=['127.0.0.1']) + + ts.Disk.records_config.update({ + 'proxy.config.dns.nameservers': '127.0.0.1:{0}'.format(dns.Variables.Port), + 'proxy.config.dns.resolv_conf': 'NULL', +# 'proxy.config.plugin.dynamic_reload': 0 + }) + + run.Processes.Default.StartBefore(pv_server) + run.Processes.Default.StartBefore(ts, ready=When.PortOpen(ts.Variables.port)) + run.Processes.Default.StartBefore(dns) + + return run + +ExtendTest(TxnBoxTestRun, name="TxnBoxTestRun") diff --git a/test/autest/gold_tests/ct_header/ct_header.test.py b/test/autest/gold_tests/ct_header/ct_header.test.py index 2da2cf7..a6fe9b4 100644 --- a/test/autest/gold_tests/ct_header/ct_header.test.py +++ b/test/autest/gold_tests/ct_header/ct_header.test.py @@ -10,15 +10,4 @@ Test.Summary = ''' Verify txn_box can filter fields as expected. ''' - -ts = Test.MakeATSProcess("ts") - -# -# Test 1: Verify that a field can be changed in both upstream and downstream -# -r = Test.AddTestRun("Verify txn_box can filter fields as expected.") -client = r.AddVerifierClientProcess( - "client1", ts, "ct_header.replay.yaml", - http_ports=[ts.Variables.port]) -server = r.AddVerifierServerProcess("server1", "ct_header.replay.yaml") -r.ConfigureTsForTxnBox(ts, server, "ct_header.replay.yaml") +r = Test.TxnBoxTestRun("Test HTTP field manipuation", "ct_header.replay.yaml", config_key="meta.txn_box") diff --git a/test/autest/gold_tests/static_file/static_file.replay.yaml b/test/autest/gold_tests/static_file/static_file.replay.yaml index 1f3d011..feab7d6 100644 --- a/test/autest/gold_tests/static_file/static_file.replay.yaml +++ b/test/autest/gold_tests/static_file/static_file.replay.yaml @@ -22,6 +22,16 @@ meta: - upstream-rsp-status: [ 200 , "OK" ] - proxy-rsp-body: "Contents of security.txt" + - when: proxy-req + do: + - with: proxy-req-path + select: + - match: "concert.txt" + do: + - proxy-req-field: + - proxy-req-field + - else: text-block + txn-box-remap: - when: proxy-rsp do: @@ -125,3 +135,60 @@ sessions: proxy-response: status: 200 + - all: { headers: { fields: [[ uuid, 4 ]]}} + client-request: + version: "1.1" + scheme: "http" + method: "GET" + url: "/concert.txt" + headers: + fields: + - [ Host, example.one ] + proxy-request: + version: "1.1" + scheme: "http" + method: "GET" + url: "/concert.txt" + headers: + fields: + - [ Author-i-tay, "Delain Concert.", equal ] + server-response: + status: 200 + content: + size: 140 + headers: + fields: + - [ Content-Type, text/html ] + - [ Content-Length, 140 ] + proxy-response: + status: 200 + + - all: { headers: { fields: [[ uuid, 5 ]]}} + client-request: + version: "1.1" + scheme: "http" + method: "GET" + url: "/concert.txt" + headers: + fields: + - [ Host, example.one ] + - [ Author-i-tay, "Nightwish Concert." ] + proxy-request: + version: "1.1" + scheme: "http" + method: "GET" + url: "/concert.txt" + headers: + fields: + - [ Author-i-tay, "Nightwish Concert.", equal ] + server-response: + status: 200 + content: + size: 50 + headers: + fields: + - [ Content-Type, text/html ] + - [ Content-Length, 50 ] + proxy-response: + status: 200 + diff --git a/test/autest/gold_tests/static_file/static_file.test.py b/test/autest/gold_tests/static_file/static_file.test.py index e2a9a62..c8d6068 100644 --- a/test/autest/gold_tests/static_file/static_file.test.py +++ b/test/autest/gold_tests/static_file/static_file.test.py @@ -7,27 +7,12 @@ # SPDX-License-Identifier: Apache-2.0 # -import os - Test.Summary = ''' Server static file as response body. ''' - -ts = Test.MakeATSProcess("ts") - -# -# Test 1: Verify that a field can be changed in both upstream and downstream -# -r = Test.AddTestRun("Serve static file content.") -client = r.AddVerifierClientProcess( - "client1", ts, "static_file.replay.yaml", - http_ports=[ts.Variables.port]) -server = r.AddVerifierServerProcess("server1", "static_file.replay.yaml") -r.ConfigureTsForTxnBox(ts, server, "static_file.replay.yaml") +r = Test.TxnBoxTestRun("Test static file support", "static_file.replay.yaml", config_key="meta.txn_box") +ts = r.Variables.TS ts.Setup.Copy("static_file.txt", ts.Variables.CONFIGDIR) ts.Disk.remap_config.AddLine( - 'map http://example.one http://example.one @plugin=txn_box.so @pparam=../../static_file.replay.yaml @pparam=meta.txn-box-remap' + 'map http://example.one http://example.one @plugin=txn_box.so @pparam=static_file.replay.yaml @pparam=meta.txn-box-remap' ) -ts.Disk.records_config.update({ - 'proxy.config.plugin.dynamic_reload_mode': 0, -})