This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new a3b78a2ddb Cripts: Refactor the cache key / URL APIs (#12377)
a3b78a2ddb is described below

commit a3b78a2ddba574c353015b9a9e70e715ac68ec26
Author: Leif Hedstrom <[email protected]>
AuthorDate: Thu Aug 14 21:56:38 2025 +0000

    Cripts: Refactor the cache key / URL APIs (#12377)
    
    * Cripts: Refactor the cache key / URL APIs
    
    - cleans up the notion around cached URLs and headers, and cache keys.
    - adds APIs to set the lookup status as well
    
    * Address review comments
---
 .../cripts/cripts-convenience.en.rst               |   3 +-
 doc/developer-guide/cripts/cripts-headers.en.rst   |  35 ++++--
 doc/developer-guide/cripts/cripts-misc.en.rst      |  16 +--
 example/cripts/example1.cc                         |   9 +-
 example/cripts/example2.cc                         |   8 +-
 include/cripts/Context.hpp                         |  20 +++-
 include/cripts/Epilogue.hpp                        |  12 +-
 include/cripts/Headers.hpp                         | 123 ++++++++++++---------
 include/cripts/Instance.hpp                        |   2 +-
 include/cripts/Lulu.hpp                            |   7 ++
 include/cripts/Matcher.hpp                         |   4 +-
 include/cripts/Preamble.hpp                        |   6 +-
 include/cripts/Time.hpp                            |  70 +++++++++++-
 include/cripts/Transaction.hpp                     |  12 --
 include/cripts/Urls.hpp                            |   4 +-
 src/cripts/Bundles/LogsMetrics.cc                  |  15 +--
 src/cripts/Context.cc                              |   6 +-
 src/cripts/Headers.cc                              | 110 ++++++++++++++----
 src/cripts/Urls.cc                                 |  16 +--
 19 files changed, 324 insertions(+), 154 deletions(-)

diff --git a/doc/developer-guide/cripts/cripts-convenience.en.rst 
b/doc/developer-guide/cripts/cripts-convenience.en.rst
index 03d3cca0de..7a93931364 100644
--- a/doc/developer-guide/cripts/cripts-convenience.en.rst
+++ b/doc/developer-guide/cripts/cripts-convenience.en.rst
@@ -72,9 +72,10 @@ Object                        Traditional API equivalent
 ``server.request``            ``borrow cripts::Server::Request::Get()``
 ``server.response``           ``borrow cripts::Server::Response::Get()``
 ``server.connection``         ``borrow cripts::Server::Connection::Get()``
+``cached.url``                ``borrow cripts::Cache::URL::Get()``
+``cached.response``           ``borrow cripts::Cache::Response::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()``
diff --git a/doc/developer-guide/cripts/cripts-headers.en.rst 
b/doc/developer-guide/cripts/cripts-headers.en.rst
index 2c5e01881d..66ff13df09 100644
--- a/doc/developer-guide/cripts/cripts-headers.en.rst
+++ b/doc/developer-guide/cripts/cripts-headers.en.rst
@@ -43,6 +43,7 @@ Header Object                   Description
 ``cripts::Client::Response``    The client response headers.
 ``cripts::Server::Request``     The server request headers.
 ``cripts::Server::Response``    The server response headers.
+``cripts::Cache::Response``     The cached response headers (immutable and 
conditional).
 =============================   
===========================================================================
 
 .. note::
@@ -50,6 +51,8 @@ Header Object                   Description
    For all of these headers, except the ``cripts::Client::Request``, the 
headers are not
    available until the respective hook is called. For example, the 
``cripts::Client::Response`` headers
    are not available until the response headers are received from the origin 
server or cache lookup.
+   The ``cripts::Cache::Response`` header is also immutable, and can only be 
used after a successful
+   cache lookup.
 
 Assigning the empty value (``""``) to a header will remove it from the header 
list. For example:
 
@@ -135,16 +138,34 @@ Member                       Description
 ==========================   
======================================================================
 ``status``                   The status code of the response. E.g. ``200``.
 ``reason``                   The reason phrase of the response. E.g. ``OK``.
-``cache``                    The cache status of the response. E.g. ``miss``.
 ==========================   
======================================================================
 
-Of these, the first two are pretty self explanatory, but the ``cache`` status 
is a bit more
-complex. This is a string that represents the cache status of the response. 
The possible values
-are:
+
+Lookup Status
+-------------
+
+The ``Cache::Response`` header also hold the status of the cache lookup, in a 
member named
+``cachelookup``. This is an integer value that represents the status of the 
cache lookup.
+The possible values are:
+
+===============================   
======================================================================
+Value                             Description
+===============================   
======================================================================
+``LookupStatus::NONE``     (-1)   No lookup status available.
+``LookupStatus::MISS``      (0)   The response was not found in the cache.
+``LookupStatus::HIT_STALE`` (1)   The response was found in the cache, but it 
was stale.
+``LookupStatus::HIT_FRESH`` (2)   The response was found in the cache and is 
fresh.
+``LookupStatus::SKIPPED``   (3)   We skipped cache lookup completely
+===============================   
======================================================================
+
+A string representation of the cache lookup status is also available in in the 
``lookupstatus`` member,
+via an implicit conversion to a string
+
 
 ==========================   
======================================================================
 Value                        Description
 ==========================   
======================================================================
+``none``                     No lookup status available.
 ``miss``                     The response was not found in the cache.
 ``hit-stale``                The response was found in the cache, but it was 
stale.
 ``hit-fresh``                The response was found in the cache and is fresh.
@@ -152,14 +173,14 @@ Value                        Description
 ==========================   
======================================================================
 
 This status can be used to determine if the response was found in the cache, 
and if so, if it was
-fresh or stale. Example usage of the cache status:
+fresh or stale. Example usage of the cache lookup status:
 
 .. code-block:: cpp
 
   do_read_response() {
-    borrow resp = cripts::Server::Response::Get();
+    borrow cached = cripts::Cache::Response::Get();
 
-    if (resp.cache == "miss") {
+    if (cached.lookupstatus == cripts::LookupStatus::MISS) {
       // Do something
     }
   }
diff --git a/doc/developer-guide/cripts/cripts-misc.en.rst 
b/doc/developer-guide/cripts/cripts-misc.en.rst
index f7a37d09f1..dd582e6eda 100644
--- a/doc/developer-guide/cripts/cripts-misc.en.rst
+++ b/doc/developer-guide/cripts/cripts-misc.en.rst
@@ -88,7 +88,6 @@ Function                    Description
 =========================   
=======================================================================
 ``DisableCallback()``       Disables a future callback in this Cript, for this 
transaction.
 ``Aborted()``               Has the transaction been aborted.
-``LookupStatus()``          Returns the cache lookup status for the 
transaction.
 =========================   
=======================================================================
 
 When disabling a callback, use the following names:
@@ -182,9 +181,9 @@ Example usage:
 Time
 ====
 
-Cripts has encapsulated some common time-related functions in the core.  At the
-moment only the localtime is available, via the ``cripts::Time::Local`` object 
and its
-``Now()`` method. The ``Now()`` method returns the current time as an object
+Cripts has encapsulated some common time-related functions in the core.  Two 
different time objects
+are available, ``cripts::Time::Local`` and ``cripts::Time::UTC`` objects and 
their respective
+``::Now()`` methods. The ``Now()`` method returns the current time as an object
 with the following functions:
 
 =====================   
===========================================================================
@@ -199,10 +198,11 @@ Function                Description
 ``Second()``            Returns the second (0-59).
 ``WeekDay()``           Returns the day of the week (0-6, Sunday is 0).
 ``YearDay()``           Returns the day of the year (0-365).
+``ToDate()``            Returns a string for the Date in HTTP header format
 =====================   
===========================================================================
 
 The time as returned by ``Now()`` can also be used directly in comparisons 
with previous or future
-times, and can be cast to an integer to get the epoch time.
+times. In addition, the ``Now()`` constructor can take an optional 
``cripts::Time::Point`` argument.
 
 Example usage:
 
@@ -212,12 +212,8 @@ Example usage:
    {
      auto now = cripts::Time::Local::Now();
 
-     CDebug("Current time: year={}, month={}, day={}",
-            now.Year(), now.Month(), now.Day());
+     CDebug("Current time: year={}, month={}, day={}", now.Year(), 
now.Month(), now.Day());
      CDebug("Epoch time: {}", now.Epoch());
-
-     // Can also be used directly as integer
-     integer epoch_time = now;
    }
 
 .. _cripts-misc-plugins:
diff --git a/example/cripts/example1.cc b/example/cripts/example1.cc
index a2361b5d51..fa1bcd8f06 100644
--- a/example/cripts/example1.cc
+++ b/example/cripts/example1.cc
@@ -79,16 +79,17 @@ do_read_response()
 
 do_send_response()
 {
-  borrow resp = cripts::Client::Response::Get();
-  borrow conn = cripts::Client::Connection::Get();
-  string msg  = "Eliminate TSCPP";
+  borrow resp   = cripts::Client::Response::Get();
+  borrow conn   = cripts::Client::Connection::Get();
+  borrow cached = cripts::Cache::Response::Get();
+  string msg    = "Eliminate TSCPP";
 
   resp["Server"]         = "";        // Deletes the Server header
   resp["X-AMC"]          = msg;       // New header
   resp["Cache-Control"]  = "Private"; // Deletes old CC values, and sets a new 
one
   resp["X-UUID"]         = cripts::UUID::Unique::Get();
   resp["X-tcpinfo"]      = conn.tcpinfo.Log();
-  resp["X-Cache-Status"] = resp.cache;
+  resp["X-Cache-Status"] = cached.lookupstatus.GetSV();
   resp["X-Integer"]      = 666;
   resp["X-Data"]         = AsString(txn_data[2]);
 
diff --git a/example/cripts/example2.cc b/example/cripts/example2.cc
index ca6b88b125..c2b8d0185a 100644
--- a/example/cripts/example2.cc
+++ b/example/cripts/example2.cc
@@ -57,8 +57,8 @@ do_txn_close()
 
 do_cache_lookup()
 {
-  CDebug("Cache URL: {}", urls.cache);
-  CDebug("Cache Host: {}", urls.cache.host);
+  CDebug("Cache URL: {}", cached.url);
+  CDebug("Cache Host: {}", cached.url.host);
 }
 
 do_send_request()
@@ -80,7 +80,7 @@ do_send_response()
   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-Cache-Status"] = cached.response.lookupstatus.GetSV();
   client.response["X-Integer"]      = 666;
   client.response["X-Data"]         = AsString(txn_data[2]);
 
@@ -118,7 +118,7 @@ do_send_response()
 do_remap()
 {
   auto ip  = client.connection.IP();
-  auto now = TimeNow();
+  auto now = LocalTimeNow();
 
   if (CRIPT_ALLOW.Match(ip)) {
     CDebug("Client IP allowed: {}", ip.string(24, 64));
diff --git a/include/cripts/Context.hpp b/include/cripts/Context.hpp
index be2af90c39..4e00a26163 100644
--- a/include/cripts/Context.hpp
+++ b/include/cripts/Context.hpp
@@ -44,7 +44,7 @@ public:
 
   // 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), _client(this), _server(this), 
_urls(this, _client.url)
+    : rri(rri_ptr), p_instance(inst), _client(this), _server(this), 
_cache(this), _urls(this, _client.url)
   {
     state.txnp    = txn_ptr;
     state.ssnp    = ssn_ptr;
@@ -76,8 +76,10 @@ public:
   friend class Server::Request;
   friend class Server::Response;
   friend class Server::Connection;
-  friend class Pristine::URL;
+  friend class Server::Response;
+  friend class Cache::Response;
   friend class Cache::URL;
+  friend class Pristine::URL;
   friend class Parent::URL;
   friend class Plugin::Remap;
 
@@ -111,10 +113,21 @@ public:
 
   } _server;
 
+  struct _CacheBlock {
+    cripts::Cache::Response response;
+    cripts::Cache::URL      url;
+
+    _CacheBlock(Context *ctx)
+    {
+      response.set_state(&ctx->state);
+      url.set_context(ctx);
+    }
+
+  } _cache;
+
   struct _UrlBlock {
     cripts::Client::URL  &request;
     cripts::Pristine::URL pristine;
-    cripts::Cache::URL    cache;
     cripts::Parent::URL   parent;
 
     struct {
@@ -126,7 +139,6 @@ public:
     {
       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);
diff --git a/include/cripts/Epilogue.hpp b/include/cripts/Epilogue.hpp
index d0d583032b..c729982898 100644
--- a/include/cripts/Epilogue.hpp
+++ b/include/cripts/Epilogue.hpp
@@ -581,8 +581,8 @@ http_txn_cont(TSCont contp, TSEvent event, void *edata)
     }
 
     if (!context->state.error.Failed()) {
-      if (context->_urls.cache.Modified()) {
-        context->_urls.cache._update(); // Make sure the cache-key gets 
updated, if modified
+      if (context->_cache.url.Modified()) {
+        context->_cache.url._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
@@ -612,8 +612,8 @@ http_txn_cont(TSCont contp, TSEvent event, void *edata)
     }
 
     if (!context->state.error.Failed()) {
-      if (context->_urls.cache.Modified()) {
-        context->_urls.cache._update(); // Make sure the cache-key gets 
updated, if modified
+      if (context->_cache.url.Modified()) {
+        context->_cache.url._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
@@ -899,8 +899,8 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo 
*rri)
 
   // Don't do the callbacks when we are in a failure state.
   if (!context->state.error.Failed()) {
-    if (context->_urls.cache.Modified()) {
-      context->_urls.cache._update(); // Make sure the cache-key gets updated, 
if modified
+    if (context->_cache.url.Modified()) {
+      context->_cache.url._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
diff --git a/include/cripts/Headers.hpp b/include/cripts/Headers.hpp
index f35a743fc8..b1010cf855 100644
--- a/include/cripts/Headers.hpp
+++ b/include/cripts/Headers.hpp
@@ -142,42 +142,6 @@ public:
 
   }; // End class cripts::Header::Method
 
-  class CacheStatus
-  {
-    using self_type = CacheStatus;
-
-  public:
-    CacheStatus() = delete;
-    CacheStatus(Header *owner) : _owner(owner) {}
-
-    cripts::string_view GetSV();
-
-    operator cripts::string_view() { return GetSV(); }
-
-    cripts::string_view::const_pointer
-    Data()
-    {
-      return GetSV().data();
-    }
-
-    cripts::string_view::size_type
-    Size()
-    {
-      return GetSV().size();
-    }
-
-    cripts::string_view::size_type
-    Length()
-    {
-      return GetSV().size();
-    }
-
-  private:
-    Header             *_owner = nullptr;
-    cripts::string_view _cache;
-
-  }; // Class cripts::Header::CacheStatus
-
   class String : public cripts::StringViewMixin<String>
   {
     using super_type = cripts::StringViewMixin<String>;
@@ -198,22 +162,19 @@ public:
     self_type &operator=(integer val);
     self_type &operator+=(const cripts::string_view str);
 
-    // These specialized assignment operators all use the above
-    template <size_t N>
-    self_type &
-    operator=(const char (&str)[N])
-    {
-      return operator=(cripts::string_view(str, str[N - 1] ? N : N - 1));
-    }
+    // ToDo: This was useful at some point, but was breaking other useful 
uses. Leaving
+    // it here for now for maybe future work.
+    // template <size_t N>
+    // self_type &
+    // operator=(const char (&str)[N])
+    //{
+    //  return operator=(cripts::string_view(str, str[N - 1] ? N : N - 1));
+    //}
 
-    self_type &
-    operator=(char *&str)
-    {
-      return operator=(cripts::string_view(str, strlen(str)));
-    }
+    self_type &operator=(std::nullptr_t) = delete;
 
     self_type &
-    operator=(char const *&str)
+    operator=(char const *str)
     {
       return operator=(cripts::string_view(str, strlen(str)));
     }
@@ -331,11 +292,11 @@ public:
     static const Iterator _end;
   }; // Class cripts::Header::iterator
 
-  Header() : status(this), reason(this), body(this), cache(this) {}
+  Header() : status(this), reason(this), body(this) {}
 
   ~Header() { Reset(); }
 
-  // Clear anything "cached" in the Url, this is rather draconian, but it's
+  // Clear anything "cached" in the Header, this is rather draconian, but it's
   // safe...
   void
   Reset()
@@ -363,6 +324,7 @@ public:
   }
 
   String operator[](const cripts::string_view str);
+  time_t AsDate(const cripts::string_view str);
 
   [[nodiscard]] bool
   Initialized() const
@@ -393,10 +355,9 @@ public:
     _state = state;
   }
 
-  Status      status;
-  Reason      reason;
-  Body        body;
-  CacheStatus cache;
+  Status status;
+  Reason reason;
+  Body   body;
 
 protected:
   static void
@@ -513,6 +474,48 @@ namespace Server
 
 } // namespace Server
 
+namespace Cache
+{
+  class Response : public ResponseHeader
+  {
+    using super_type = ResponseHeader;
+    using self_type  = Response;
+
+    class LookupStatus
+    {
+      using self_type = LookupStatus;
+
+    public:
+      LookupStatus() = delete;
+      LookupStatus(Response *owner) : _owner(owner) {}
+
+      cripts::string_view GetSV();
+      self_type          &operator=(int status);
+
+      operator integer();
+
+    private:
+      Response *_owner  = nullptr;
+      int       _lookup = -1; // Note set yet
+
+    }; // Class cripts::Cache::LookupStatus
+
+  public:
+    Response() : ResponseHeader(), lookupstatus(this){};
+
+    Response(const self_type &)       = delete;
+    void operator=(const self_type &) = delete;
+
+    // Implemented later, because needs the context.
+    static self_type &_get(cripts::Context *context);
+    void              _initialize() override;
+
+    LookupStatus lookupstatus;
+
+  }; // End class Cache::Response
+
+} // namespace Cache
+
 // Some static methods for the Method class
 namespace Method
 {
@@ -530,6 +533,16 @@ namespace Method
   extern const cripts::Header::Method PURGE;
 } // namespace Method
 
+// Lookup status constants, these are used in the Cache::Response::lookupstatus
+namespace LookupStatus
+{
+  inline constexpr int NONE      = -1;
+  inline constexpr int MISS      = TS_CACHE_LOOKUP_MISS;
+  inline constexpr int HIT_STALE = TS_CACHE_LOOKUP_HIT_STALE;
+  inline constexpr int HIT_FRESH = TS_CACHE_LOOKUP_HIT_FRESH;
+  inline constexpr int SKIPPED   = TS_CACHE_LOOKUP_SKIPPED;
+} // namespace LookupStatus
+
 class Context;
 } // namespace cripts
 
diff --git a/include/cripts/Instance.hpp b/include/cripts/Instance.hpp
index b5317f9efd..bf0af5f622 100644
--- a/include/cripts/Instance.hpp
+++ b/include/cripts/Instance.hpp
@@ -112,7 +112,7 @@ public:
     }
   }
 
-  std::array<DataType, 32>                       data;
+  std::array<DataType, 16>                       data;
   cripts::string                                 to_url;
   cripts::string                                 from_url;
   cripts::string                                 plugin_debug_tag;
diff --git a/include/cripts/Lulu.hpp b/include/cripts/Lulu.hpp
index f003d7714a..4025b8527b 100644
--- a/include/cripts/Lulu.hpp
+++ b/include/cripts/Lulu.hpp
@@ -24,6 +24,7 @@
 #include <string_view>
 #include <charconv>
 #include <type_traits>
+#include <chrono>
 
 #include <fmt/core.h>
 
@@ -103,6 +104,12 @@ public:
     return bool(*this);
   }
 
+  [[nodiscard]] time_t
+  ToDate() const
+  {
+    return TSMimeParseDate(_value.data(), _value.size());
+  }
+
   std::vector<mixin_type>
   Splitter(mixin_type input, char delim)
   {
diff --git a/include/cripts/Matcher.hpp b/include/cripts/Matcher.hpp
index 30574b67ac..6a749910a3 100644
--- a/include/cripts/Matcher.hpp
+++ b/include/cripts/Matcher.hpp
@@ -20,6 +20,7 @@
 // Setup for PCRE2
 #define PCRE2_CODE_UNIT_WIDTH 8
 #include <pcre2.h>
+#include <algorithm>
 #include <vector>
 #include <tuple>
 
@@ -151,7 +152,8 @@ namespace List
     {
       auto data = method.Data();
 
-      return end() != std::find_if(begin(), end(), [&](const 
cripts::Header::Method &header) { return header.Data() == data; });
+      return end() !=
+             std::ranges::find_if(begin(), end(), [&](const 
cripts::Header::Method &header) { return header.Data() == data; });
     }
 
     [[nodiscard]] bool
diff --git a/include/cripts/Preamble.hpp b/include/cripts/Preamble.hpp
index b484826f18..5a2b4b8e96 100644
--- a/include/cripts/Preamble.hpp
+++ b/include/cripts/Preamble.hpp
@@ -115,12 +115,14 @@ extern cripts::Versions version; // Access to the ATS 
version information
 #define client                      context->_client
 #define server                      context->_server
 #define urls                        context->_urls
+#define cached                      context->_cache
 #define Regex(_name_, ...)          static cripts::Matcher::PCRE 
_name_(__VA_ARGS__);
 #define ACL(_name_, ...)            static cripts::Matcher::Range::IP 
_name_(__VA_ARGS__);
 #define StatusCode(_name_, ...)     cripts::Error::Status::Set(_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();
+#define UniqueUUID()                cripts::UUID::Unique::Get()
+#define LocalTimeNow()              cripts::Time::Local::Now()
+#define UTCTimeNow()                cripts::Time::UTC::Now()
 #endif
diff --git a/include/cripts/Time.hpp b/include/cripts/Time.hpp
index 9afc578cb0..40e3d55e02 100644
--- a/include/cripts/Time.hpp
+++ b/include/cripts/Time.hpp
@@ -28,6 +28,12 @@
 // std::chrono :-/ Todo: Rewrite this with std::chrono when it has things like
 // std::chrono::year_month_day
 
+namespace cripts::Time
+{
+using Clock = std::chrono::system_clock;
+using Point = Clock::time_point;
+} // namespace cripts::Time
+
 namespace detail
 {
 class BaseTime
@@ -95,16 +101,24 @@ public:
     return static_cast<integer>(_result.tm_yday) + 1;
   }
 
+  [[nodiscard]] const cripts::string_view
+  ToDate()
+  {
+    int len = sizeof(_buffer);
+
+    TSMimeFormatDate(_now, _buffer, &len);
+    return {_buffer, len};
+  }
+
 protected:
-  std::time_t _now    = std::time(nullptr);
-  std::tm     _result = {};
+  char        _buffer[64] = {};
+  std::time_t _now        = std::time(nullptr);
+  std::tm     _result     = {};
 };
 } // namespace detail
 
 namespace cripts::Time
 {
-// ToDo: Right now, we only have localtime, but we should support e.g. UTC and
-// other time zone instances.
 class Local : public detail::BaseTime
 {
   using super_type = detail::BaseTime;
@@ -123,8 +137,38 @@ public:
     return {};
   }
 
+  explicit Local(Time::Point tp)
+  {
+    _now = Time::Clock::to_time_t(tp);
+    localtime_r(&_now, static_cast<struct tm *>(&_result));
+  }
+
 }; // End class Time::Local
 
+class UTC : public detail::BaseTime
+{
+  using super_type = detail::BaseTime;
+  using self_type  = UTC;
+
+public:
+  UTC(const self_type &)            = delete;
+  void operator=(const self_type &) = delete;
+
+  UTC() { gmtime_r(&_now, static_cast<struct tm *>(&_result)); }
+
+  explicit UTC(Time::Point tp)
+  {
+    _now = Time::Clock::to_time_t(tp);
+    gmtime_r(&_now, static_cast<struct tm *>(&_result));
+  }
+
+  static UTC
+  Now()
+  {
+    return {};
+  }
+};
+
 } // namespace cripts::Time
 
 // Formatters for {fmt}
@@ -139,9 +183,25 @@ template <> struct formatter<cripts::Time::Local> {
 
   template <typename FormatContext>
   auto
-  format(cripts::Time::Local &time, FormatContext &ctx) const -> 
decltype(ctx.out())
+  format(const cripts::Time::Local &time, FormatContext &ctx) const -> 
decltype(ctx.out())
+  {
+    return fmt::format_to(ctx.out(), "{}", time.Epoch());
+  }
+};
+
+template <> struct formatter<cripts::Time::UTC> {
+  constexpr auto
+  parse(const format_parse_context &ctx) -> decltype(ctx.begin())
+  {
+    return ctx.begin();
+  }
+
+  template <typename FormatContext>
+  auto
+  format(const cripts::Time::UTC &time, FormatContext &ctx) const -> 
decltype(ctx.out())
   {
     return fmt::format_to(ctx.out(), "{}", time.Epoch());
   }
 };
+
 } // namespace fmt
diff --git a/include/cripts/Transaction.hpp b/include/cripts/Transaction.hpp
index 9bcdb83d33..29f9b5cc36 100644
--- a/include/cripts/Transaction.hpp
+++ b/include/cripts/Transaction.hpp
@@ -81,18 +81,6 @@ public:
     return false;
   }
 
-  [[nodiscard]] int
-  LookupStatus() const
-  {
-    int status = 0;
-
-    if (TSHttpTxnCacheLookupStatusGet(txnp, &status) != TS_SUCCESS) {
-      return -1;
-    }
-
-    return status;
-  }
-
 }; // class Transaction
 
 } // namespace cripts
