Repository: apache/trafficserver
Pull request #12155: Adds convenience APIs / definitions to Cripts
Author: login: zwoop
Labels: name: New Feature
name: Cripts
name: Release Doc Done
Created: 2025-04-02T19:46:56Z | Merged: 2025-04-06T02:23:46Z

This does a few things:

1. It adds a few top level symbols, polluting the name space with client, server and urls.
2. Adds a few convenience macros, again populated in the top level name space
3. Cleans up some code as needed.

These new symbols and macros are not in the normal name space, for now at least. They are instead activated by adding this to the absolute beginning of any Cripts that wants to use them:

```
#define CRIPT_CONVENIENCE_APIS 1
```


Diff:
diff --git a/doc/developer-guide/cripts/cripts-convenience.en.rst b/doc/developer-guide/cripts/cripts-convenience.en.rst
new file mode 100644
index 00000000000..cb2fc4010c2
--- /dev/null
+++ b/doc/developer-guide/cripts/cripts-convenience.en.rst
@@ -0,0 +1,140 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+
+.. include:: ../../common.defs
+
+.. highlight:: cpp
+.. default-domain:: cpp
+
+.. _cripts-convenience:
+
+Convenience APIs
+****************
+
+To make Cripts even more approachable, a set of optional convenience APIs are added
+to the core and top level name space. These APIs are not required to be used, and
+are enabled by adding a define to your Cript file (or when compiling the Cript).
+Making this addition optional allows users to choose whether they want to use
+these convenience APIs or stick with the traditional Cripts API style.
+
+The convenience APIs carries a small (very small) overhead, due to how Cripts defers
+(or delays) initializations of objects until they are actually used. These new APIs
+are designed to be as efficient as possible, but they do introduce potential conflicts
+in the top level namespace. Here's a simple example of how to enable these APIs:
+
+.. code-block:: cpp
+
+   #define CRIPTS_CONVENIENCE_APIS 1
+
+   #include <cripts/Preamble.hpp>
+
+   do_remap()
+   {
+     urls.request.query.Keep({"foo", "bar"});
+   }
+
+   #include <cripts/Epilogue.hpp>
+
+
+.. _cripts-convenience-toplevel:
+
+Top level API additions
+=======================
+
+For the most common patterns, the top level API additions are ``client``, ``server``, and
+``urls``. These all have sub-level API additions, as explained in the following table:
+
+===========================   =============================================
+Object                        Traditional API equivalent
+===========================   =============================================
+``client.request``            ``borrow cripts::Client::Request::Get()``
+``client.response``           ``borrow cripts::Client::Response::Get()``
+``client.connection``         ``borrow cripts::Client::Connection::Get()``
+``client.url``                ``borrow cripts::Client::URL::Get()``
+``server.request``            ``borrow cripts::Server::Request::Get()``
+``server.response``           ``borrow cripts::Server::Response::Get()``
+``server.connection``         ``borrow cripts::Server::Connection::Get()``
+``urls.request``              ``borrow cripts::Client::URL::Get()``
+``urls.pristine``             ``borrow cripts::Pristine::URL::Get()``
+``urls.cache``                ``borrow cripts::Cache::URL::Get()``
+``urls.parent``               ``borrow cripts::Parent::URL::Get()``
+``urls.remap.to``             ``borrow cripts::Remap::To::URL::Get``
+``urls.remap.from``           ``borrow cripts::Remap::From::URL::Get``
+===========================   =============================================
+
+The use of these top-level objects are identical to how you would use them with the traditinoal
+APIs. The following code shows both the traditional Cripts API and the new APIs in a simple
+example:
+
+.. code-block:: cpp
+
+   // Traditional Cripts API
+   do_remap()
+   {
+     borrow req = cripts::Client::Request::Get();
+     borrow url = cripts::Client::URL::Get();
+
+     url.query.Keep({"foo", "bar"});
+     req["X-Foo"] = "bar"
+   }
+
+   // Convenience API, does not need the borrow statements
+   do_remap()
+   {
+     urls.request.query.Keep({"foo", "bar"});
+     client.request["X-Foo"] = "bar";
+   }
+
+.. note::
+   Both ``client.url`` and `` urls.request`` refer to the same underlying object, which is
+   ``cripts::Client::URL``. This means that any changes made to ``urls.request``
+   will also be reflected in ``client.url`` and vice versa.
+
+.. _cripts-convenience-macros:
+
+Convenience macros
+==================
+
+In addition to the top-level APIs, a set of convenience macros are provided as well,
+enabled with the same ``#define`` as above. The following macros have been added,
+which again populates in the top level namespace:
+
+===========================   ====================================================================
+Macro                         Traditional API equivalent
+===========================   ====================================================================
+``Regex(name, ...)``          ``static cripts::Matcher::PCRE name(...)``
+``ACL(name, ...)``            ``static cripts::Matcher::Range::IP name(...)``
+``CreateCounter(id, name)``   ``instance.metrics[id] = cripts::Metrics::Counter::Create(name)``
+``CreateGauge(id, name)``     ``instance.metrics[id] = cripts::Metrics::Gauge::Create(name)``
+``FilePath(name, path)``      ``static const cripts::File::Path name(path)``
+``UniqueUUID()``              ``cripts::UUID::Unique::Get()``
+``TimeNow()``                 ``cripts::Time::Local::Now()``
+===========================   ====================================================================
+
+An example of using ACLs and regular expressions:
+
+.. code-block:: cpp
+
+   do_remap()
+   {
+     Regex(rex, "^/([^/]+)/(.*)$");
+     ACL(allow, {"192.168.201.0/24", "10.0.0.0/8"});
+
+     if (allow.Match(client.connection.IP()) && rex.Match(urls.request.path)) {
+       // do something
+     }
+   }
diff --git a/doc/developer-guide/cripts/cripts-global.en.rst b/doc/developer-guide/cripts/cripts-global.en.rst
index 56fd35bb354..e348e846118 100644
--- a/doc/developer-guide/cripts/cripts-global.en.rst
+++ b/doc/developer-guide/cripts/cripts-global.en.rst
@@ -50,9 +50,9 @@ depending on your preference. The file must be readable by the ATS process. Exam
 
    glb_read_request()
    {
-     borrow url = cripts::Client::URL::get();
+     borrow url = cripts::Client::URL::Get();
 
-     url.query.keep({"foo", "bar"});
+     url.query.Keep({"foo", "bar"});
    }
 
    #include <cripts/Epilogue.hpp>
