diff --git a/include/cripts/Connections.hpp b/include/cripts/Connections.hpp index a8e851d7ae1..88a76e896e2 100644 --- a/include/cripts/Connections.hpp +++ b/include/cripts/Connections.hpp @@ -459,10 +459,9 @@ class ConnBase void virtual _initialize() { _initialized = true; } - cripts::Transaction *_state = nullptr; - struct sockaddr const *_socket = nullptr; - TSVConn _vc = nullptr; - char _str[INET6_ADDRSTRLEN + 1]; + cripts::Transaction *_state = nullptr; + struct sockaddr const *_socket = nullptr; + TSVConn _vc = nullptr; bool _initialized = false; }; // End class ConnBase diff --git a/include/cripts/Context.hpp b/include/cripts/Context.hpp index 00bc36eafc6..19b92bc77a5 100644 --- a/include/cripts/Context.hpp +++ b/include/cripts/Context.hpp @@ -18,6 +18,7 @@ #pragma once #include +#include #include #include "ts/ts.h" #include "ts/remap.h" @@ -28,7 +29,7 @@ #include "cripts/Connections.hpp" // These are pretty arbitrary for now -constexpr int CONTEXT_DATA_SLOTS = 4; +constexpr int CONTEXT_DATA_SLOTS = 16; namespace cripts { @@ -132,23 +133,16 @@ class Context } _cache; struct _UrlBlock { - cripts::Client::URL &request; - cripts::Pristine::URL pristine; - cripts::Parent::URL parent; + cripts::Client::URL &request; + std::unique_ptr pristine; + std::unique_ptr parent; struct { - cripts::Remap::From::URL from; - cripts::Remap::To::URL to; + std::unique_ptr from; + std::unique_ptr to; } remap; - _UrlBlock(Context *ctx, cripts::Client::URL &alias) : request(alias) - { - request.set_context(ctx); - pristine.set_context(ctx); - parent.set_context(ctx); - remap.from.set_context(ctx); - remap.to.set_context(ctx); - } + _UrlBlock(Context *ctx, cripts::Client::URL &alias) : request(alias) { request.set_context(ctx); } } _urls; }; // End class Context diff --git a/include/cripts/Error.hpp b/include/cripts/Error.hpp index da674abe600..f0957c7bf68 100644 --- a/include/cripts/Error.hpp +++ b/include/cripts/Error.hpp @@ -17,6 +17,7 @@ */ #pragma once +#include #include #include "ts/ts.h" @@ -132,10 +133,10 @@ class Error void Execute(cripts::Context *context); private: - Reason _reason; - Status _status; - bool _failed = false; - bool _redirect = false; + std::unique_ptr _reason; + Status _status; + bool _failed = false; + bool _redirect = false; }; } // namespace cripts diff --git a/include/cripts/Urls.hpp b/include/cripts/Urls.hpp index c28f1d1dab8..73580aa068f 100644 --- a/include/cripts/Urls.hpp +++ b/include/cripts/Urls.hpp @@ -18,6 +18,7 @@ #pragma once #include +#include #include #include #include @@ -338,7 +339,7 @@ class Url cripts::string_view GetSV() override; cripts::string operator+=(cripts::string_view add); - self_type operator=(cripts::string_view path); + self_type &operator=(cripts::string_view path); String operator[](Segments::size_type ix); void @@ -346,8 +347,10 @@ class Url { auto p = operator[](ix); - _size -= p.size(); - p.operator=(""); + if (_state && ix < _state->segments.size()) { + _state->size -= p.size(); + p.operator=(""); + } } void @@ -368,7 +371,7 @@ class Url void Flush() { - if (_modified) { + if (_state && _state->modified) { operator=(GetSV()); } } @@ -376,10 +379,23 @@ class Url private: void _parser(); - bool _modified = false; - Segments _segments; // Lazy loading on this - cripts::string _storage; // Used when recombining the segments into a full path - cripts::string::size_type _size = 0; // Mostly a guestimate for managing _storage + struct State { + bool modified = false; + Segments segments; // Ordered list of path segments + cripts::string storage; // Used when recombining the segments into a full path + cripts::string::size_type size = 0; // Mostly a guestimate for managing storage + }; + + State & + _ensure_state() + { + if (!_state) { + _state = std::make_unique(); + } + return *_state; + } + + std::unique_ptr _state; // Lazily allocated when path is parsed or modified }; // End class Url::Path @@ -461,18 +477,18 @@ class Url using Component::Component; - Query(cripts::string_view load) + Query(cripts::string_view load) : _state(std::make_unique()) { - _data = load; - _size = load.size(); - _loaded = true; - _standalone = true; + _data = load; + _state->size = load.size(); + _loaded = true; + _state->standalone = true; } void Reset() override; cripts::string_view GetSV() override; - self_type operator=(cripts::string_view query); + self_type &operator=(cripts::string_view query); cripts::string operator+=(cripts::string_view add); Parameter operator[](cripts::string_view param); void Erase(cripts::string_view param); @@ -482,7 +498,9 @@ class Url Erase() { operator=(""); - _size = 0; + if (_state) { + _state->size = 0; + } } void @@ -503,14 +521,14 @@ class Url // Make sure the hash and vector are populated _parser(); - std::ranges::sort(_ordered); - _modified = true; + std::ranges::sort(_state->ordered); + _state->modified = true; } void Flush() { - if (_modified) { + if (_state && _state->modified) { operator=(GetSV()); } } @@ -518,19 +536,33 @@ class Url private: void _parser(); - bool _modified = false; - bool _standalone = false; // This component is used outside of a URL owner, not common - OrderedParams _ordered; // Ordered vector of all parameters, can be sorted etc. - HashParams _hashed; // Unordered map to go from "name" to the query parameter - cripts::string _storage; // Used when recombining the query params into a - // full query string - cripts::string::size_type _size = 0; // Mostly a guesttimate + struct State { + bool modified = false; + bool standalone = false; // This component is used outside of a URL owner, not common + OrderedParams ordered; // Ordered vector of all parameters, can be sorted etc. + HashParams hashed; // Unordered map to go from "name" to the query parameter + cripts::string storage; // Used when recombining the query params into a full query string + cripts::string::size_type size = 0; // Mostly a guesttimate + }; + + State & + _ensure_state() + { + if (!_state) { + _state = std::make_unique(); + } + return *_state; + } + + std::unique_ptr _state; // Lazily allocated when query is parsed or modified }; // End class Url::Query public: Url() : scheme(this), host(this), port(this), path(this), query(this) {} + virtual ~Url() = default; + // Clear anything "cached" in the Url, this is rather draconian, but it's safe... virtual void Reset() diff --git a/src/cripts/Context.cc b/src/cripts/Context.cc index 76bcc822b2d..fe1a2ce389a 100644 --- a/src/cripts/Context.cc +++ b/src/cripts/Context.cc @@ -48,13 +48,12 @@ Context::reset() _server.request.Reset(); } - // Clear the initialized URLs before calling next hook - if (_urls.pristine.Initialized()) { - _urls.pristine.Reset(); - } - if (_urls.parent.Initialized()) { - _urls.parent.Reset(); - } + // Release lazy URLs entirely — they get recreated on demand for the next txn. + _urls.pristine.reset(); + _urls.parent.reset(); + _urls.remap.from.reset(); + _urls.remap.to.reset(); + if (_cache.url.Initialized()) { _cache.url.Reset(); } diff --git a/src/cripts/Error.cc b/src/cripts/Error.cc index 63935a52063..f93ea90b929 100644 --- a/src/cripts/Error.cc +++ b/src/cripts/Error.cc @@ -41,7 +41,10 @@ void Error::Reason::_set(cripts::Context *context, const cripts::string_view msg) { context->state.error.Fail(); - context->state.error._reason._setter(msg); + if (!context->state.error._reason) { + context->state.error._reason = std::make_unique(); + } + context->state.error._reason->_setter(msg); } // For convenience, an optional Reason message can also be specified with the status @@ -52,7 +55,10 @@ Error::Status::_set(cripts::Context *context, TSHttpStatus status, const cripts: context->state.error._status._setter(status); if (msg.size() > 0) { - context->state.error._reason._setter(msg); + if (!context->state.error._reason) { + context->state.error._reason = std::make_unique(); + } + context->state.error._reason->_setter(msg); } if (context->state.error.Redirected() || status == TS_HTTP_STATUS_MOVED_PERMANENTLY || diff --git a/src/cripts/Urls.cc b/src/cripts/Urls.cc index f47e49f371b..044b0c551a0 100644 --- a/src/cripts/Urls.cc +++ b/src/cripts/Urls.cc @@ -112,25 +112,27 @@ Url::Port::operator=(int port) cripts::string_view Url::Path::GetSV() { - if (_segments.size() > 0) { + if (_state && _state->segments.size() > 0) { std::ostringstream path; - std::ranges::copy(_segments, std::ostream_iterator(path, "/")); - _storage.reserve(_size); - _storage = std::string_view(path.str()); - if (_storage.size() > 0) { - _storage.pop_back(); // Removes the trailing / + std::ranges::copy(_state->segments, std::ostream_iterator(path, "/")); + _state->storage.reserve(_state->size); + _state->storage = std::string_view(path.str()); + if (_state->storage.size() > 0) { + _state->storage.pop_back(); // Removes the trailing / } - return {_storage}; + return {_state->storage}; } else if (_owner && _data.empty()) { const char *value = nullptr; int len = 0; _ensure_initialized(_owner); - value = TSUrlPathGet(_owner->_bufp, _owner->_urlp, &len); - _data = cripts::string_view(value, len); - _size = len; + value = TSUrlPathGet(_owner->_bufp, _owner->_urlp, &len); + _data = cripts::string_view(value, len); + if (_state) { + _state->size = len; + } _loaded = true; } @@ -144,14 +146,14 @@ Url::Path::operator[](Segments::size_type ix) _ensure_initialized(_owner); _parser(); // Make sure the segments are loaded - if (ix < _segments.size()) { - ret._initialize(_segments[ix], this, ix); + if (_state && ix < _state->segments.size()) { + ret._initialize(_state->segments[ix], this, ix); } return ret; // RVO } -Url::Path +Url::Path & Url::Path::operator=(cripts::string_view path) { _ensure_initialized(_owner); @@ -183,10 +185,11 @@ Url::Path::String::operator=(const cripts::string_view str) { _ensure_initialized(_owner->_owner); CAssert(!_owner->_owner->ReadOnly()); // This can not be a read-only URL - _owner->_size -= _owner->_segments[_ix].size(); - _owner->_segments[_ix] = str; - _owner->_size += str.size(); - _owner->_modified = true; + CAssert(_owner->_state); // Should have been allocated by operator[]/_parser() + _owner->_state->size -= _owner->_state->segments[_ix].size(); + _owner->_state->segments[_ix] = str; + _owner->_state->size += str.size(); + _owner->_state->modified = true; return *this; } @@ -196,51 +199,53 @@ Url::Path::Reset() { Component::Reset(); - _segments.clear(); - _storage.clear(); - _size = 0; - _modified = false; + _state.reset(); } void Url::Path::Push(cripts::string_view val) { _parser(); - _modified = true; - _segments.push_back(val); + auto &s = _ensure_state(); + s.modified = true; + s.segments.push_back(val); } void Url::Path::Insert(Segments::size_type ix, cripts::string_view val) { _parser(); - _modified = true; - _segments.insert(_segments.begin() + ix, val); + auto &s = _ensure_state(); + s.modified = true; + s.segments.insert(s.segments.begin() + ix, val); } void Url::Path::_parser() { - if (_segments.size() == 0) { - _segments = Split('/'); + auto &s = _ensure_state(); + + if (s.segments.size() == 0) { + s.segments = Split('/'); } } Url::Query::Parameter & Url::Query::Parameter::operator=(const cripts::string_view str) { - CAssert(!_owner->_standalone); + CAssert(!_owner->_state || !_owner->_state->standalone); _ensure_initialized(_owner->_owner); CAssert(!_owner->_owner->ReadOnly()); // This can not be a read-only URL - auto iter = _owner->_hashed.find(_name); + auto &s = _owner->_ensure_state(); + auto iter = s.hashed.find(_name); - if (iter != _owner->_hashed.end()) { + if (iter != s.hashed.end()) { iter->second = str; // Can be an empty string here! } else { - _owner->_ordered.push_back(_name); - _owner->_hashed[_name] = str; + s.ordered.push_back(_name); + s.hashed[_name] = str; } - _owner->_modified = true; + s.modified = true; return *this; } @@ -248,31 +253,31 @@ Url::Query::Parameter::operator=(const cripts::string_view str) cripts::string_view Url::Query::GetSV() { - if (!_standalone) { + if (!_state || !_state->standalone) { _ensure_initialized(_owner); } - if (_ordered.size() > 0) { - _storage.clear(); - _storage.reserve(_size); + if (_state && _state->ordered.size() > 0) { + _state->storage.clear(); + _state->storage.reserve(_state->size); // ToDo: This is wonky, has to be a better std:: iteration to do here - for (const auto key : _ordered) { - auto iter = _hashed.find(key); + for (const auto key : _state->ordered) { + auto iter = _state->hashed.find(key); - if (_storage.size() > 0) { - _storage += "&"; + if (_state->storage.size() > 0) { + _state->storage += "&"; } - if (iter != _hashed.end()) { - _storage += iter->first; + if (iter != _state->hashed.end()) { + _state->storage += iter->first; if (iter->second.size() > 0) { - _storage += '='; - _storage += iter->second; + _state->storage += '='; + _state->storage += iter->second; } } } - return {_storage}; + return {_state->storage}; } // This gets weird when we modify the query parameter components, and can possibly empty @@ -282,19 +287,21 @@ Url::Query::GetSV() const char *value = nullptr; int len = 0; - value = TSUrlHttpQueryGet(_owner->_bufp, _owner->_urlp, &len); - _data = cripts::string_view(value, len); - _size = len; + value = TSUrlHttpQueryGet(_owner->_bufp, _owner->_urlp, &len); + _data = cripts::string_view(value, len); + if (_state) { + _state->size = len; + } _loaded = true; } return _data; } -Url::Query +Url::Query & Url::Query::operator=(cripts::string_view query) { - CAssert(!_standalone); + CAssert(!_state || !_state->standalone); _ensure_initialized(_owner); CAssert(!_owner->ReadOnly()); // This can not be a read-only URL TSUrlHttpQuerySet(_owner->_bufp, _owner->_urlp, query.data(), query.size()); @@ -323,15 +330,15 @@ Url::Query::Parameter Url::Query::operator[](cripts::string_view param) { // Make sure the hash and vector are populated, but only if we have an owner - if (!_standalone) { + if (!_state || !_state->standalone) { _ensure_initialized(_owner); } _parser(); Parameter ret; - auto iter = _hashed.find(param); + auto iter = _state->hashed.find(param); - if (iter != _hashed.end()) { + if (iter != _state->hashed.end()) { ret._initialize(iter->first, iter->second, this); } else { ret._initialize(param, "", this); @@ -346,21 +353,25 @@ Url::Query::Erase(cripts::string_view param) // Make sure the hash and vector are populated _parser(); - auto iter = _hashed.find(param); - auto viter = std::ranges::find(_ordered, param); + auto &s = *_state; + auto iter = s.hashed.find(param); + auto viter = std::ranges::find(s.ordered, param); - if (iter != _hashed.end()) { - _size -= iter->second.size(); // Size of the erased value - _hashed.erase(iter); + if (iter != s.hashed.end()) { + s.size -= iter->second.size(); // Size of the erased value + s.hashed.erase(iter); - CAssert(viter != _ordered.end()); - _size -= viter->size(); // Length of the erased key - _ordered.erase(viter); + CAssert(viter != s.ordered.end()); + s.size -= viter->size(); // Length of the erased key + s.ordered.erase(viter); - if (_ordered.size() == 0) { + if (s.ordered.size() == 0) { Reset(); + // After Reset() the state is gone; re-create it just so _modified can be tracked. + _ensure_state().modified = true; + } else { + s.modified = true; } - _modified = true; // Make sure to set this after we reset above ... } } @@ -371,21 +382,23 @@ Url::Query::Erase(std::initializer_list list, bool keep) // Make sure the hash and vector are populated _parser(); - for (auto viter = _ordered.begin(); viter != _ordered.end();) { + auto &s = *_state; + + for (auto viter = s.ordered.begin(); viter != s.ordered.end();) { if (list.end() == std::ranges::find(list, *viter)) { - auto iter = _hashed.find(*viter); - - CAssert(iter != _hashed.end()); - _size -= iter->second.size(); // Size of the erased value - _size -= viter->size(); // Length of the erased key - _hashed.erase(iter); - viter = _ordered.erase(viter); - _modified = true; + auto iter = s.hashed.find(*viter); + + CAssert(iter != s.hashed.end()); + s.size -= iter->second.size(); // Size of the erased value + s.size -= viter->size(); // Length of the erased key + s.hashed.erase(iter); + viter = s.ordered.erase(viter); + s.modified = true; } else { ++viter; } } - if (_ordered.size() == 0) { + if (s.ordered.size() == 0) { Reset(); } } else { @@ -400,17 +413,15 @@ Url::Query::Reset() { Component::Reset(); - _ordered.clear(); - _hashed.clear(); - _storage.clear(); - _size = 0; - _modified = false; + _state.reset(); } void Url::Query::_parser() { - if (_ordered.size() == 0) { + auto &s = _ensure_state(); + + if (s.ordered.size() == 0) { for (const auto sv : Split('&')) { const auto eq = sv.find_first_of('='); cripts::string_view key = sv.substr(0, eq); @@ -420,8 +431,8 @@ Url::Query::_parser() val = sv.substr(eq + 1); } - _ordered.push_back(key); // Keep the order - _hashed[key] = val; + s.ordered.push_back(key); // Keep the order + s.hashed[key] = val; } } } @@ -447,17 +458,21 @@ Url::String() Pristine::URL & Pristine::URL::_get(cripts::Context *context) { - _ensure_initialized(&context->_urls.pristine); - return context->_urls.pristine; + auto &slot = context->_urls.pristine; + + if (!slot) { + slot = std::make_unique(); + slot->set_context(context); + } + _ensure_initialized(slot.get()); + return *slot; } void Pristine::URL::_initialize() { - Pristine::URL *url = &_context->_urls.pristine; - TSAssert(_context->state.txnp); - if (TSHttpTxnPristineUrlGet(_context->state.txnp, &url->_bufp, &url->_urlp) != TS_SUCCESS) { + if (TSHttpTxnPristineUrlGet(_context->state.txnp, &_bufp, &_urlp) != TS_SUCCESS) { _context->state.error.Fail(); } else { super_type::_initialize(); // Only if successful @@ -519,8 +534,14 @@ Remap::From::URL::_initialize() Remap::From::URL & Remap::From::URL::_get(cripts::Context *context) { - _ensure_initialized(&context->_urls.remap.from); - return context->_urls.remap.from; + auto &slot = context->_urls.remap.from; + + if (!slot) { + slot = std::make_unique(); + slot->set_context(context); + } + _ensure_initialized(slot.get()); + return *slot; } void @@ -539,8 +560,14 @@ Remap::To::URL::_initialize() Remap::To::URL & Remap::To::URL::_get(cripts::Context *context) { - _ensure_initialized(&context->_urls.remap.to); - return context->_urls.remap.to; + auto &slot = context->_urls.remap.to; + + if (!slot) { + slot = std::make_unique(); + slot->set_context(context); + } + _ensure_initialized(slot.get()); + return *slot; } Cache::URL & @@ -623,19 +650,24 @@ Cache::URL::_update() Parent::URL & Parent::URL::_get(cripts::Context *context) { - _ensure_initialized(&context->_urls.parent); - return context->_urls.parent; + auto &slot = context->_urls.parent; + + if (!slot) { + slot = std::make_unique(); + slot->set_context(context); + } + _ensure_initialized(slot.get()); + return *slot; } void Parent::URL::_initialize() { - Parent::URL *url = &_context->_urls.parent; Client::Request &req = Client::Request::_get(_context); // Repurpose / create the shared request object - if (TSUrlCreate(req.BufP(), &url->_urlp) == TS_SUCCESS) { + if (TSUrlCreate(req.BufP(), &_urlp) == TS_SUCCESS) { TSAssert(_context->state.txnp); - if (TSHttpTxnParentSelectionUrlGet(_context->state.txnp, req.BufP(), url->_urlp) != TS_SUCCESS) { + if (TSHttpTxnParentSelectionUrlGet(_context->state.txnp, req.BufP(), _urlp) != TS_SUCCESS) { _context->state.error.Fail(); return; }