diff --git a/include/cripts/Urls.hpp b/include/cripts/Urls.hpp
index 47c8c5069e..1ebf13f7d1 100644
--- a/include/cripts/Urls.hpp
+++ b/include/cripts/Urls.hpp
@@ -17,10 +17,12 @@
 */
 #pragma once
 
+#include <algorithm>
 #include <string>
 #include <string_view>
 #include <unordered_map>
 #include <vector>
+#include <concepts>
 
 #include "ts/ts.h"
 #include "ts/remap.h"
@@ -500,7 +502,7 @@ public:
       // Make sure the hash and vector are populated
       _parser();
 
-      std::sort(_ordered.begin(), _ordered.end());
+      std::ranges::sort(_ordered);
       _modified = true;
     }
 
diff --git a/src/cripts/Bundles/LogsMetrics.cc 
b/src/cripts/Bundles/LogsMetrics.cc
index cc1b34193b..85e9c90aea 100644
--- a/src/cripts/Bundles/LogsMetrics.cc
+++ b/src/cripts/Bundles/LogsMetrics.cc
@@ -113,7 +113,7 @@ LogsMetrics::doTxnClose(cripts::Context *context)
     resp["@TCPInfo"] += fmt::format(",TC; {}", conn.tcpinfo.Log());
   }
 