diff --git a/doc/developer-guide/cripts/cripts-misc.en.rst b/doc/developer-guide/cripts/cripts-misc.en.rst
index 698be215a5f..8fe9c4ed954 100644
--- a/doc/developer-guide/cripts/cripts-misc.en.rst
+++ b/doc/developer-guide/cripts/cripts-misc.en.rst
@@ -56,7 +56,7 @@ Example:
 
    do_remap()
    {
-     borrow req  = cripts::Client::Request::get();
+     borrow req  = cripts::Client::Request::Get();
 
      if (req["X-Header"] == "yes") {
        cripts::Error::Status::Set(403);
@@ -119,7 +119,7 @@ Example usage to turn off a particular hook conditionally:
 
    do_remap()
    {
-     static borrow req = cripts::Client::Request::get();
+     static borrow req = cripts::Client::Request::Get();
 
      if (req["X-Header"] == "yes") {
        transaction.DisableCallback(cripts::Callback::DO_READ_RESPONSE);
diff --git a/doc/developer-guide/cripts/cripts-overview.en.rst b/doc/developer-guide/cripts/cripts-overview.en.rst
index 174503049f4..ea8704dd6f3 100644
--- a/doc/developer-guide/cripts/cripts-overview.en.rst
+++ b/doc/developer-guide/cripts/cripts-overview.en.rst
@@ -94,9 +94,9 @@ depending on your preference. The file must be readable by the ATS process. Exam
 
    do_remap()
    {
-     borrow url = cripts::Client::URL::get();
+     borrow url = cripts::Client::URL::Get();
 
-     url.query.keep({"foo", "bar"});
+     url.query.Keep({"foo", "bar"});
    }
 
    #include <cripts/Epilogue.hpp>
diff --git a/doc/developer-guide/cripts/cripts-urls.en.rst b/doc/developer-guide/cripts/cripts-urls.en.rst
index 2b0500ed07e..162125bb7bb 100644
--- a/doc/developer-guide/cripts/cripts-urls.en.rst
+++ b/doc/developer-guide/cripts/cripts-urls.en.rst
@@ -124,7 +124,7 @@ For example:
 
 .. code-block:: cpp
 
-  borrow url = cripts::Client::URL::get();
+  borrow url = cripts::Client::URL::Get();
 
   url.query.Erase("key"); // Removes the key from the query
   url.query.Erase({"key1", "key2"}); // Removes both keys from the query
diff --git a/doc/developer-guide/cripts/index.en.rst b/doc/developer-guide/cripts/index.en.rst
index c1fb0578177..03fa6da08fc 100644
--- a/doc/developer-guide/cripts/index.en.rst
+++ b/doc/developer-guide/cripts/index.en.rst
@@ -35,4 +35,5 @@ Cripts
    cripts-crypto.en
    cripts-misc.en
    cripts-bundles.en
+   cripts-convenience.en
    cripts-examples.en
diff --git a/example/cripts/example1.cc b/example/cripts/example1.cc
index 205aa90fcf1..6270cf4c1bb 100644
--- a/example/cripts/example1.cc
+++ b/example/cripts/example1.cc
@@ -59,7 +59,7 @@ do_cache_lookup()
 {
   borrow url2 = cripts::Cache::URL::Get();
 
-  CDebug("Cache URL: {}", url2.String());
+  CDebug("Cache URL: {}", url2);
   CDebug("Cache Host: {}", url2.host);
 }
 
diff --git a/example/cripts/example2.cc b/example/cripts/example2.cc
new file mode 100644
index 00000000000..ca6b88b1253
--- /dev/null
+++ b/example/cripts/example2.cc
@@ -0,0 +1,244 @@
+/*
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#define CRIPTS_CONVENIENCE_APIS 1
+
+// The primary include file, this has to always be included
+#include <cripts/Preamble.hpp>
+
+#include <cripts/Bundles/Common.hpp>
+#include <cripts/Bundles/Caching.hpp>
+
+// Globals for this Cript
+ACL(CRIPT_ALLOW, {"192.168.201.0/24", "10.0.0.0/8"});
+
+// This is called only when the plugin is initialized
+do_init()
+{
+  // TSDebug("Cript", "Hello, example1 plugin is being initialized");
+}
+
+do_create_instance()
+{
+  CreateCounter(0, "cript.example1.c0");
+  CreateCounter(1, "cript.example1.c1");
+  CreateCounter(2, "cript.example1.c2");
+  CreateCounter(3, "cript.example1.c3");
+  CreateCounter(4, "cript.example1.c4");
+  CreateCounter(5, "cript.example1.c5");
+  CreateCounter(6, "cript.example1.c6");
+  CreateCounter(7, "cript.example1.c7");
+  CreateCounter(8, "cript.example1.c8"); // This one should resize the storage
+
+  cripts::Bundle::Common::Activate().dscp(10);
+  cripts::Bundle::Caching::Activate().cache_control("max-age=259200");
+}
+
+do_txn_close()
+{
+  client.connection.pacing = cripts::Pacing::Off;
+  CDebug("Cool, TXN close also works");
+}
+
+do_cache_lookup()
+{
+  CDebug("Cache URL: {}", urls.cache);
+  CDebug("Cache Host: {}", urls.cache.host);
+}
+
+do_send_request()
+{
+  server.request["X-Leif"] = "Meh";
+}
+
+do_read_response()
+{
+  server.response["X-DBJ"] = "Vrooom!";
+}
+
+do_send_response()
+{
+  string msg = "Eliminate TSCPP";
+
+  client.response["Server"]         = "";        // Deletes the Server header
+  client.response["X-AMC"]          = msg;       // New header
+  client.response["Cache-Control"]  = "Private"; // Deletes old CC values, and sets a new one
+  client.response["X-UUID"]         = UniqueUUID();
+  client.response["X-tcpinfo"]      = client.connection.tcpinfo.Log();
+  client.response["X-Cache-Status"] = client.response.cache;
+  client.response["X-Integer"]      = 666;
+  client.response["X-Data"]         = AsString(txn_data[2]);
+
+  client.response["X-ASN"]         = client.connection.geo.ASN();
+  client.response["X-ASN-Name"]    = client.connection.geo.ASNName();
+  client.response["X-Country"]     = client.connection.geo.Country();
+  client.response["X-ISO-Country"] = client.connection.geo.CountryCode();
+
+  // Setup some connection parameters
+  client.connection.congestion = "bbr";
+  client.connection.dscp       = 8;
+  client.connection.pacing     = 100000;
+  client.connection.mark       = 17;
+
+  // Some file operations (note that the paths aren't required here, can just be strings, but it's a good practice)
+  FilePath(p1, "/tmp/foo");
+  FilePath(p2, "/tmp/secret.txt");
+
+  if (cripts::File::Status(p1).type() == cripts::File::Type::regular) {
+    client.response["X-Foo-Exists"] = "yes";
+  } else {
+    client.response["X-Foo-Exists"] = "no";
+  }
+
+  string secret = cripts::File::Line::Reader(p2);
+  CDebug("Read secret = {}", secret);
+
+  if (client.response.status == 200) {
+    client.response.status = 222;
+  }
+
+  CDebug("Txn count: {}", client.connection.Count());
+}
+
+do_remap()
+{
+  auto ip  = client.connection.IP();
+  auto now = TimeNow();
+
+  if (CRIPT_ALLOW.Match(ip)) {
+    CDebug("Client IP allowed: {}", ip.string(24, 64));
+  }
+
+  CDebug("Epoch time is {} (or via .epoch(), {}", now, now.Epoch());
+  CDebug("Year is {}", now.Year());
+  CDebug("Month is {}", now.Month());
+  CDebug("Day is {}", now.Day());
+  CDebug("Hour is {}", now.Hour());
+  CDebug("Day number is {}", now.YearDay());
+
+  CDebug("from_url = {}", instance.from_url.c_str());
+  CDebug("to_url = {}", instance.to_url.c_str());
+
+  // Turn off the cache for testing
+  // proxy.config.http.cache.http.set(1);
+  // control.cache.nostore.set(true);
+
+  CDebug("Int config cache.http = {}", proxy.config.http.cache.http.Get());
+  CDebug("Float config cache.heuristic_lm_factor = {}", proxy.config.http.cache.heuristic_lm_factor.Get());
+  CDebug("String config http.response_server_str = {}", proxy.config.http.response_server_str.GetSV(context));
+  CDebug("X-Miles = {}", client.request["X-Miles"]);
+  CDebug("random(1000) = {}", cripts::Random(1000));
+
+  auto old_port = urls.request.port;
+
+  CDebug("Method is {}", client.request.method);
+  CDebug("Scheme is {}", urls.request.scheme);
+  CDebug("Host is {}", urls.request.host);
+  CDebug("Port is {}", urls.request.port);
+  CDebug("Path is {}", urls.request.path);
+  CDebug("Path[1] is {}", urls.request.path[1]);
+  CDebug("Query is {}", urls.request.query);
+
+  auto testing_trim = urls.request.path.trim();
+
+  CDebug("Trimmed path is {}", testing_trim);
+
+  if (urls.request.query["foo"] > 100) {
+    CDebug("Query[foo] is > 100");
+  }
+
+  if (urls.request.path == "some/url" || urls.request.path[0] == "other") {
+    CDebug("The path comparison triggered");
+  }
+
+  urls.request.host = "foobar.com";
+  urls.request.port = "81";
+  urls.request.port = old_port;
+
+  // TXN data slots
+  txn_data[0] = true;
+  txn_data[1] = 17;
+  txn_data[2] = "DBJ";
+
+  // Regular expressions
+  Regex(pcre, "^/([^/]+)/(.*)$");
+
+  auto res = pcre.Match("/foo/bench/bar"); // Can also call contains(), same thing
+
+  if (res) {
+    CDebug("Ovector count is {}", res.Count());
+    CDebug("First capture is {}", res[1]);
+    CDebug("Second capture is {}", res[2]);
+  } else {
+    CDebug("Regular expression did not match, that is not expected!");
+  }
+
+  // ATS versions
+  CDebug("ATS version = {}", version);
+  CDebug("ATS Major Version = {}", version.major);
+
+  // Some Crypto::Base64 tests
+  static auto base64_test = "VGltZSB3aWxsIG5vdCBzbG93IGRvd24gd2hlbiBzb21ldGhpbmcgdW5wbGVhc2FudCBsaWVzIGFoZWFkLg==";
+  auto        hp          = cripts::Crypto::Base64::Decode(base64_test);
+  auto        hp2         = cripts::Crypto::Base64::Encode(hp);
+
+  CDebug("HP quote: {}", hp);
+  if (base64_test != hp2) {
+    CDebug("Base64 failed: {}", hp2);
+  } else {
+    CDebug("Base64 encode reproduced the decoded HP string");
+  }
+
+  // Some Crypto::Escape (URL escaping) tests
+  static auto escape_test = "Hello_World_!@%23$%25%5E&*()_%2B%3C%3E?%2C.%2F";
+  auto        uri         = cripts::Crypto::Escape::Decode(escape_test);
+  auto        uri2        = cripts::Crypto::Escape::Encode(uri);
+
+  CDebug("Unescaped URI: {}", uri);
+  if (escape_test != uri2) {
+    CDebug("URL Escape failed: {}", uri2);
+  } else {
+    CDebug("Url Escape encode reproduced the decoded HP string");
+  }
+
+  // Testing Crypto SHA and encryption
+  auto hex = format("{}", cripts::Crypto::SHA256::Encode("Hello World"));
+
+  CDebug("SHA256 = {}", hex);
+
+  // Testing iterators
+  for (auto hdr : client.request) {
+    CDebug("Header: {} = {}", hdr, client.request[hdr]);
+    if (hdr.starts_with("AWS-")) {
+      client.request[hdr].clear();
+    }
+  }
+
+  // Testing some simple metrics
+  static auto m1 = cripts::Metrics::Gauge("cript.example1.m1");
+  static auto m2 = cripts::Metrics::Counter("cript.example1.m2");
+
+  m1.Increment(100);
+  m1.Decrement(10);
+  m2.Increment();
+
+  instance.metrics[0]->Increment();
+  instance.metrics[8]->Increment();
+}
+
+#include <cripts/Epilogue.hpp>
diff --git a/include/cripts/Connections.hpp b/include/cripts/Connections.hpp
index 37b645bbeb7..ae4dc1d6c4d 100644
--- a/include/cripts/Connections.hpp
+++ b/include/cripts/Connections.hpp
@@ -82,6 +82,7 @@ namespace detail
 class ConnBase
 {
   using self_type = ConnBase;
+
   class Dscp
   {
     using self_type = Dscp;
@@ -99,7 +100,7 @@ class ConnBase
     void
     operator=(int val)
     {
-      TSAssert(_owner);
+      _ensure_initialized(_owner);
       _owner->SetDscp(val);
       _val = val;
     }
@@ -147,7 +148,7 @@ class ConnBase
     self_type &
     operator=([[maybe_unused]] cripts::string_view const &str)
     {
-      TSAssert(_owner);
+      _ensure_initialized(_owner);
 #if defined(TCP_CONGESTION)
       int connfd = _owner->FD();
       int res    = setsockopt(connfd, IPPROTO_TCP, TCP_CONGESTION, str.data(), str.size());
@@ -182,7 +183,7 @@ class ConnBase
     void
     operator=(int val)
     {
-      TSAssert(_owner);
+      _ensure_initialized(_owner);
       _owner->SetMark(val);
       _val = val;
     }
@@ -309,23 +310,23 @@ class ConnBase
   [[nodiscard]] virtual int FD() const = 0; // This needs the txnp from the Context
 
   [[nodiscard]] struct sockaddr const *
-  Socket() const
+  Socket()
   {
-    TSAssert(_vc);
+    _ensure_initialized(this);
     return TSNetVConnRemoteAddrGet(_vc);
   }
 
   [[nodiscard]] cripts::IP
-  IP() const
+  IP()
   {
-    TSAssert(Initialized());
+    _ensure_initialized(this);
     return cripts::IP{Socket()};
   }
 
   [[nodiscard]] bool
   Initialized() const
   {
-    return _state != nullptr;
+    return _initialized;
   }
 
   [[nodiscard]] bool
@@ -334,10 +335,17 @@ class ConnBase
     return TSHttpTxnIsInternal(_state->txnp);
   }
 
-  [[nodiscard]] virtual cripts::IP LocalIP() const  = 0;
-  [[nodiscard]] virtual int        Count() const    = 0;
-  virtual void                     SetDscp(int val) = 0;
-  virtual void                     SetMark(int val) = 0;
+  [[nodiscard]] virtual cripts::IP LocalIP() const        = 0;
+  [[nodiscard]] virtual int        Count() const          = 0;
+  virtual void                     SetDscp(int val) const = 0;
+  virtual void                     SetMark(int val) const = 0;
+
+  // This should only be called from the Context initializers!
+  void
+  set_state(cripts::Transaction *state)
+  {
+    _state = state;
+  }
 
   Dscp       dscp;
   Congestion congestion;
@@ -349,10 +357,21 @@ class ConnBase
   cripts::string_view string(unsigned ipv4_cidr = 32, unsigned ipv6_cidr = 128);
 
 protected:
+  static void
+  _ensure_initialized(self_type *ptr)
+  {
+    if (!ptr->Initialized()) [[unlikely]] {
+      ptr->_initialize();
+    }
+  }
+
+  void virtual _initialize() { _initialized = true; }
+
   cripts::Transaction   *_state  = nullptr;
   struct sockaddr const *_socket = nullptr;
   TSVConn                _vc     = nullptr;
   char                   _str[INET6_ADDRSTRLEN + 1];
+  bool                   _initialized = false;
 
 }; // End class ConnBase
 
@@ -365,8 +384,8 @@ namespace Client
 {
   class Connection : public detail::ConnBase
   {
-    using pe        = detail::ConnBase;
-    using self_type = Connection;
+    using super_type = detail::ConnBase;
+    using self_type  = Connection;
 
   public:
     Connection()                      = default;
@@ -376,15 +395,16 @@ namespace Client
     [[nodiscard]] int  FD() const override;
     [[nodiscard]] int  Count() const override;
     static Connection &_get(cripts::Context *context);
+    void               _initialize() override;
 
     void
-    SetDscp(int val) override
+    SetDscp(int val) const override
     {
       TSHttpTxnClientPacketDscpSet(_state->txnp, val);
     }
 
     void
-    SetMark(int val) override
+    SetMark(int val) const override
     {
       TSHttpTxnClientPacketMarkSet(_state->txnp, val);
     }
@@ -403,8 +423,8 @@ namespace Server
 {
   class Connection : public detail::ConnBase
   {
-    using pe        = detail::ConnBase;
-    using self_type = Connection;
+    using super_type = detail::ConnBase;
+    using self_type  = Connection;
 
   public:
     Connection()                      = default;
@@ -414,15 +434,16 @@ namespace Server
     [[nodiscard]] int  FD() const override;
     [[nodiscard]] int  Count() const override;
     static Connection &_get(cripts::Context *context);
+    void               _initialize() override;
 
     void
-    SetDscp(int val) override
+    SetDscp(int val) const override
     {
       TSHttpTxnServerPacketDscpSet(_state->txnp, val);
     }
 
     void
-    SetMark(int val) override
+    SetMark(int val) const override
     {
       TSHttpTxnServerPacketMarkSet(_state->txnp, val);
     }
diff --git a/include/cripts/Context.hpp b/include/cripts/Context.hpp
index e3592aefe22..be2af90c395 100644
--- a/include/cripts/Context.hpp
+++ b/include/cripts/Context.hpp
@@ -44,7 +44,7 @@ class Context
 
   // This will, and should, only be called via the ProxyAllocator as used in the factory.
   Context(TSHttpTxn txn_ptr, TSHttpSsn ssn_ptr, TSRemapRequestInfo *rri_ptr, cripts::Instance &inst)
-    : rri(rri_ptr), p_instance(inst)
+    : rri(rri_ptr), p_instance(inst), _client(this), _server(this), _urls(this, _client.url)
   {
     state.txnp    = txn_ptr;
     state.ssnp    = ssn_ptr;
@@ -67,7 +67,6 @@ class Context
 
   // These are private, but needs to be visible to our friend classes that
   // depends on the Context.
-private:
   friend class Client::Request;
   friend class Client::Response;
   friend class Client::Connection;
@@ -84,18 +83,56 @@ class Context
 
   // These are "pre-allocated", but not initialized. They will be initialized
   // when used via a factory.
-  cripts::Client::Response   _client_resp_header;
-  cripts::Client::Request    _client_req_header;
-  cripts::Client::Connection _client_conn;
-  cripts::Client::URL        _client_url;
-  cripts::Remap::From::URL   _remap_from_url;
-  cripts::Remap::To::URL     _remap_to_url;
-  cripts::Pristine::URL      _pristine_url;
-  cripts::Server::Response   _server_resp_header;
-  cripts::Server::Request    _server_req_header;
-  cripts::Server::Connection _server_conn;
-  cripts::Cache::URL         _cache_url;
-  cripts::Parent::URL        _parent_url;
+  struct _ClientBlock {
+    cripts::Client::Response   response;
+    cripts::Client::Request    request;
+    cripts::Client::Connection connection;
+    cripts::Client::URL        url;
+
+    _ClientBlock(Context *ctx)
+    {
+      response.set_state(&ctx->state);
+      request.set_state(&ctx->state);
+      connection.set_state(&ctx->state);
+    }
+  } _client;
+
+  struct _ServerBlock {
+    cripts::Server::Response   response;
+    cripts::Server::Request    request;
+    cripts::Server::Connection connection;
+
+    _ServerBlock(Context *ctx)
+    {
+      response.set_state(&ctx->state);
+      request.set_state(&ctx->state);
+      connection.set_state(&ctx->state);
+    }
+
+  } _server;
+
+  struct _UrlBlock {
+    cripts::Client::URL  &request;
+    cripts::Pristine::URL pristine;
+    cripts::Cache::URL    cache;
+    cripts::Parent::URL   parent;
+
+    struct {
+      cripts::Remap::From::URL from;
+      cripts::Remap::To::URL   to;
+    } remap;
+
+    _UrlBlock(Context *ctx, cripts::Client::URL &alias) : request(alias)
+    {
+      request.set_context(ctx);
+      pristine.set_context(ctx);
+      cache.set_context(ctx);
+      parent.set_context(ctx);
+      remap.from.set_context(ctx);
+      remap.to.set_context(ctx);
+    }
+
+  } _urls;
 }; // End class Context
 
 } // namespace cripts
diff --git a/include/cripts/Epilogue.hpp b/include/cripts/Epilogue.hpp
index 6c63818faf4..d0d583032bf 100644
--- a/include/cripts/Epilogue.hpp
+++ b/include/cripts/Epilogue.hpp
@@ -441,7 +441,9 @@ http_txn_cont(TSCont contp, TSEvent event, void *edata)
       }
       CDebug("Entering glb_read_request()");
       wrap_glb_read_request(context, true, CaseArg);
-      cripts::Client::URL::_get(context).Update(); // Make sure any changes to the request URL is updated
+      if (context->_urls.request.Modified()) {
+        context->_urls.request._update(); // Make sure any changes to the request URL is updated
+      }
     }
     break;
 
@@ -464,7 +466,9 @@ http_txn_cont(TSCont contp, TSEvent event, void *edata)
         CDebug("Entering glb_send_request()");
         wrap_glb_send_request(context, true, CaseArg);
       }
-      cripts::Client::URL::_get(context).Update(); // Make sure any changes to the request URL is updated
+      if (context->_urls.request.Modified()) {
+        context->_urls.request._update(); // Make sure any changes to the request URL is updated
+      }
     }
     break;
 
@@ -577,8 +581,12 @@ http_txn_cont(TSCont contp, TSEvent event, void *edata)
     }
 
     if (!context->state.error.Failed()) {
-      cripts::Cache::URL::_get(context).Update();  // Make sure the cache-key gets updated, if modified
-      cripts::Client::URL::_get(context).Update(); // Make sure any changes to the request URL is updated
+      if (context->_urls.cache.Modified()) {
+        context->_urls.cache._update(); // Make sure the cache-key gets updated, if modified
+      }
+      if (context->_urls.request.Modified()) {
+        context->_urls.request._update(); // Make sure any changes to the request URL is updated
+      }
     }
     break;
 
@@ -604,8 +612,12 @@ http_txn_cont(TSCont contp, TSEvent event, void *edata)
     }
 
     if (!context->state.error.Failed()) {
-      cripts::Cache::URL::_get(context).Update();  // Make sure the cache-key gets updated, if modified
-      cripts::Client::URL::_get(context).Update(); // Make sure any changes to the request URL is updated
+      if (context->_urls.cache.Modified()) {
+        context->_urls.cache._update(); // Make sure the cache-key gets updated, if modified
+      }
+      if (context->_urls.request.Modified()) {
+        context->_urls.request._update(); // Make sure any changes to the request URL is updated
+      }
     }
     break;
 
@@ -725,7 +737,7 @@ TSPluginInit(int argc, const char *argv[])
   }
 
   // ToDo: This InstanceContext should also be usabled / used by other non-HTTP hooks.
-  cripts::InstanceContext *context = new cripts::InstanceContext(*inst);
+  auto *context = new cripts::InstanceContext(*inst);
 
   pthread_once(&init_once_control, global_initialization);
   bool needs_glb_init = wrap_glb_init(context, false, CaseArg);
@@ -866,11 +878,11 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
     (wrap_txn_close(static_cast<cripts::Context *>(nullptr), false, CaseArg) ? cripts::Callbacks::DO_TXN_CLOSE :
                                                                                cripts::Callbacks::NONE);
 
-  TSHttpSsn ssnp         = TSHttpTxnSsnGet(txnp);
-  auto     *inst         = static_cast<cripts::Instance *>(ih);
-  auto      bundle_cbs   = inst->Callbacks();
-  auto     *context      = cripts::Context::Factory(txnp, ssnp, rri, *inst);
-  bool      keep_context = false;
+  auto  ssnp         = TSHttpTxnSsnGet(txnp);
+  auto *inst         = static_cast<cripts::Instance *>(ih);
+  auto  bundle_cbs   = inst->Callbacks();
+  auto *context      = cripts::Context::Factory(txnp, ssnp, rri, *inst);
+  bool  keep_context = false;
 
   context->state.hook          = TS_HTTP_READ_REQUEST_HDR_HOOK; // Not quite true
   context->state.enabled_hooks = (enabled_hooks | bundle_cbs);
@@ -887,8 +899,12 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
 
   // Don't do the callbacks when we are in a failure state.
   if (!context->state.error.Failed()) {
-    cripts::Cache::URL::_get(context).Update();  // Make sure the cache-key gets updated, if modified
-    cripts::Client::URL::_get(context).Update(); // Make sure any changes to the request URL is updated
+    if (context->_urls.cache.Modified()) {
+      context->_urls.cache._update(); // Make sure the cache-key gets updated, if modified
+    }
+    if (context->_urls.request.Modified()) {
+      context->_urls.request._update(); // Make sure any changes to the request URL is updated
+    }
 
     if (context->state.enabled_hooks >= cripts::Callbacks::DO_POST_REMAP) {
       context->contp = TSContCreate(http_txn_cont, nullptr);
@@ -935,7 +951,7 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
   }
 
   // See if the Client URL was modified, which dicates the return code here.
-  if (cripts::Client::URL::_get(context).Modified()) {
+  if (context->_urls.request.Modified()) {
     context->p_instance.debug("Client::URL was modified, returning TSREMAP_DID_REMAP");
     return TSREMAP_DID_REMAP;
   } else {
diff --git a/include/cripts/Headers.hpp b/include/cripts/Headers.hpp
index ba448b02a1e..f35a743fc8b 100644
--- a/include/cripts/Headers.hpp
+++ b/include/cripts/Headers.hpp
@@ -345,18 +345,20 @@ class Header
       _hdr_loc = nullptr;
       _bufp    = nullptr;
     }
-    _state = nullptr;
+    _initialized = false;
   }
 
   [[nodiscard]] TSMBuffer
-  BufP() const
+  BufP()
   {
+    _ensure_initialized(this);
     return _bufp;
   }
 
   [[nodiscard]] TSMLoc
-  MLoc() const
+  MLoc()
   {
+    _ensure_initialized(this);
     return _hdr_loc;
   }
 
@@ -365,12 +367,13 @@ class Header
   [[nodiscard]] bool
   Initialized() const
   {
-    return (_state != nullptr);
+    return _initialized;
   }
 
   void
   Erase(const cripts::string_view header)
   {
+    _ensure_initialized(this);
     operator[](header) = "";
   }
 
@@ -383,23 +386,35 @@ class Header
     return Iterator::end(); // Static end iterator. ToDo: Does this have any value over making a new one always?
   }
 
+  // This should only be called from the Context initializers!
+  void
+  set_state(cripts::Transaction *state)
+  {
+    _state = state;
+  }
+
   Status      status;
   Reason      reason;
   Body        body;
   CacheStatus cache;
 
 protected:
-  void
-  _initialize(cripts::Transaction *state)
+  static void
+  _ensure_initialized(self_type *ptr)
   {
-    _state = state;
+    if (!ptr->Initialized()) [[unlikely]] {
+      ptr->_initialize();
+    }
   }
 
+  void virtual _initialize() { _initialized = true; }
+
   TSMBuffer            _bufp         = nullptr;
   TSMLoc               _hdr_loc      = nullptr;
   cripts::Transaction *_state        = nullptr; // Pointer into the owning Context's State
   TSMLoc               _iterator_loc = nullptr;
   uint32_t             _iterator_tag = 0; // This is used to assure that we don't have more than one active iterator on a header
+  bool                 _initialized  = false;
 
 }; // End class Header
 
@@ -440,6 +455,7 @@ namespace Client
 
     // Implemented later, because needs the context.
     static self_type &_get(cripts::Context *context);
+    void              _initialize() override;
 
   }; // End class Client::Request
 
@@ -455,6 +471,7 @@ namespace Client
 
     // Implemented later, because needs the context.
     static self_type &_get(cripts::Context *context);
+    void              _initialize() override;
 
   }; // End class Client::Response
 
@@ -473,7 +490,9 @@ namespace Server
     void operator=(const self_type &) = delete;
 
     // Implemented later, because needs the context.
-    static Request &_get(cripts::Context *context);
+    static self_type &_get(cripts::Context *context);
+    void              _initialize() override;
+
   }; // End class Server::Request
 
   class Response : public ResponseHeader
@@ -488,6 +507,7 @@ namespace Server
 
     // Implemented later, because needs the context.
     static self_type &_get(cripts::Context *context);
+    void              _initialize() override;
 
   }; // End class Server::Response
 
diff --git a/include/cripts/Preamble.hpp b/include/cripts/Preamble.hpp
index 518b3bfec23..9f92fa958ea 100644
--- a/include/cripts/Preamble.hpp
+++ b/include/cripts/Preamble.hpp
@@ -107,3 +107,18 @@ using fmt::format;
 extern cripts::Proxy    proxy;   // Access to all overridable configurations
 extern cripts::Control  control; // Access to the HTTP control mechanism
 extern cripts::Versions version; // Access to the ATS version information
+
+// These are not enabled by default, since they adds additional overhead
+// and pollution of the top level namespace.
+#if CRIPTS_CONVENIENCE_APIS
+#define client                      context->_client
+#define server                      context->_server
+#define urls                        context->_urls
+#define Regex(_name_, ...)          static cripts::Matcher::PCRE _name_(__VA_ARGS__);
+#define ACL(_name_, ...)            static cripts::Matcher::Range::IP _name_(__VA_ARGS__);
+#define CreateCounter(_id_, _name_) instance.metrics[_id_] = cripts::Metrics::Counter::Create(_name_);
+#define CreateGauge(_id_, _name_)   instance.metrics[_id_] = cripts::Metrics::Gauge::Create(_name_);
+#define FilePath(_name_, _path_)    static const cripts::File::Path _name_(_path_);
+#define UniqueUUID()                cripts::UUID::Unique::Get();
+#define TimeNow()                   cripts::Time::Local::Now();
+#endif
diff --git a/include/cripts/Urls.hpp b/include/cripts/Urls.hpp
index c3d049112ed..47c8c5069ee 100644
--- a/include/cripts/Urls.hpp
+++ b/include/cripts/Urls.hpp
@@ -33,6 +33,8 @@ class Context;
 
 class Url
 {
+  using self_type = Url;
+
   class Component
   {
     using self_type = Component;
@@ -537,13 +539,14 @@ class Url
       query.Reset();
       path.Reset();
     }
-    _state = nullptr;
+    _initialized = false;
+    _modified    = false;
   }
 
   [[nodiscard]] bool
   Initialized() const
   {
-    return (_state != nullptr);
+    return _initialized;
   }
 
   [[nodiscard]] bool
@@ -564,8 +567,15 @@ class Url
     return false;
   }
 
+  // Some of these URL objects needs the full Context, making life miserable.
+  void
+  set_context(cripts::Context *context)
+  {
+    _context = context;
+  }
+
   // This is the full string for a URL, which needs allocations.
-  [[nodiscard]] cripts::string String() const;
+  [[nodiscard]] cripts::string String();
 
   Scheme scheme;
   Host   host;
@@ -574,17 +584,28 @@ class Url
   Query  query;
 
 protected:
-  void
-  _initialize(cripts::Transaction *state)
+  static void
+  _ensure_initialized(self_type *ptr)
+  {
+    if (!ptr->Initialized()) [[unlikely]] {
+      ptr->_initialize();
+    }
+  }
+
+  virtual void
+  _initialize()
   {
-    _state = state;
+    _initialized = true;
+    _modified    = false;
   }
 
-  TSMBuffer            _bufp     = nullptr; // These two gets setup via initializing, to appropriate headers
-  TSMLoc               _hdr_loc  = nullptr; // Do not release any of this within the URL classes!
-  TSMLoc               _urlp     = nullptr; // This is owned by us.
-  cripts::Transaction *_state    = nullptr; // Pointer into the owning Context's State
-  bool                 _modified = false;   // We have pending changes on the path/query components
+  TSMBuffer            _bufp        = nullptr; // These two gets setup via initializing, to appropriate headers
+  TSMLoc               _hdr_loc     = nullptr; // Do not release any of this within the URL classes!
+  TSMLoc               _urlp        = nullptr; // This is owned by us.
+  cripts::Transaction *_state       = nullptr; // Pointer into the owning Context's State
+  cripts::Context     *_context     = nullptr; // Pointer to the owning Context
+  bool                 _modified    = false;   // We have pending changes on the path/query components
+  bool                 _initialized = false;   // Have we been initialized ?
 
 }; // End class Url
 
@@ -609,6 +630,9 @@ namespace Pristine
       return true;
     }
 
+  protected:
+    void _initialize() override;
+
   }; // End class Pristine::URL
 
 } // namespace Pristine
@@ -632,10 +656,10 @@ namespace Client
     }
 
     static self_type &_get(cripts::Context *context);
-    bool              _update(cripts::Context *context);
+    bool              _update();
 
-  private:
-    void _initialize(cripts::Context *context);
+  protected:
+    void _initialize() override;
 
   }; // End class Client::URL
 
@@ -668,10 +692,10 @@ namespace Remap
       }
 
       static self_type &_get(cripts::Context *context);
-      bool              _update(cripts::Context *context);
+      bool              Update();
 
     private:
-      void _initialize(cripts::Context *context);
+      void _initialize() override;
 
     }; // End class Client::URL
 
@@ -702,10 +726,10 @@ namespace Remap
       }
 
       static self_type &_get(cripts::Context *context);
-      bool              _update(cripts::Context *context);
+      bool              Update();
 
     private:
-      void _initialize(cripts::Context *context);
+      void _initialize() override;
 
     }; // End class Client::URL
 
@@ -726,17 +750,10 @@ namespace Cache
     void operator=(const self_type &) = delete;
 
     static self_type &_get(cripts::Context *context);
-    bool              _update(cripts::Context *context);
+    bool              _update();
 
-  private:
-    void
-    _initialize(cripts::Transaction *state, Client::Request *req)
-    {
-      Url::_initialize(state);
-
-      _bufp    = req->BufP();
-      _hdr_loc = req->MLoc();
-    }
+  protected:
+    void _initialize() override;
 
   }; // End class Cache::URL
 
@@ -755,17 +772,10 @@ namespace Parent
     void operator=(const self_type &) = delete;
 
     static self_type &_get(cripts::Context *context);
-    bool              _update(cripts::Context *context);
-
-  private:
-    void
-    _initialize(cripts::Transaction *state, Client::Request *req)
-    {
-      Url::_initialize(state);
+    bool              Update();
 
-      _bufp    = req->BufP();
-      _hdr_loc = req->MLoc();
-    }
+  protected:
+    void _initialize() override;
 
   }; // End class Cache::URL
 
@@ -776,6 +786,21 @@ namespace Parent
 // Formatters for {fmt}
 namespace fmt
 {
+template <std::derived_from<cripts::Url> T> struct formatter<T> {
+  constexpr auto
+  parse(format_parse_context &ctx) -> decltype(ctx.begin())
+  {
+    return ctx.begin();
+  }
+
+  template <typename FormatContext>
+  auto
+  format(T &url, FormatContext &ctx) const -> decltype(ctx.out())
+  {
+    return fmt::format_to(ctx.out(), "{}", url.String());
+  }
+};
+
 template <> struct formatter<cripts::Url::Scheme> {
   constexpr auto
   parse(format_parse_context &ctx) -> decltype(ctx.begin())
diff --git a/src/cripts/Connections.cc b/src/cripts/Connections.cc
index 98001cb03de..eac1c223f03 100644
--- a/src/cripts/Connections.cc
+++ b/src/cripts/Connections.cc
@@ -30,7 +30,7 @@ const cripts::Matcher::Range::IP cripts::Net::RFC1918({"10.0.0.0/8", "172.16.0.0
 void
 detail::ConnBase::Pacing::operator=(uint32_t val)
 {
-  TSAssert(_owner);
+  _ensure_initialized(_owner);
   if (val == 0) {
     val = Off;
   }
@@ -51,6 +51,7 @@ void
 detail::ConnBase::TcpInfo::initialize()
 {
 #if defined(TCP_INFO) && defined(HAVE_STRUCT_TCP_INFO)
+  _ensure_initialized(_owner);
   if (!_ready) {
     int connfd = _owner->FD();
 
@@ -70,6 +71,7 @@ detail::ConnBase::TcpInfo::initialize()
 cripts::string_view
 detail::ConnBase::TcpInfo::Log()
 {
+  _ensure_initialized(_owner);
   initialize();
   // We intentionally do not use the old tcpinfo that may be stored, since we may
   // request this numerous times (measurements). Also make sure there's always a value.
@@ -205,18 +207,19 @@ IP::Sample(double rate, uint32_t seed, unsigned ipv4_cidr, unsigned ipv6_cidr)
 Client::Connection &
 Client::Connection::_get(cripts::Context *context)
 {
-  Client::Connection *conn = &context->_client_conn;
-
-  if (!conn->Initialized()) {
-    TSAssert(context->state.ssnp);
-    TSAssert(context->state.txnp);
+  _ensure_initialized(&context->_client.connection);
+  return context->_client.connection;
+}
 
-    conn->_state  = &context->state;
-    conn->_vc     = TSHttpSsnClientVConnGet(context->state.ssnp);
-    conn->_socket = TSHttpTxnClientAddrGet(context->state.txnp);
-  }
+void
+Client::Connection::_initialize()
+{
+  TSAssert(_state->ssnp);
+  TSAssert(_state->txnp);
 
-  return context->_client_conn;
+  _vc     = TSHttpSsnClientVConnGet(_state->ssnp);
+  _socket = TSHttpTxnClientAddrGet(_state->txnp);
+  super_type::_initialize();
 }
 
 int
@@ -233,27 +236,25 @@ Client::Connection::FD() const
 int
 Client::Connection::Count() const
 {
-  TSHttpSsn ssn = TSHttpTxnSsnGet(_state->txnp);
-
-  TSAssert(_state->txnp);
-
-  return ssn ? TSHttpSsnTransactionCount(ssn) : -1;
+  TSAssert(_state->ssnp);
+  return TSHttpSsnTransactionCount(_state->ssnp);
 }
 
 Server::Connection &
 Server::Connection::_get(cripts::Context *context)
 {
-  cripts::Server::Connection *conn = &context->_server_conn;
-
-  if (!conn->Initialized()) {
-    TSAssert(context->state.ssnp);
+  _ensure_initialized(&context->_server.connection);
+  return context->_server.connection;
+}
 
-    conn->_state  = &context->state;
-    conn->_vc     = TSHttpSsnServerVConnGet(context->state.ssnp);
-    conn->_socket = TSHttpTxnServerAddrGet(context->state.txnp);
-  }
+void
+Server::Connection::_initialize()
+{
+  TSAssert(_state->ssnp);
 
-  return context->_server_conn;
+  _vc     = TSHttpSsnServerVConnGet(_state->ssnp);
+  _socket = TSHttpTxnServerAddrGet(_state->txnp);
+  super_type::_initialize();
 }
 
 int
@@ -270,6 +271,7 @@ Server::Connection::FD() const
 int
 Server::Connection::Count() const
 {
+  TSAssert(_state->txnp);
   return TSHttpTxnServerSsnTransactionCount(_state->txnp);
 }
 
diff --git a/src/cripts/Context.cc b/src/cripts/Context.cc
index 0359aa4c8df..536f8bfde2e 100644
--- a/src/cripts/Context.cc
+++ b/src/cripts/Context.cc
@@ -35,28 +35,28 @@ Context::reset()
   // Clear the initialized headers before calling next hook
   // Note: we don't clear the pristine URL, nor the Remap From/To URLs, they are static.
   //      We also don't clear the client URL, since it's from the RRI.
-  if (_client_resp_header.Initialized()) {
-    _client_resp_header.Reset();
+  if (_client.response.Initialized()) {
+    _client.response.Reset();
   }
-  if (_server_resp_header.Initialized()) {
-    _server_resp_header.Reset();
+  if (_server.response.Initialized()) {
+    _server.response.Reset();
   }
-  if (_client_req_header.Initialized()) {
-    _client_req_header.Reset();
+  if (_client.request.Initialized()) {
+    _client.request.Reset();
   }
-  if (_server_req_header.Initialized()) {
-    _server_req_header.Reset();
+  if (_server.request.Initialized()) {
+    _server.request.Reset();
   }
 
   // Clear the initialized URLs before calling next hook
-  if (_pristine_url.Initialized()) {
-    _pristine_url.Reset();
+  if (_urls.pristine.Initialized()) {
+    _urls.pristine.Reset();
   }
-  if (_cache_url.Initialized()) {
-    _cache_url.Reset();
+  if (_urls.cache.Initialized()) {
+    _urls.cache.Reset();
   }
-  if (_parent_url.Initialized()) {
-    _parent_url.Reset();
+  if (_urls.parent.Initialized()) {
+    _urls.parent.Reset();
   }
 }
 
diff --git a/src/cripts/Headers.cc b/src/cripts/Headers.cc
index 8c8e704799e..5b199cfe0a7 100644
--- a/src/cripts/Headers.cc
+++ b/src/cripts/Headers.cc
@@ -45,6 +45,7 @@ namespace Method
 Header::Status &
 Header::Status::operator=(int status)
 {
+  _ensure_initialized(_owner);
   _status = static_cast<TSHttpStatus>(status);
 
   switch (_owner->_state->hook) {
@@ -66,6 +67,7 @@ Header::Status::operator=(int status)
 Header::Status::operator integer()
 {
   if (_status == TS_HTTP_STATUS_NONE) {
+    _ensure_initialized(_owner);
     _status = TSHttpHdrStatusGet(_owner->_bufp, _owner->_hdr_loc);
   }
   return _status;
@@ -74,6 +76,7 @@ Header::Status::operator integer()
 Header::Reason &
 Header::Reason::operator=(cripts::string_view reason)
 {
+  _ensure_initialized(_owner);
   TSHttpHdrReasonSet(_owner->_bufp, _owner->_hdr_loc, reason.data(), reason.size());
   _owner->_state->context->p_instance.debug("Setting reason = {}", reason);
 
@@ -85,6 +88,7 @@ Header::Body::operator=(cripts::string_view body)
 {
   auto b = static_cast<char *>(TSmalloc(body.size() + 1));
 
+  _ensure_initialized(_owner);
   memcpy(b, body.data(), body.size());
   b[body.size()] = '\0';
   TSHttpTxnErrorBodySet(_owner->_state->txnp, b, body.size(), nullptr);
@@ -96,6 +100,7 @@ cripts::string_view
 Header::Method::GetSV()
 {
   if (_method.size() == 0) {
+    _ensure_initialized(_owner);
     int         len;
     const char *value = TSHttpHdrMethodGet(_owner->_bufp, _owner->_hdr_loc, &len);
 
@@ -116,6 +121,7 @@ Header::CacheStatus::GetSV()
   };
   int status;
 
+  _ensure_initialized(_owner);
   if (_cache.size() == 0) {
     TSAssert(_owner->_state->txnp);
     if (TSHttpTxnCacheLookupStatusGet(_owner->_state->txnp, &status) == TS_ERROR || status < 0 || status >= 4) {
@@ -131,6 +137,7 @@ Header::CacheStatus::GetSV()
 Header::String &
 Header::String::operator=(const cripts::string_view str)
 {
+  _ensure_initialized(_owner);
   if (_field_loc) {
     if (str.empty()) {
       TSMLoc tmp;
@@ -183,6 +190,7 @@ Header::String::operator=(const cripts::string_view str)
 Header::String &
 Header::String::operator=(integer val)
 {
+  _ensure_initialized(_owner);
   if (_field_loc) {
     TSMLoc tmp   = nullptr;
     bool   first = true;
@@ -218,6 +226,7 @@ Header::String::operator=(integer val)
 Header::String &
 Header::String::operator+=(const cripts::string_view str)
 {
+  _ensure_initialized(_owner);
   if (_field_loc) {
     if (!str.empty()) {
       // Drop the old field loc for now ... ToDo: Oh well, we need to figure out how to handle multi-value headers better
@@ -242,6 +251,7 @@ Header::String::operator+=(const cripts::string_view str)
 Header::String
 Header::operator[](const cripts::string_view str)
 {
+  _ensure_initialized(this);
   TSAssert(_bufp && _hdr_loc);
 
   TSMLoc         field_loc = TSMimeHdrFieldFind(_bufp, _hdr_loc, str.data(), str.size());
@@ -262,23 +272,26 @@ Header::operator[](const cripts::string_view str)
 Client::Request &
 Client::Request::_get(cripts::Context *context)
 {
-  Client::Request *request = &context->_client_req_header;
+  _ensure_initialized(&context->_client.request);
+  return context->_client.request;
+}
 
-  if (!request->Initialized()) {
-    TSAssert(context->state.txnp);
-    if (TSHttpTxnClientReqGet(context->state.txnp, &request->_bufp, &request->_hdr_loc) != TS_SUCCESS) {
-      context->state.error.Fail();
-    } else {
-      request->_initialize(&context->state); // Don't initialize unless properly setup
-    }
-  }
+void
+Client::Request::_initialize()
+{
+  TSAssert(_state->txnp);
 
-  return context->_client_req_header;
+  if (TSHttpTxnClientReqGet(_state->txnp, &_bufp, &_hdr_loc) != TS_SUCCESS) {
+    _state->error.Fail();
+  } else {
+    super_type::_initialize(); // Don't initialize unless properly setup
+  }
 }
 
 Header::Iterator
 Header::begin()
 {
+  _ensure_initialized(this);
   // Cleanup any lingering iterator state
   if (_iterator_loc) {
     TSHandleMLocRelease(_bufp, _hdr_loc, _iterator_loc);
@@ -304,6 +317,7 @@ Header::begin()
 cripts::string_view
 Header::iterate()
 {
+  _ensure_initialized(this);
   TSMLoc next_loc = TSMimeHdrFieldNext(_bufp, _hdr_loc, _iterator_loc);
 
   TSHandleMLocRelease(_bufp, _hdr_loc, _iterator_loc);
@@ -319,69 +333,76 @@ Header::iterate()
   }
 }
 
-cripts::Client::Response &
+Client::Response &
 Client::Response::_get(cripts::Context *context)
 {
-  CAssert(context->state.hook != TS_HTTP_READ_REQUEST_HDR_HOOK);
-  CAssert(context->state.hook != TS_HTTP_POST_REMAP_HOOK);
-  CAssert(context->state.hook != TS_HTTP_SEND_REQUEST_HDR_HOOK);
+  _ensure_initialized(&context->_client.response);
+  return context->_client.response;
+}
 
-  Client::Response *response = &context->_client_resp_header;
+void
+Client::Response::_initialize()
+{
+  CAssert(_state->hook != TS_HTTP_READ_REQUEST_HDR_HOOK);
+  CAssert(_state->hook != TS_HTTP_POST_REMAP_HOOK);
+  CAssert(_state->hook != TS_HTTP_SEND_REQUEST_HDR_HOOK);
 
-  if (!response->Initialized()) {
-    TSAssert(context->state.txnp);
-    if (TSHttpTxnClientRespGet(context->state.txnp, &response->_bufp, &response->_hdr_loc) != TS_SUCCESS) {
-      context->state.error.Fail();
-    } else {
-      response->_initialize(&context->state); // Don't initialize unless properly setup
-    }
-  }
+  TSAssert(_state->txnp);
 
-  return context->_client_resp_header;
+  if (TSHttpTxnClientRespGet(_state->txnp, &_bufp, &_hdr_loc) != TS_SUCCESS) {
+    _state->error.Fail();
+  } else {
+    super_type::_initialize(); // Don't initialize unless properly setup
+  }
 }
 
 Server::Request &
 Server::Request::_get(cripts::Context *context)
 {
-  CAssert(context->state.hook != TS_HTTP_READ_REQUEST_HDR_HOOK);
-  CAssert(context->state.hook != TS_HTTP_POST_REMAP_HOOK);
-  CAssert(context->state.hook != TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK);
-  CAssert(context->state.hook != TS_HTTP_READ_RESPONSE_HDR_HOOK);
-
-  Server::Request *request = &context->_server_req_header;
+  _ensure_initialized(&context->_server.request);
+  return context->_server.request;
+}
 
-  if (!request->Initialized()) {
-    TSAssert(context->state.txnp);
-    if (TSHttpTxnServerReqGet(context->state.txnp, &request->_bufp, &request->_hdr_loc) != TS_SUCCESS) {
-      context->state.error.Fail();
+void
+Server::Request::_initialize()
+{
+  CAssert(_state->hook != TS_HTTP_READ_REQUEST_HDR_HOOK);
+  CAssert(_state->hook != TS_HTTP_POST_REMAP_HOOK);
+  CAssert(_state->hook != TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK);
+  CAssert(_state->hook != TS_HTTP_READ_RESPONSE_HDR_HOOK);
+
+  if (!Initialized()) {
+    TSAssert(_state->txnp);
+    if (TSHttpTxnServerReqGet(_state->txnp, &_bufp, &_hdr_loc) != TS_SUCCESS) {
+      _state->error.Fail();
     } else {
-      request->_initialize(&context->state); // Don't initialize unless properly setup
+      super_type::_initialize(); // Don't initialize unless properly setup
     }
   }
-
-  return context->_server_req_header;
 }
 
 Server::Response &
 Server::Response::_get(cripts::Context *context)
 {
-  CAssert(context->state.hook != TS_HTTP_READ_REQUEST_HDR_HOOK);
-  CAssert(context->state.hook != TS_HTTP_POST_REMAP_HOOK);
-  CAssert(context->state.hook != TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK);
-  CAssert(context->state.hook != TS_HTTP_SEND_REQUEST_HDR_HOOK);
+  _ensure_initialized(&context->_server.response);
+  return context->_server.response;
+}
+
+void
+Server::Response::_initialize()
+{
+  CAssert(_state->hook != TS_HTTP_READ_REQUEST_HDR_HOOK);
+  CAssert(_state->hook != TS_HTTP_POST_REMAP_HOOK);
+  CAssert(_state->hook != TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK);
+  CAssert(_state->hook != TS_HTTP_SEND_REQUEST_HDR_HOOK);
 
-  Server::Response *response = &context->_server_resp_header;
+  TSAssert(_state->txnp);
 
-  if (!response->Initialized()) {
-    TSAssert(context->state.txnp);
-    if (TSHttpTxnServerRespGet(context->state.txnp, &response->_bufp, &response->_hdr_loc) != TS_SUCCESS) {
-      context->state.error.Fail();
-    } else {
-      response->_initialize(&context->state); // Don't initialize unless properly setup
-    }
+  if (TSHttpTxnServerRespGet(_state->txnp, &_bufp, &_hdr_loc) != TS_SUCCESS) {
+    _state->error.Fail();
+  } else {
+    super_type::_initialize(); // Don't initialize unless properly setup
   }
-
-  return context->_server_resp_header;
 }
 
 } // namespace cripts
diff --git a/src/cripts/Urls.cc b/src/cripts/Urls.cc
index 9d0423371a2..749b802602a 100644
--- a/src/cripts/Urls.cc
+++ b/src/cripts/Urls.cc
@@ -36,6 +36,7 @@ Url::Scheme::GetSV()
     const char *value = nullptr;
     int         len   = 0;
 
+    _ensure_initialized(_owner);
     value = TSUrlSchemeGet(_owner->_bufp, _owner->_urlp, &len);
     _data = cripts::string_view(value, len);
   }
@@ -47,6 +48,7 @@ Url::Scheme
 Url::Scheme::operator=(cripts::string_view scheme)
 {
   CAssert(!_owner->ReadOnly()); // This can not be a read-only URL
+  _ensure_initialized(_owner);
   TSUrlSchemeSet(_owner->_bufp, _owner->_urlp, scheme.data(), scheme.size());
   _owner->_modified = true;
   Reset();
@@ -62,6 +64,7 @@ Url::Host::GetSV()
     const char *value = nullptr;
     int         len   = 0;
 
+    _ensure_initialized(_owner);
     value   = TSUrlHostGet(_owner->_bufp, _owner->_urlp, &len);
     _data   = cripts::string_view(value, len);
     _loaded = true;
@@ -73,6 +76,7 @@ Url::Host::GetSV()
 Url::Host
 Url::Host::operator=(cripts::string_view host)
 {
+  _ensure_initialized(_owner);
   CAssert(!_owner->ReadOnly()); // This can not be a read-only URL
   TSUrlHostSet(_owner->_bufp, _owner->_urlp, host.data(), host.size());
   _owner->_modified = true;
@@ -84,6 +88,7 @@ Url::Host::operator=(cripts::string_view host)
 
 Url::Port::operator integer() // This should not be explicit
 {
+  _ensure_initialized(_owner);
   if (_owner && _port < 0) {
     _port = TSUrlPortGet(_owner->_bufp, _owner->_urlp);
   }
@@ -94,6 +99,7 @@ Url::Port::operator integer() // This should not be explicit
 Url::Port
 Url::Port::operator=(int port)
 {
+  _ensure_initialized(_owner);
   CAssert(!_owner->ReadOnly()); // This can not be a read-only URL
   TSUrlPortSet(_owner->_bufp, _owner->_urlp, port);
   _owner->_modified = true;
@@ -120,6 +126,7 @@ Url::Path::GetSV()
     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;
@@ -134,6 +141,7 @@ Url::Path::operator[](Segments::size_type ix)
 {
   Url::Path::String ret;
 
+  _ensure_initialized(_owner);
   _parser(); // Make sure the segments are loaded
   if (ix < _segments.size()) {
     ret._initialize(_segments[ix], this, ix);
@@ -145,6 +153,7 @@ Url::Path::operator[](Segments::size_type ix)
 Url::Path
 Url::Path::operator=(cripts::string_view path)
 {
+  _ensure_initialized(_owner);
   CAssert(!_owner->ReadOnly()); // This can not be a read-only URL
   TSUrlPathSet(_owner->_bufp, _owner->_urlp, path.data(), path.size());
   _owner->_modified = true;
@@ -171,6 +180,7 @@ Url::Path::operator+=(cripts::string_view add)
 Url::Path::String &
 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;
@@ -218,6 +228,7 @@ Url::Path::_parser()
 Url::Query::Parameter &
 Url::Query::Parameter::operator=(const cripts::string_view str)
 {
+  _ensure_initialized(_owner->_owner);
   CAssert(!_owner->_owner->ReadOnly()); // This can not be a read-only URL
   auto iter = _owner->_hashed.find(_name);
 
@@ -235,6 +246,7 @@ Url::Query::Parameter::operator=(const cripts::string_view str)
 cripts::string_view
 Url::Query::GetSV()
 {
+  _ensure_initialized(_owner);
   if (_ordered.size() > 0) {
     _storage.clear();
     _storage.reserve(_size);
@@ -278,6 +290,7 @@ Url::Query::GetSV()
 Url::Query
 Url::Query::operator=(cripts::string_view query)
 {
+  _ensure_initialized(_owner);
   CAssert(!_owner->ReadOnly()); // This can not be a read-only URL
   TSUrlHttpQuerySet(_owner->_bufp, _owner->_urlp, query.data(), query.size());
   _owner->_modified = true;
@@ -305,6 +318,7 @@ Url::Query::Parameter
 Url::Query::operator[](cripts::string_view param)
 {
   // Make sure the hash and vector are populated
+  _ensure_initialized(_owner);
   _parser();
 
   Parameter ret;
@@ -406,18 +420,18 @@ Url::Query::_parser()
 }
 
 cripts::string
-Url::String() const
+Url::String()
 {
   cripts::string ret;
 
-  if (_state) {
-    int   full_len = 0;
-    char *full_str = TSUrlStringGet(_bufp, _urlp, &full_len);
+  CAssert(_context);
+  _ensure_initialized(this);
+  int   full_len = 0;
+  char *full_str = TSUrlStringGet(_bufp, _urlp, &full_len);
 
-    if (full_str) {
-      ret.assign(full_str, full_len);
-      TSfree(static_cast<void *>(full_str));
-    }
+  if (full_str) {
+    ret.assign(full_str, full_len);
+    TSfree(static_cast<void *>(full_str));
   }
 
   return ret;
@@ -426,37 +440,41 @@ Url::String() const
 Pristine::URL &
 Pristine::URL::_get(cripts::Context *context)
 {
-  if (!context->_pristine_url.Initialized()) {
-    Pristine::URL *url = &context->_pristine_url;
+  _ensure_initialized(&context->_urls.pristine);
+  return context->_urls.pristine;
+}
 
-    TSAssert(context->state.txnp);
-    if (TSHttpTxnPristineUrlGet(context->state.txnp, &url->_bufp, &url->_urlp) != TS_SUCCESS) {
-      context->state.error.Fail();
-    } else {
-      url->_initialize(&context->state);
-    }
-  }
+void
+Pristine::URL::_initialize()
+{
+  Pristine::URL *url = &_context->_urls.pristine;
 
-  return context->_pristine_url;
+  TSAssert(_context->state.txnp);
+  if (TSHttpTxnPristineUrlGet(_context->state.txnp, &url->_bufp, &url->_urlp) != TS_SUCCESS) {
+    _context->state.error.Fail();
+  } else {
+    super_type::_initialize(); // Only if successful
+  }
 }
 
 void
-Client::URL::_initialize(cripts::Context *context)
+Client::URL::_initialize()
 {
-  Url::_initialize(&context->state);
-
-  if (context->rri) {
-    _bufp    = context->rri->requestBufp;
-    _hdr_loc = context->rri->requestHdrp;
-    _urlp    = context->rri->requestUrl;
+  if (_context->rri) {
+    _bufp    = _context->rri->requestBufp;
+    _hdr_loc = _context->rri->requestHdrp;
+    _urlp    = _context->rri->requestUrl;
+    super_type::_initialize();
   } else {
-    Client::Request &req = Client::Request::_get(context); // Repurpose / create the shared request object
+    Client::Request &req = Client::Request::_get(_context); // Repurpose / create the shared request object
 
     _bufp    = req.BufP();
     _hdr_loc = req.MLoc();
 
     if (TSHttpHdrUrlGet(_bufp, _hdr_loc, &_urlp) != TS_SUCCESS) {
-      context->state.error.Fail();
+      _context->state.error.Fail();
+    } else {
+      super_type::_initialize();
     }
   }
 }
@@ -464,16 +482,14 @@ Client::URL::_initialize(cripts::Context *context)
 Client::URL &
 Client::URL::_get(cripts::Context *context)
 {
-  if (!context->_client_url.Initialized()) {
-    context->_client_url._initialize(context);
-  }
-
-  return context->_client_url;
+  _ensure_initialized(&context->_urls.request);
+  return context->_urls.request;
 }
 
 bool
-Client::URL::_update(cripts::Context * /* context ATS_UNUSED */)
+Client::URL::_update()
 {
+  _ensure_initialized(this);
   path.Flush();
   query.Flush();
 
@@ -481,103 +497,106 @@ Client::URL::_update(cripts::Context * /* context ATS_UNUSED */)
 }
 
 void
-Remap::From::URL::_initialize(cripts::Context *context)
+Remap::From::URL::_initialize()
 {
-  Url::_initialize(&context->state);
-
-  _bufp    = context->rri->requestBufp;
-  _hdr_loc = context->rri->requestHdrp;
-  _urlp    = context->rri->mapFromUrl;
+  super_type::_initialize();
+  _bufp    = _context->rri->requestBufp;
+  _hdr_loc = _context->rri->requestHdrp;
+  _urlp    = _context->rri->mapFromUrl;
 }
 
 Remap::From::URL &
 Remap::From::URL::_get(cripts::Context *context)
 {
-  if (!context->_remap_from_url.Initialized()) {
-    context->_remap_from_url._initialize(context);
-  }
-
-  return context->_remap_from_url;
+  _ensure_initialized(&context->_urls.remap.from);
+  return context->_urls.remap.from;
 }
 
 void
-Remap::To::URL::_initialize(cripts::Context *context)
+Remap::To::URL::_initialize()
 {
-  Url::_initialize(&context->state);
+  super_type::_initialize();
 
-  _bufp    = context->rri->requestBufp;
-  _hdr_loc = context->rri->requestHdrp;
-  _urlp    = context->rri->mapToUrl;
+  _bufp    = _context->rri->requestBufp;
+  _hdr_loc = _context->rri->requestHdrp;
+  _urlp    = _context->rri->mapToUrl;
 }
 
 Remap::To::URL &
 Remap::To::URL::_get(cripts::Context *context)
 {
-  if (!context->_remap_to_url.Initialized()) {
-    context->_remap_to_url._initialize(context);
-  }
-
-  return context->_remap_to_url;
+  _ensure_initialized(&context->_urls.remap.to);
+  return context->_urls.remap.to;
 }
 
 Cache::URL &
 Cache::URL::_get(cripts::Context *context)
 {
-  if (!context->_cache_url.Initialized()) {
-    Cache::URL      *url = &context->_cache_url;
-    Client::Request &req = Client::Request::_get(context); // Repurpose / create the shared request object
-
-    switch (context->state.hook) {
-    // In these hooks, the internal cache-url has been properly set
-    case TS_HTTP_SEND_RESPONSE_HDR_HOOK:
-    case TS_HTTP_READ_RESPONSE_HDR_HOOK:
-    case TS_HTTP_SEND_REQUEST_HDR_HOOK:
-    case TS_HTTP_TXN_CLOSE_HOOK:
-      if (TSUrlCreate(req.BufP(), &url->_urlp) == TS_SUCCESS) {
-        TSAssert(context->state.txnp);
-        if (TSHttpTxnCacheLookupUrlGet(context->state.txnp, req.BufP(), url->_urlp) == TS_SUCCESS) {
-          url->_initialize(&context->state, &req);
-        }
-      } else {
-        context->state.error.Fail();
-      }
-      break;
-    default: { // This means we have to clone. ToDo: For now, this is implicitly using Client::URL
-      Client::URL &src = Client::URL::_get(context);
+  _ensure_initialized(&context->_urls.cache);
+  return context->_urls.cache;
+}
 
-      if (TSUrlClone(req.BufP(), req.BufP(), src.UrlP(), &url->_urlp) == TS_SUCCESS) {
-        url->_initialize(&context->state, &req);
-      } else {
-        context->state.error.Fail();
+void
+Cache::URL::_initialize()
+{
+  Cache::URL      *url = &_context->_urls.cache;
+  Client::Request &req = Client::Request::_get(_context); // Repurpose / create the shared request object
+
+  switch (_context->state.hook) {
+  // In these hooks, the internal cache-url has been properly set
+  case TS_HTTP_SEND_RESPONSE_HDR_HOOK:
+  case TS_HTTP_READ_RESPONSE_HDR_HOOK:
+  case TS_HTTP_SEND_REQUEST_HDR_HOOK:
+  case TS_HTTP_TXN_CLOSE_HOOK:
+    if (TSUrlCreate(req.BufP(), &url->_urlp) == TS_SUCCESS) {
+      TSAssert(_context->state.txnp);
+      if (TSHttpTxnCacheLookupUrlGet(_context->state.txnp, req.BufP(), url->_urlp) != TS_SUCCESS) {
+        _context->state.error.Fail();
+        return;
       }
-    } break;
+    } else {
+      _context->state.error.Fail();
+      return;
     }
+    break;
+  default: { // This means we have to clone. ToDo: For now, this is implicitly using Client::URL
+    Client::URL &src = Client::URL::_get(_context);
+
+    if (TSUrlClone(req.BufP(), req.BufP(), src.UrlP(), &url->_urlp) != TS_SUCCESS) {
+      _context->state.error.Fail();
+      return;
+    }
+  } break;
   }
 
-  return context->_cache_url;
+  // Only if we succeeded above
+  super_type::_initialize();
+  _bufp    = req.BufP();
+  _hdr_loc = req.MLoc();
 }
 
 // This has to be implemented here, since the cripts::Context is not complete yet
 bool
-Cache::URL::_update(cripts::Context *context)
+Cache::URL::_update()
 {
   // For correctness, we will also make sure the Path and Query objects are flushed
   path.Flush();
   query.Flush();
 
   if (_modified) {
-    TSAssert(context->state.txnp);
+    _ensure_initialized(&_context->_urls.cache);
+    TSAssert(_context->state.txnp);
     _modified = false;
-    if (TS_SUCCESS == TSHttpTxnCacheLookupUrlSet(context->state.txnp, _bufp, _urlp)) {
-      if (context->p_instance.DebugOn()) {
-        context->p_instance.debug("Successfully setting cache-key to {}", String());
+    if (TS_SUCCESS == TSHttpTxnCacheLookupUrlSet(_context->state.txnp, _bufp, _urlp)) {
+      if (_context->p_instance.DebugOn()) {
+        _context->p_instance.debug("Successfully setting cache-key to {}", String());
       }
       return true;
     } else {
-      if (context->p_instance.DebugOn()) {
-        context->p_instance.debug("Could not set the cache key to {}", String());
+      if (_context->p_instance.DebugOn()) {
+        _context->p_instance.debug("Could not set the cache key to {}", String());
       }
-      context->state.error.Fail();
+      _context->state.error.Fail();
       return false;
     }
   }
@@ -589,21 +608,31 @@ Cache::URL::_update(cripts::Context *context)
 Parent::URL &
 Parent::URL::_get(cripts::Context *context)
 {
-  if (!context->_cache_url.Initialized()) {
-    Parent::URL     *url = &context->_parent_url;
-    Client::Request &req = Client::Request::_get(context); // Repurpose / create the shared request object
+  _ensure_initialized(&context->_urls.parent);
+  return context->_urls.parent;
+}
 
-    if (TSUrlCreate(req.BufP(), &url->_urlp) == TS_SUCCESS) {
-      TSAssert(context->state.txnp);
-      if (TSHttpTxnParentSelectionUrlGet(context->state.txnp, req.BufP(), url->_urlp) == TS_SUCCESS) {
-        url->_initialize(&context->state, &req);
-      }
-    } else {
-      context->state.error.Fail();
+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) {
+    TSAssert(_context->state.txnp);
+    if (TSHttpTxnParentSelectionUrlGet(_context->state.txnp, req.BufP(), url->_urlp) != TS_SUCCESS) {
+      _context->state.error.Fail();
+      return;
     }
+  } else {
+    _context->state.error.Fail();
+    return;
   }
 
-  return context->_parent_url;
+  // Only if successful above
+  super_type::_initialize();
+  _bufp    = req.BufP();
+  _hdr_loc = req.MLoc();
 }
 
 } // namespace cripts


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds convenience APIs and definitions to Cripts, introducing new top‐level symbols and macros for client/server interactions and URL handling while refactoring numerous classes to support lazy initialization via _ensure_initialized.  
- Incorporates lazy initialization calls in URL, Header, Connections, and related classes  
- Refactors Context to group client, server, and URL components into structured blocks  
- Introduces convenience macros and definitions activated via CRIPT_CONVENIENCE_APIS in the preamble

### Reviewed Changes

Copilot reviewed 11 out of 13 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File                                  | Description                                                     |
| ------------------------------------- | --------------------------------------------------------------- |
| src/cripts/Urls.cc                    | Inserts _ensure_initialized calls in various URL methods        |
| src/cripts/Headers.cc                  | Adds initialization checks before header operations             |
| src/cripts/Context.cc                  | Updates Context to use new structured member blocks              |
| src/cripts/Connections.cc              | Refactors connection methods to use lazy initialization            |
| include/cripts/Urls.hpp                | Refactors URL class initialization and updates const correctness   |
| include/cripts/Preamble.hpp            | Defines convenience macros conditioned on CRIPT_CONVENIENCE_APIS    |
| include/cripts/Headers.hpp             | Inserts _ensure_initialized calls in header accessor methods         |
| include/cripts/Epilogue.hpp            | Adjusts URL update calls with modified-check conditions          |
| include/cripts/Context.hpp             | Refactors Context to use aggregated client/server/url blocks         |
| include/cripts/Connections.hpp         | Updates connection class implementations to use lazy initialization  |
| example/cripts/example2.cc             | Demonstrates usage of the new convenience APIs and macros            |
</details>


<details>
<summary>Files not reviewed (2)</summary>

* **doc/developer-guide/cripts/cripts-convenience.en.rst**: Language not supported
* **doc/developer-guide/cripts/index.en.rst**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**include/cripts/Urls.hpp:578**
* The removal of the const qualifier from the String() method may impact const correctness in parts of the code that expect read-only access. Consider verifying if the method should remain const to prevent unintended modifications.
```
[[nodiscard]] cripts::string String();
```
**include/cripts/Urls.hpp:588**
* The _ensure_initialized function assumes that _context is always set. It would be safer to add an explicit check for a non-null _context before using it to avoid potential null pointer dereferences.
```
if (!ptr->Initialized()) [[unlikely]] { ptr->_initialize(ptr->_context); }
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T19:47:17Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces a suite of convenience APIs and definitions to Cripts by adding top‑level symbols and macros that simplify interacting with URLs, Headers, Connections, and Context objects. The changes add systematic initialization checks (via _ensure_initialized), refactor the internal structures (grouping client, server, and URL blocks into dedicated sub‑structures), and update several method signatures and macros accordingly.

### Reviewed Changes

Copilot reviewed 11 out of 13 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File                                | Description                                                                          |
| ----------------------------------- | ------------------------------------------------------------------------------------ |
| src/cripts/Urls.cc                  | Adds _ensure_initialized calls and adjusts URL initialization and method signatures. |
| src/cripts/Headers.cc               | Inserts _ensure_initialized calls in header operations to guarantee proper setup.      |
| src/cripts/Context.cc               | Refactors header and URL resets to use the new sub‑structure members in Context.        |
| src/cripts/Connections.cc           | Enhances connection methods with initialization checks and refactors const usage.      |
| include/cripts/Urls.hpp             | Introduces _ensure_initialized and new initialization flags for URL objects.           |
| include/cripts/Preamble.hpp         | Adds convenience macros (e.g. client, server, urls) for simplified Cripts usage.         |
| include/cripts/Headers.hpp          | Updates header API functions with initialization checks and minor interface changes.    |
| include/cripts/Epilogue.hpp         | Replaces direct Update() calls with conditional _update() invocations on URLs.          |
| include/cripts/Context.hpp          | Groups client, server, and URL objects into dedicated blocks within Context.           |
| include/cripts/Connections.hpp      | Refactors connection methods and revises SetDscp/SetMark signatures for const usage.   |
| example/cripts/example2.cc          | Provides a usage example demonstrating the newly introduced convenience APIs.          |
</details>


<details>
<summary>Files not reviewed (2)</summary>

* **doc/developer-guide/cripts/cripts-convenience.en.rst**: Language not supported
* **doc/developer-guide/cripts/index.en.rst**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (3)</summary>

**src/cripts/Urls.cc:423**
* Removing the 'const' qualifier from the Url::String() method may break const-correctness. Consider restoring 'const' if the method does not modify object state.
```
Url::String()
```
**include/cripts/Connections.hpp:401**
* Marking SetDscp as const may be misleading since it triggers side effects on connection state; consider removing the const qualifier.
```
void SetDscp(int val) const override
```
**include/cripts/Connections.hpp:407**
* Marking SetMark as const may be misleading since it triggers side effects; consider removing the const qualifier.
```
void SetMark(int val) const override
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T22:55:38Z
user: login: zwoop
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T01:20:58Z
user: login: cmcfarlen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T17:51:49Z
user: login: cmcfarlen
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T18:01:24Z
user: login: cmcfarlen
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T18:41:49Z
user: login: cmcfarlen
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T19:13:26Z
user: login: bryancall
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T20:14:40Z
user: login: bryancall
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-06T02:22:22Z

Review comments:
diff_hunk: @@ -345,18 +345,20 @@ class Header
       _hdr_loc = nullptr;
       _bufp    = nullptr;
     }
-    _state = nullptr;
+    _initialized = false;
   }
 
   [[nodiscard]] TSMBuffer
-  BufP() const
+  BufP()
path: include/cripts/Headers.hpp
user: login: Copilot
body: Changing BufP() from a const method to a non-const one might break const-correctness. Verify that modifying the constness is intended.
```suggestion
  BufP() const
```
created_at: 2025-04-02T22:55:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -345,18 +345,20 @@ class Header
       _hdr_loc = nullptr;
       _bufp    = nullptr;
     }
-    _state = nullptr;
+    _initialized = false;
   }
 
   [[nodiscard]] TSMBuffer
-  BufP() const
+  BufP()
path: include/cripts/Headers.hpp
user: login: zwoop
body: Unfortunately they have to be non-const now, since this could access the uninitialized bufp, so we check for that which may trigger initialization.
created_at: 2025-04-03T01:20:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -414,15 +434,16 @@ namespace Server
     [[nodiscard]] int  FD() const override;
     [[nodiscard]] int  Count() const override;
     static Connection &_get(cripts::Context *context);
+    void               _initialize() override;
 
     void
-    SetDscp(int val) override
+    SetDscp(int val) const override
     {
       TSHttpTxnServerPacketDscpSet(_state->txnp, val);
     }
 
     void
-    SetMark(int val) override
+    SetMark(int val) const override
path: include/cripts/Connections.hpp
user: login: cmcfarlen
body: Should these be const? It's mutating the txn...
created_at: 2025-04-04T17:51:49Z
author_association: CONTRIBUTOR

PR comments:
user: login: zwoop
created_at: 2025-04-04T18:34:33Z
body: [approve ci autest 1]
author_association: CONTRIBUTOR
user: login: cmcfarlen
created_at: 2025-04-08T19:22:29Z
body: Cherry-picked to 10.1.x branch
author_association: CONTRIBUTOR