-  // .label(str)
+  // .propstats(str)
   if (_label.length() > 0) {
     
instance.metrics[Bundle::PROPSTAT_CLIENT_BYTES_IN]->Increment(TSHttpTxnClientReqHdrBytesGet(transaction.txnp)
 +
                                                                   
TSHttpTxnClientReqBodyBytesGet(transaction.txnp));
@@ -170,12 +170,12 @@ LogsMetrics::doSendResponse(cripts::Context *context)
 void
 LogsMetrics::doCacheLookup(cripts::Context *context)
 {
-  auto status = transaction.LookupStatus();
+  borrow cached = cripts::Cache::Response::Get();
 
-  // .label(str)
+  // .propstats(str)
   if (_label.length() > 0) {
-    if (status >= 0 && status <= 3) {
-      instance.metrics[status]->Increment(); // This assumes the 4 cache stats 
are first
+    if (cached.lookupstatus >= LookupStatus::MISS && cached.lookupstatus <= 
LookupStatus::SKIPPED) {
+      instance.metrics[cached.lookupstatus]->Increment(); // This assumes the 
4 cache stats are first
     }
   }
 }
@@ -196,8 +196,9 @@ LogsMetrics::doRemap(cripts::Context *context)
 
   // .tcpinfo(bool)
   if (_tcpinfo && sampled) {
-    borrow req      = cripts::Client::Request::Get();
-    borrow conn     = cripts::Client::Connection::Get();
+    borrow req  = cripts::Client::Request::Get();
+    borrow conn = cripts::Client::Connection::Get();
+
     req["@TCPInfo"] = fmt::format("TS; {}", conn.tcpinfo.Log());
   }
 }
diff --git a/src/cripts/Context.cc b/src/cripts/Context.cc
index 536f8bfde2..d71473e3d2 100644
--- a/src/cripts/Context.cc
+++ b/src/cripts/Context.cc
@@ -52,12 +52,12 @@ Context::reset()
   if (_urls.pristine.Initialized()) {
     _urls.pristine.Reset();
   }
-  if (_urls.cache.Initialized()) {
-    _urls.cache.Reset();
-  }
   if (_urls.parent.Initialized()) {
     _urls.parent.Reset();
   }
+  if (_cache.url.Initialized()) {
+    _cache.url.Reset();
+  }
 }
 
 Context *
diff --git a/src/cripts/Headers.cc b/src/cripts/Headers.cc
index 5b199cfe0a..1a648391f1 100644
--- a/src/cripts/Headers.cc
+++ b/src/cripts/Headers.cc
@@ -110,30 +110,6 @@ Header::Method::GetSV()
   return _method;
 }
 
-cripts::string_view
-Header::CacheStatus::GetSV()
-{
-  static std::array<cripts::string_view, 4> names{
-    "miss",      // TS_CACHE_LOOKUP_MISS,
-    "hit-stale", // TS_CACHE_LOOKUP_HIT_STALE,
-    "hit-fresh", // TS_CACHE_LOOKUP_HIT_FRESH,
-    "skipped"    // TS_CACHE_LOOKUP_SKIPPED
-  };
-  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) {
-      _cache = "none";
-    } else {
-      _cache = names[status];
-    }
-  }
-
-  return _cache;
-}
-
 Header::String &
 Header::String::operator=(const cripts::string_view str)
 {
@@ -269,6 +245,24 @@ Header::operator[](const cripts::string_view str)
   return ret;
 }
 
+time_t
+Header::AsDate(const cripts::string_view str)
+{
+  _ensure_initialized(this);
+  TSAssert(_bufp && _hdr_loc);
+
+  time_t ret       = 0;
+  TSMLoc field_loc = TSMimeHdrFieldFind(_bufp, _hdr_loc, str.data(), 
str.size());
+
+  if (field_loc) {
+    ret = TSMimeHdrFieldValueDateGet(_bufp, _hdr_loc, field_loc);
+    // Since this is not owned by a Header::String, we have to release it now
+    TSHandleMLocRelease(_bufp, _hdr_loc, field_loc);
+  }
+
+  return ret;
+}
+
 Client::Request &
 Client::Request::_get(cripts::Context *context)
 {
@@ -405,4 +399,72 @@ Server::Response::_initialize()
   }
 }
 
+Cache::Response &
+Cache::Response::_get(cripts::Context *context)
+{
+  _ensure_initialized(&context->_cache.response);
+  return context->_cache.response;
+}
+
+void
+Cache::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);
+
+  TSAssert(_state->txnp);
+
+  if (TSHttpTxnCachedRespGet(_state->txnp, &_bufp, &_hdr_loc) != TS_SUCCESS) {
+    _state->error.Fail();
+  } else {
+    super_type::_initialize(); // Don't initialize unless properly setup
+  }
+}
+
+Cache::Response::LookupStatus::operator integer()
+{
+  if (_lookup == -1) {
+    TSAssert(_owner->_state->txnp);
+
+    if (TSHttpTxnCacheLookupStatusGet(_owner->_state->txnp, &_lookup) == 
TS_ERROR || _lookup < 0 || _lookup >= 4) {
+      _lookup = -1;
+    }
+  }
+
+  return _lookup;
+}
+
+cripts::string_view
+Cache::Response::LookupStatus::GetSV()
+{
+  static std::array<cripts::string_view, 4> names{
+    "miss",      // TS_CACHE_LOOKUP_MISS,
+    "hit-stale", // TS_CACHE_LOOKUP_HIT_STALE,
+    "hit-fresh", // TS_CACHE_LOOKUP_HIT_FRESH,
+    "skipped"    // TS_CACHE_LOOKUP_SKIPPED
+  };
+
+  int status = operator integer();
+
+  if (status == -1) {
+    return "none";
+  } else {
+    return names[status];
+  }
+}
+
+Cache::Response::LookupStatus &
+Cache::Response::LookupStatus::operator=(int lookup)
+{
+  if (TSHttpTxnCacheLookupStatusSet(_owner->_state->txnp, lookup) == 
TS_SUCCESS) {
+    _owner->_state->context->p_instance.debug("Setting LookupStatus = {}", 
lookup);
+  } else {
+    // ToDo: Should we fail here?
+    // _owner->_state->error.Fail();
+  }
+
+  return *this;
+}
+
 } // namespace cripts
diff --git a/src/cripts/Urls.cc b/src/cripts/Urls.cc
index 749b802602..5052365d06 100644
--- a/src/cripts/Urls.cc
+++ b/src/cripts/Urls.cc
@@ -15,6 +15,7 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 */
+#include <algorithm>
 #include <sstream>
 
 #include "cripts/Lulu.hpp"
@@ -114,7 +115,7 @@ Url::Path::GetSV()
   if (_segments.size() > 0) {
     std::ostringstream path;
 
-    std::copy(_segments.begin(), _segments.end(), 
std::ostream_iterator<cripts::string_view>(path, "/"));
+    std::ranges::copy(_segments, 
std::ostream_iterator<cripts::string_view>(path, "/"));
     _storage.reserve(_size);
     _storage = std::string_view(path.str());
     if (_storage.size() > 0) {
@@ -340,7 +341,7 @@ Url::Query::Erase(cripts::string_view param)
   _parser();
 
   auto iter  = _hashed.find(param);
-  auto viter = std::find(_ordered.begin(), _ordered.end(), param);
+  auto viter = std::ranges::find(_ordered, param);
 
   if (iter != _hashed.end()) {
     _size -= iter->second.size(); // Size of the erased value
@@ -365,7 +366,7 @@ 
Url::Query::Erase(std::initializer_list<cripts::string_view> list, bool keep)
     _parser();
 
     for (auto viter = _ordered.begin(); viter != _ordered.end();) {
-      if (list.end() == std::find(list.begin(), list.end(), *viter)) {
+      if (list.end() == std::ranges::find(list, *viter)) {
         auto iter = _hashed.find(*viter);
 
         CAssert(iter != _hashed.end());
@@ -532,14 +533,14 @@ Remap::To::URL::_get(cripts::Context *context)
 Cache::URL &
 Cache::URL::_get(cripts::Context *context)
 {
-  _ensure_initialized(&context->_urls.cache);
-  return context->_urls.cache;
+  _ensure_initialized(&context->_cache.url);
+  return context->_cache.url;
 }
 
 void
 Cache::URL::_initialize()
 {
-  Cache::URL      *url = &_context->_urls.cache;
+  Cache::URL      *url = &_context->_cache.url;
   Client::Request &req = Client::Request::_get(_context); // Repurpose / 
create the shared request object
 
   switch (_context->state.hook) {
@@ -547,6 +548,7 @@ Cache::URL::_initialize()
   case TS_HTTP_SEND_RESPONSE_HDR_HOOK:
   case TS_HTTP_READ_RESPONSE_HDR_HOOK:
   case TS_HTTP_SEND_REQUEST_HDR_HOOK:
+  case TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK:
   case TS_HTTP_TXN_CLOSE_HOOK:
     if (TSUrlCreate(req.BufP(), &url->_urlp) == TS_SUCCESS) {
       TSAssert(_context->state.txnp);
@@ -584,7 +586,7 @@ Cache::URL::_update()
   query.Flush();
 
   if (_modified) {
-    _ensure_initialized(&_context->_urls.cache);
+    _ensure_initialized(&_context->_cache.url);
     TSAssert(_context->state.txnp);
     _modified = false;
     if (TS_SUCCESS == TSHttpTxnCacheLookupUrlSet(_context->state.txnp, _bufp, 
_urlp)) {


Reply via email to