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

bnolsen 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 421bfb6eb Add support for CMCD-Request header nor field to prefetch 
plugin (#9232)
421bfb6eb is described below

commit 421bfb6ebed04bbab2e7ef4f139016d2413a9177
Author: Brian Olsen <[email protected]>
AuthorDate: Fri Mar 10 16:04:58 2023 -0700

    Add support for CMCD-Request header nor field to prefetch plugin (#9232)
    
    * prefetch: Add support for cmcd-request nor prefetch handling
    
    * prefetch: restore failing behavior on missing cachekey
    
    * prefetch: make cmcd autest more robust
    
    * prefetch: cmcd percent decode and scrub query params
    
    * revert debug statement change
    
    ---------
    
    Co-authored-by: Brian Olsen <[email protected]>
---
 doc/admin-guide/plugins/prefetch.en.rst            |  24 ++
 plugins/prefetch/common.h                          |   1 +
 plugins/prefetch/configs.cc                        |  12 +-
 plugins/prefetch/configs.h                         |  11 +-
 plugins/prefetch/fetch.cc                          |  16 +-
 plugins/prefetch/fetch.h                           |   5 +-
 plugins/prefetch/plugin.cc                         | 227 ++++++++++----
 .../pluginTest/prefetch/header_rewrite.conf        |  19 ++
 .../pluginTest/prefetch/prefetch_cmcd.test.py      | 337 +++++++++++++++++++++
 .../pluginTest/prefetch/prefetch_cmcd0.gold        |  13 +
 .../pluginTest/prefetch/prefetch_cmcd1.gold        |  12 +
 .../prefetch_simple.gold                           |   0
 .../prefetch_simple.test.py                        |   0
 13 files changed, 608 insertions(+), 69 deletions(-)

diff --git a/doc/admin-guide/plugins/prefetch.en.rst 
b/doc/admin-guide/plugins/prefetch.en.rst
index 74312d934..3a23d8dc3 100644
--- a/doc/admin-guide/plugins/prefetch.en.rst
+++ b/doc/admin-guide/plugins/prefetch.en.rst
@@ -170,6 +170,29 @@ specified with an integer followed by a colon, e.g. 
``{8:$2+2}``,
 causing the resulting number to be padded with leading zeroes if it
 has fewer digits than the width.
 
+CMCD (Common Media Client Data) CMCD-Request header with nor field
+------------------------------------------------------------------
+
+If the ``--cmcd-nor`` option is specified the Cmcd-Request header with nor 
field is handled.
+
+With setup ::
+
+  map http://example.com http://origin.com \
+      @plugin=cachekey.so @pparam=--remove-all-params=true \
+      @plugin=prefetch.so \
+          @pparam=--cmcd-nor=true
+
+If the incoming request is ::
+
+  http://example-seed.com/path/someitem.m4a
+
+with header ::
+
+  Cmcd-Request: nor="otheritem.m4a"
+
+The following URL will be requested to be prefetched ::
+
+  http://example-seed.com/path/otheritem.m4a
 
 Overhead from **next object** prefetch
 --------------------------------------
@@ -238,6 +261,7 @@ Plugin parameters
   - ``true`` - configures the plugin run on the **front-tier**,
   - ``false`` - to be run on the **back-tier**.
 * ``--api-header`` - the header used by the plugin internally, also used to 
mark a prefetch request to the next tier in dual-tier usage.
+* ``--cmcd-nor`` - prefetch for a Cmcd-Request header with nor field.
 * ``--fetch-policy`` - fetch policy
   - ``simple`` - this policy just makes sure there are no same concurrent 
prefetches triggered (default and always used in combination with any other 
policy)
   - ``lru:n`` - this policy uses LRU to identify "hot" objects and triggers 
prefetch if the object is not found. `n` is the size of the LRU
diff --git a/plugins/prefetch/common.h b/plugins/prefetch/common.h
index 35bbab0d9..8c0ae36ae 100644
--- a/plugins/prefetch/common.h
+++ b/plugins/prefetch/common.h
@@ -31,6 +31,7 @@
 #include <vector>
 
 typedef std::string String;
+typedef std::string_view StringView;
 typedef std::set<std::string> StringSet;
 typedef std::list<std::string> StringList;
 typedef std::vector<std::string> StringVector;
diff --git a/plugins/prefetch/configs.cc b/plugins/prefetch/configs.cc
index 21c2ffa81..aa869b563 100644
--- a/plugins/prefetch/configs.cc
+++ b/plugins/prefetch/configs.cc
@@ -57,6 +57,7 @@ PrefetchConfig::init(int argc, char *argv[])
   static const struct option longopt[] = {
     {const_cast<char *>("front"),              optional_argument, nullptr, 
'f'},
     {const_cast<char *>("api-header"),         optional_argument, nullptr, 
'h'},
+    {const_cast<char *>("cmcd-nor"),           optional_argument, nullptr, 
'd'},
     {const_cast<char *>("next-header"),        optional_argument, nullptr, 
'n'},
     {const_cast<char *>("fetch-policy"),       optional_argument, nullptr, 
'p'},
     {const_cast<char *>("fetch-count"),        optional_argument, nullptr, 
'c'},
@@ -68,15 +69,15 @@ PrefetchConfig::init(int argc, char *argv[])
     {const_cast<char *>("metrics-prefix"),     optional_argument, nullptr, 
'm'},
     {const_cast<char *>("exact-match"),        optional_argument, nullptr, 
'y'},
     {const_cast<char *>("log-name"),           optional_argument, nullptr, 
'l'},
-    {nullptr,                                  0,                 nullptr, 0  }
+    {nullptr,                                  0,                 nullptr, 0  
},
   };
 
   bool status = true;
   optind      = 0;
 
   /* argv contains the "to" and "from" URLs. Skip the first so that the second 
one poses as the program name. */
-  argc--;
-  argv++;
+  --argc;
+  ++argv;
 
   for (;;) {
     int opt;
@@ -97,6 +98,10 @@ PrefetchConfig::init(int argc, char *argv[])
       setApiHeader(optarg);
       break;
 
+    case 'd': /* --cmcd-nor */
+      _cmcd_nor = ::isTrue(optarg);
+      break;
+
     case 'n': /* --next-header */
       setNextHeader(optarg);
       break;
@@ -167,6 +172,7 @@ PrefetchConfig::finalize()
   PrefetchDebug("front-end: %s", (_front ? "true" : "false"));
   PrefetchDebug("exact match: %s", (_exactMatch ? "true" : "false"));
   PrefetchDebug("query key: %s", _queryKey.c_str());
+  PrefetchDebug("cncd-nor: %s", (_front ? "true" : "false"));
   PrefetchDebug("API header name: %s", _apiHeader.c_str());
   PrefetchDebug("next object header name: %s", _nextHeader.c_str());
   PrefetchDebug("fetch policy parameters: %s", _fetchPolicy.c_str());
diff --git a/plugins/prefetch/configs.h b/plugins/prefetch/configs.h
index 1996f4fce..c1058c5e9 100644
--- a/plugins/prefetch/configs.h
+++ b/plugins/prefetch/configs.h
@@ -35,8 +35,8 @@ class PrefetchConfig
 {
 public:
   PrefetchConfig()
-    : _apiHeader("X-AppleCDN-Prefetch"),
-      _nextHeader("X-AppleCDN-Prefetch-Next"),
+    : _apiHeader("X-CDN-Prefetch"),
+      _nextHeader("X-CDN-Prefetch-Next"),
       _replaceHost(),
       _namespace("default"),
       _metricsPrefix("prefetch.stats")
@@ -111,6 +111,12 @@ public:
     return _exactMatch;
   }
 
+  bool
+  isCmcdNor() const
+  {
+    return _cmcd_nor;
+  }
+
   void
   setFetchCount(const char *optarg)
   {
@@ -208,5 +214,6 @@ private:
   unsigned _fetchMax   = 0;
   bool _front          = false;
   bool _exactMatch     = false;
+  bool _cmcd_nor       = false;
   MultiPattern _nextPaths;
 };
diff --git a/plugins/prefetch/fetch.cc b/plugins/prefetch/fetch.cc
index a114be055..d1a9b3e98 100644
--- a/plugins/prefetch/fetch.cc
+++ b/plugins/prefetch/fetch.cc
@@ -409,11 +409,12 @@ BgFetch::~BgFetch()
 
 bool
 BgFetch::schedule(BgFetchState *state, const PrefetchConfig &config, bool 
askPermission, TSMBuffer requestBuffer,
-                  TSMLoc requestHeaderLoc, TSHttpTxn txnp, const char *path, 
size_t pathLen, const String &cachekey)
+                  TSMLoc requestHeaderLoc, TSHttpTxn txnp, const char *path, 
size_t pathLen, const String &cachekey,
+                  bool removeQuery)
 {
   bool ret       = false;
   BgFetch *fetch = new BgFetch(state, config, askPermission);
-  if (fetch->init(requestBuffer, requestHeaderLoc, txnp, path, pathLen, 
cachekey)) {
+  if (fetch->init(requestBuffer, requestHeaderLoc, txnp, path, pathLen, 
cachekey, removeQuery)) {
     fetch->schedule();
     ret = true;
   } else {
@@ -451,7 +452,7 @@ BgFetch::addBytes(int64_t b)
  */
 bool
 BgFetch::init(TSMBuffer reqBuffer, TSMLoc reqHdrLoc, TSHttpTxn txnp, const 
char *fetchPath, size_t fetchPathLen,
-              const String &cachekey)
+              const String &cachekey, bool removeQuery)
 {
   TSAssert(TS_NULL_MLOC == _headerLoc);
   TSAssert(TS_NULL_MLOC == _urlLoc);
@@ -506,6 +507,15 @@ BgFetch::init(TSMBuffer reqBuffer, TSMLoc reqHdrLoc, 
TSHttpTxn txnp, const char
     return false;
   }
 
+  /* Remove the query string */
+  if (removeQuery) {
+    if (TS_SUCCESS == TSUrlHttpQuerySet(_mbuf, _urlLoc, "", 0)) {
+      PrefetchDebug("original query string removed");
+    } else {
+      PrefetchError("failed to remove original query string");
+    }
+  }
+
   /* Now set or remove the prefetch API header */
   const String &header = _config.getApiHeader();
   if (_config.isFront()) {
diff --git a/plugins/prefetch/fetch.h b/plugins/prefetch/fetch.h
index 0471e1e71..3f72e4ea8 100644
--- a/plugins/prefetch/fetch.h
+++ b/plugins/prefetch/fetch.h
@@ -169,13 +169,14 @@ class BgFetch
 {
 public:
   static bool schedule(BgFetchState *state, const PrefetchConfig &config, bool 
askPermission, TSMBuffer requestBuffer,
-                       TSMLoc requestHeaderLoc, TSHttpTxn txnp, const char 
*path, size_t pathLen, const String &cachekey);
+                       TSMLoc requestHeaderLoc, TSHttpTxn txnp, const char 
*path, size_t pathLen, const String &cachekey,
+                       bool removeQuery = false);
 
 private:
   BgFetch(BgFetchState *state, const PrefetchConfig &config, bool lock);
   ~BgFetch();
   bool init(TSMBuffer requestBuffer, TSMLoc requestHeaderLoc, TSHttpTxn txnp, 
const char *fetchPath, size_t fetchPathLen,
-            const String &cacheKey);
+            const String &cacheKey, bool removeQuery = false);
   void schedule();
   static int handler(TSCont contp, TSEvent event, void * /* edata ATS_UNUSED 
*/);
   bool saveIp(TSHttpTxn txnp);
diff --git a/plugins/prefetch/plugin.cc b/plugins/prefetch/plugin.cc
index ef1d2991d..7306cfbb7 100644
--- a/plugins/prefetch/plugin.cc
+++ b/plugins/prefetch/plugin.cc
@@ -20,9 +20,7 @@
  * @file plugin.cc
  * @brief traffic server plugin entry points.
  */
-
 #include <sstream>
-#include <iomanip>
 
 #include "ts/ts.h" /* ATS API */
 
@@ -227,8 +225,12 @@ appendCacheKey(const TSHttpTxn txnp, const TSMBuffer 
reqBuffer, String &key)
         TSfree(static_cast<void *>(url));
         ret = true;
       }
+    } else {
+      PrefetchDebug("Failure lookup up cache url");
     }
     TSHandleMLocRelease(reqBuffer, TS_NULL_MLOC, keyLoc);
+  } else {
+    PrefetchDebug("Failure creating url");
   }
 
   if (!ret) {
@@ -348,6 +350,79 @@ getPristineUrlQuery(TSHttpTxn txnp)
   return pristineQuery;
 }
 
+static constexpr StringView CmcdHeader{"Cmcd-Request"};
+static constexpr StringView CmcdNorFieldPrefix{"nor="};
+static constexpr StringView CmcdNrrFieldPrefix{"nrr="};
+
+/**
+ * @brief Look for and return the nor field of any Cmcd-Request header
+ *
+ * @param txnp HTTP transaction structure
+ * @param buffer request TSMBuffer
+ * @param hdrloc request TSMLoc
+ * @return unquoted relative cmcd path from the nor field
+ *
+ * If the 'nrr' field is encountered the 'nor' field will be ignored.
+ *
+ * sample header:
+ * Cmcd-Request: mtp=103600,bl=153500,nor="14_176.mp4a"
+ */
+static String
+getCmcdNor(const TSMBuffer buffer, const TSMLoc hdrloc)
+{
+  String relpath;
+  bool hasnrr = false; // don't prefetch if range request
+
+  const TSMLoc cmcdloc = TSMimeHdrFieldFind(buffer, hdrloc, CmcdHeader.data(), 
CmcdHeader.length());
+  if (TS_NULL_MLOC != cmcdloc) {
+    // iterate through the fields
+    const int cnt = TSMimeHdrFieldValuesCount(buffer, hdrloc, cmcdloc);
+    for (int ind = 0; ind < cnt; ++ind) {
+      int flen               = 0;
+      const char *const fval = TSMimeHdrFieldValueStringGet(buffer, hdrloc, 
cmcdloc, ind, &flen);
+
+      StringView fv(fval, flen);
+      PrefetchDebug("cmcd-request field: '%.*s'", (int)fv.length(), fv.data());
+      if (0 == fv.compare(0, CmcdNrrFieldPrefix.length(), CmcdNrrFieldPrefix)) 
{
+        PrefetchDebug("cmcd-request nrr field encountered, skipping 
prefetch!");
+        hasnrr = true;
+        break;
+      }
+
+      if (0 == fv.compare(0, CmcdNorFieldPrefix.length(), CmcdNorFieldPrefix)) 
{
+        fv.remove_prefix(CmcdNorFieldPrefix.length());
+        if (fv.front() == '"') {
+          fv.remove_prefix(1);
+        }
+        if (fv.back() == '"') {
+          fv.remove_suffix(1);
+        }
+
+        PrefetchDebug("Extracted nor field: '%.*s'", (int)fv.length(), 
fv.data());
+
+        // Undo any percent encoding
+        char buf[8192];
+        size_t blen = sizeof(buf);
+        if (TS_SUCCESS == TSStringPercentDecode(fv.data(), fv.length(), buf, 
blen, &blen)) {
+          relpath.assign(buf, blen);
+        } else {
+          PrefetchDebug("Error percent decoding nor field: '%.*s'", 
(int)fv.length(), fv.data());
+        }
+      }
+    }
+    TSHandleMLocRelease(buffer, hdrloc, cmcdloc);
+  } else {
+    PrefetchDebug("No Cmcd-Request header found");
+  }
+
+  // don't prefetch if range request
+  if (hasnrr) {
+    relpath.clear();
+  }
+
+  return relpath;
+}
+
 /**
  * @brief short-cut to set the response .
  */
@@ -425,19 +500,26 @@ contHandleFetch(const TSCont contp, TSEvent event, void 
*edata)
   TSHttpTxn txnp         = static_cast<TSHttpTxn>(edata);
   PrefetchConfig &config = data->_inst->_config;
   BgFetchState *state    = data->_inst->_state;
-  TSMBuffer reqBuffer;
-  TSMLoc reqHdrLoc;
+  TSMBuffer reqBuffer    = nullptr;
+  TSMLoc reqHdrLoc       = TS_NULL_MLOC;
 
   PrefetchDebug("event: %s (%d)", getEventName(event), event);
 
   TSEvent retEvent = TS_EVENT_HTTP_CONTINUE;
 
-  if ((event == TS_EVENT_HTTP_POST_REMAP || event == 
TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE ||
-       event == TS_EVENT_HTTP_SEND_RESPONSE_HDR) &&
-      TS_SUCCESS != TSHttpTxnClientReqGet(txnp, &reqBuffer, &reqHdrLoc)) {
-    PrefetchError("failed to get client request");
-    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR);
-    return 0;
+  // For these cases we need to access the client request
+  switch (event) {
+  case TS_EVENT_HTTP_POST_REMAP:
+  case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE:
+  case TS_EVENT_HTTP_SEND_RESPONSE_HDR:
+    if (TS_SUCCESS != TSHttpTxnClientReqGet(txnp, &reqBuffer, &reqHdrLoc)) {
+      PrefetchError("failed to get client request");
+      TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR);
+      return 0;
+    }
+    break;
+  default:
+    break;
   }
 
   switch (event) {
@@ -506,69 +588,96 @@ contHandleFetch(const TSCont contp, TSEvent event, void 
*edata)
     if (data->frontend()) {
       /* front-end instance */
 
-      String currentPath  = getPristineUrlPath(txnp);
-      String currentQuery = getPristineUrlQuery(txnp);
-      bool hasValidQuery  = false;
+      const String currentPath  = getPristineUrlPath(txnp);
+      const String currentQuery = getPristineUrlQuery(txnp);
+      bool hasValidQuery        = false;
 
       // If there is a --fetch-query defined in the config, and that string is 
found in the querystring, assume it is
       // valid, and prefer the --fetch-query over the --fetch-path-pattern(s).
-      if (!config.getQueryKeyName().empty() && 
currentQuery.find(config.getQueryKeyName()) != std::string::npos) {
+      if (!config.getQueryKeyName().empty() && 
currentQuery.find(config.getQueryKeyName()) != String::npos) {
         PrefetchDebug("Setting hasValidQuery to true");
         hasValidQuery = true;
       }
 
-      if (!hasValidQuery && data->firstPass() && data->_fetchable && 
!config.getNextPath().empty() && respToTriggerPrefetch(txnp)) {
-        /* Trigger all necessary background fetches based on the next path 
pattern */
+      if (data->firstPass() && data->_fetchable && 
respToTriggerPrefetch(txnp)) {
+        // If configured to handle Cmcd-Request nor field.
+        // This still allows other prefetch configurations to work.
+        if (config.isCmcdNor()) {
+          PrefetchDebug("Considering cmcd nor request");
 
-        if (!currentPath.empty()) {
-          unsigned total = config.getFetchCount();
-          for (unsigned i = 0; i < total; ++i) {
-            PrefetchDebug("generating prefetch request %d/%d", i + 1, total);
-            String expandedPath;
+          TSAssert(nullptr != reqBuffer);
+          TSAssert(TS_NULL_MLOC != reqHdrLoc);
 
-            if (config.getNextPath().replace(currentPath, expandedPath)) {
-              PrefetchDebug("replaced: %s", expandedPath.c_str());
-              expand(expandedPath);
-              PrefetchDebug("expanded: %s cachekey: %s", expandedPath.c_str(), 
data->_cachekey.c_str());
+          const String relpath = getCmcdNor(reqBuffer, reqHdrLoc);
+          if (!relpath.empty()) {
+            PrefetchDebug("Current path: '%s'", currentPath.c_str());
+            PrefetchDebug("Parsed cmcd nor relpath: '%s'", relpath.c_str());
 
-              BgFetch::schedule(state, config, /* askPermission */ false, 
reqBuffer, reqHdrLoc, txnp, expandedPath.c_str(),
-                                expandedPath.length(), data->_cachekey);
-            } else {
-              /* We should be here only if the pattern replacement fails 
(match already checked) */
-              PrefetchError("failed to process the pattern");
+            const String::size_type lsi = currentPath.find_last_of("/");
+            const String nextPath       = currentPath.substr(0, lsi + 1) + 
relpath;
 
-              /* If the first or any matches fails there must be something 
wrong so don't continue */
-              break;
-            }
-            currentPath.assign(expandedPath);
+            PrefetchDebug("Next cmcd nor path: '%s'", nextPath.c_str());
+
+            constexpr bool askPermission = false;
+            constexpr bool removeQuery   = true;
+            BgFetch::schedule(state, config, askPermission, reqBuffer, 
reqHdrLoc, txnp, nextPath.c_str(), nextPath.length(),
+                              data->_cachekey, removeQuery);
           }
-        } else {
-          PrefetchDebug("failed to get current path");
         }
-      }
-      if (hasValidQuery && data->firstPass() && data->_fetchable && 
respToTriggerPrefetch(txnp)) {
-        /* Trigger all necessary background fetches based on the query 
string(s) */
-
-        PrefetchDebug("currentQuery: %s", currentQuery.c_str());
-        size_t lastSlashIndex = currentPath.find_last_of("/");
-        size_t keyLen         = config.getQueryKeyName().size();
-        unsigned done         = 1;
-        std::istringstream cStringStream(currentQuery);
-        std::string param;
-
-        while (getline(cStringStream, param, '&')) {
-          if (param.find(config.getQueryKeyName()) != 0) {
-            continue;
-          }
-          if (config.getFetchCount() < done++) {
-            break;
+
+        if (!hasValidQuery && !config.getNextPath().empty()) {
+          /* Trigger all necessary background fetches based on the next path 
pattern */
+
+          if (!currentPath.empty()) {
+            const unsigned total = config.getFetchCount();
+            String workingPath   = currentPath;
+            for (unsigned i = 0; i < total; ++i) {
+              PrefetchDebug("generating prefetch request %d/%d", i + 1, total);
+              String expandedPath;
+
+              if (config.getNextPath().replace(workingPath, expandedPath)) {
+                PrefetchDebug("replaced: %s", expandedPath.c_str());
+                expand(expandedPath);
+                PrefetchDebug("expanded: %s cachekey: %s", 
expandedPath.c_str(), data->_cachekey.c_str());
+
+                BgFetch::schedule(state, config, /* askPermission */ false, 
reqBuffer, reqHdrLoc, txnp, expandedPath.c_str(),
+                                  expandedPath.length(), data->_cachekey);
+              } else {
+                /* We should be here only if the pattern replacement fails 
(match already checked) */
+                PrefetchError("failed to process the pattern");
+
+                /* If the first or any matches fails there must be something 
wrong so don't continue */
+                break;
+              }
+              workingPath.assign(expandedPath);
+            }
+          } else {
+            PrefetchDebug("failed to get current path");
           }
-          std::string nextFile = param.substr(keyLen + 1); // +1 for the '='
-          std::string nextPath = currentPath.substr(0, lastSlashIndex + 1) + 
nextFile;
+        } else if (hasValidQuery) {
+          /* Trigger all necessary background fetches based on the query 
string(s) */
+
+          PrefetchDebug("currentQuery: %s", currentQuery.c_str());
+          const size_t lastSlashIndex = currentPath.find_last_of("/");
+          const size_t keyLen         = config.getQueryKeyName().size();
+          unsigned done               = 1;
+          std::istringstream cStringStream(currentQuery);
+          String param;
+
+          while (getline(cStringStream, param, '&')) {
+            if (param.find(config.getQueryKeyName()) != 0) {
+              continue;
+            }
+            if (config.getFetchCount() < done++) {
+              break;
+            }
+            String nextFile = param.substr(keyLen + 1); // +1 for the '='
+            String nextPath = currentPath.substr(0, lastSlashIndex + 1) + 
nextFile;
 
-          PrefetchDebug("nextPath %s, cacheKey %s", nextPath.c_str(), 
data->_cachekey.c_str());
-          BgFetch::schedule(state, config, /* askPermission */ false, 
reqBuffer, reqHdrLoc, txnp, nextPath.c_str(),
-                            nextPath.length(), data->_cachekey);
+            PrefetchDebug("nextPath %s, cacheKey %s", nextPath.c_str(), 
data->_cachekey.c_str());
+            BgFetch::schedule(state, config, /* askPermission */ false, 
reqBuffer, reqHdrLoc, txnp, nextPath.c_str(),
+                              nextPath.length(), data->_cachekey);
+          }
         }
       }
     }
@@ -720,7 +829,7 @@ TSRemapDoRemap(void *instance, TSHttpTxn txnp, 
TSRemapRequestInfo *rri)
 
       /* Make sure we handle only URLs that match the path pattern on the 
front-end + first-pass, cancel otherwise */
       bool handleFetch = true;
-      if (front && firstPass) {
+      if (front && firstPass && !config.isCmcdNor()) {
         /* Front-end plug-in instance + first pass. */
         if (config.getNextPath().empty()) {
           /* No next path pattern specified then pass this request untouched. 
*/
diff --git a/tests/gold_tests/pluginTest/prefetch/header_rewrite.conf 
b/tests/gold_tests/pluginTest/prefetch/header_rewrite.conf
new file mode 100644
index 000000000..04631fa6a
--- /dev/null
+++ b/tests/gold_tests/pluginTest/prefetch/header_rewrite.conf
@@ -0,0 +1,19 @@
+#
+# 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.
+
+cond %{TXN_START_HOOK}
+set-header x-debug x-cache-key
diff --git a/tests/gold_tests/pluginTest/prefetch/prefetch_cmcd.test.py 
b/tests/gold_tests/pluginTest/prefetch/prefetch_cmcd.test.py
new file mode 100644
index 000000000..43017edc0
--- /dev/null
+++ b/tests/gold_tests/pluginTest/prefetch/prefetch_cmcd.test.py
@@ -0,0 +1,337 @@
+'''
+'''
+#  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.
+
+import os
+import urllib.parse
+
+Test.Summary = '''
+Test prefetch.so plugin (simple mode).
+'''
+
+origin = Test.MakeOriginServer("origin")
+
+asset_name = 'request.txt'
+pf_name = 'prefetch.txt'
+pf_header = f'Cmcd-Request: foo=12,nor="{pf_name}",bar=42'
+
+request_header = {
+    "headers":
+    f"GET /tests/{asset_name} HTTP/1.1\r\n"
+    "Host: does.not.matter\r\n"  # But cant be omitted
+    f"{pf_header}\r\n"
+    "\r\n",
+    "timestamp": "1469733493.993",
+    "body": ""
+}
+response_header = {
+    "headers":
+    "HTTP/1.1 200 OK\r\n"
+    "Connection: close\r\n"
+    "Cache-control: max-age=60\r\n"
+    "\r\n",
+    "timestamp": "1469733493.993",
+    "body": f"This is the body for {asset_name}\n"
+}
+origin.addResponse("sessionlog.json", request_header, response_header)
+
+# query string
+query_name = 'query?this=foo&that'
+query_pf_name = 'query?bar=baz'
+query_pf_header = f'Cmcd-Request: nor="{query_pf_name}"'
+
+# nor field may be percent encoded
+query_pf_perc_name = urllib.parse.quote(query_pf_name)
+query_pf_perc_header = f'Cmcd-Request: nor="{query_pf_perc_name}"'
+
+request_header = {
+    "headers":
+    f"GET /tests/{query_name} HTTP/1.1\r\n"
+    "Host: does.not.matter\r\n"  # But cant be omitted
+    f"{query_pf_perc_header}\r\n"
+    "\r\n",
+    "timestamp": "1469733493.993",
+    "body": ""
+}
+response_header = {
+    "headers":
+    "HTTP/1.1 200 OK\r\n"
+    "Connection: close\r\n"
+    "Cache-control: max-age=60\r\n"
+    "\r\n",
+    "timestamp": "1469733493.993",
+    "body": f"This is the body for {query_name}\n"
+}
+origin.addResponse("sessionlog.json", request_header, response_header)
+
+# setup the prefetched assets
+names = [pf_name, query_pf_name]
+
+for name in names:
+    request_header = {
+        "headers":
+        f"GET /tests/{name} HTTP/1.1\r\n"
+        "Host: does.not.matter\r\n"  # But cant be omitted
+        "\r\n",
+        "timestamp": "1469733493.993",
+        "body": ""
+    }
+    response_header = {
+        "headers":
+        "HTTP/1.1 200 OK\r\n"
+        "Connection: close\r\n"
+        "Cache-control: max-age=60\r\n"
+        "\r\n",
+        "timestamp": "1469733493.993",
+        "body": f"This is the body for {name}\n"
+    }
+    origin.addResponse("sessionlog.json", request_header, response_header)
+
+# prefetch from root
+root_name = 'root.txt'
+root_header = f'Cmcd-Request: nor="rooted"'
+
+request_header = {
+    "headers":
+    f"GET /{root_name} HTTP/1.1\r\n"
+    "Host: does.not.matter\r\n"  # But cant be omitted
+    f"{root_header}\r\n"
+    "\r\n",
+    "timestamp": "1469733493.993",
+    "body": ""
+}
+response_header = {
+    "headers":
+    "HTTP/1.1 200 OK\r\n"
+    "Connection: close\r\n"
+    "Cache-control: max-age=60\r\n"
+    "\r\n",
+    "timestamp": "1469733493.993",
+    "body": f"This is the body for {root_name}\n"
+}
+origin.addResponse("sessionlog.json", request_header, response_header)
+
+request_header = {
+    "headers":
+    f"GET /rooted HTTP/1.1\r\n"
+    "Host: does.not.matter\r\n"  # But cant be omitted
+    "\r\n",
+    "timestamp": "1469733493.993",
+    "body": ""
+}
+response_header = {
+    "headers":
+    "HTTP/1.1 200 OK\r\n"
+    "Connection: close\r\n"
+    "Cache-control: max-age=60\r\n"
+    "\r\n",
+    "timestamp": "1469733493.993",
+    "body": f"This is the body for rooted\n"
+}
+origin.addResponse("sessionlog.json", request_header, response_header)
+
+# ignore if cmcd-request nrr= found
+crr_name = 'crr.txt'
+crr_header = f'Cmcd-Request: foo=12,nor="{crr_name}",bar=42,nrr="0-"'
+request_header = {
+    "headers":
+    f"GET /tests/{crr_name} HTTP/1.1\r\n"
+    "Host: does.not.matter\r\n"  # But cant be omitted
+    f"{crr_header}\r\n"
+    "\r\n",
+    "timestamp": "1469733493.993",
+    "body": ""
+}
+response_header = {
+    "headers":
+    "HTTP/1.1 200 OK\r\n"
+    "Connection: close\r\n"
+    "Cache-control: max-age=60\r\n"
+    "\r\n",
+    "timestamp": "1469733493.993",
+    "body": f"This is the body for {crr_name}\n"
+}
+origin.addResponse("sessionlog.json", request_header, response_header)
+
+# allows for multiple ats on localhost
+dns = Test.MakeDNServer("dns")
+
+# next hop trafficserver instance
+ts1 = Test.MakeATSProcess("ts1")
+ts1.Disk.records_config.update({
+    'proxy.config.diags.debug.enabled': 1,
+    'proxy.config.diags.debug.tags': 'prefetch|http',
+    'proxy.config.dns.nameservers': f"127.0.0.1:{dns.Variables.Port}",
+    'proxy.config.dns.resolv_conf': "NULL",
+    'proxy.config.http.parent_proxy.self_detect': 0,
+})
+dns.addRecords(records={f"ts1": ["127.0.0.1"]})
+ts1.Disk.remap_config.AddLine(
+    f"map / http://127.0.0.1:{origin.Variables.Port}"; +
+    " @plugin=cachekey.so @pparam==--sort-params=true"
+    " @plugin=prefetch.so @pparam==--front=false"
+)
+
+ts1.Disk.logging_yaml.AddLines(
+    '''
+logging:
+ formats:
+  - name: custom
+    format: '%<cquuc> %<pssc> %<crc> %<cwr> %<pscl> %<{X-CDN-Prefetch}cqh>'
+ logs:
+  - filename: transaction
+    format: custom
+'''.split("\n")
+)
+
+ts0 = Test.MakeATSProcess("ts0")
+ts0.Disk.records_config.update({
+    'proxy.config.diags.debug.enabled': 1,
+    'proxy.config.diags.debug.tags': 'prefetch|http',
+    'proxy.config.dns.nameservers': f"127.0.0.1:{dns.Variables.Port}",
+    'proxy.config.dns.resolv_conf': "NULL",
+    'proxy.config.http.parent_proxy.self_detect': 0,
+})
+
+dns.addRecords(records={f"ts0": ["127.0.0.1"]})
+ts0.Disk.remap_config.AddLine(
+    f"map http://ts0 http://ts1:{ts1.Variables.port}"; +
+    " @plugin=cachekey.so @pparam=--sort-params=true"
+    " @plugin=prefetch.so" +
+    " @pparam=--front=true" +
+    " @pparam=--fetch-policy=simple" +
+    " @pparam=--cmcd-nor=true"
+)
+
+ts0.Disk.logging_yaml.AddLines(
+    '''
+logging:
+ formats:
+  - name: custom
+    format: '%<cquuc> %<pssc> %<crc> %<cwr> %<pscl> %<{X-CDN-Prefetch}cqh>'
+ logs:
+  - filename: transaction
+    format: custom
+'''.split("\n")
+)
+
+
+# start everything up
+tr = Test.AddTestRun()
+tr.Processes.Default.StartBefore(origin)
+tr.Processes.Default.StartBefore(dns)
+tr.Processes.Default.StartBefore(ts0)
+tr.Processes.Default.StartBefore(ts1)
+tr.Processes.Default.Command = 'echo start TS, TSH_N, HTTP origin and DNS.'
+tr.Processes.Default.ReturnCode = 0
+
+# attempt to get normal asset
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+    f"curl --verbose --proxy 127.0.0.1:{ts0.Variables.port} 
http://ts0/tests/{asset_name}";
+)
+tr.Processes.Default.ReturnCode = 0
+
+# issue curl form same asset, with prefetch
+tr = Test.AddTestRun()
+tr.DelayStart = 1
+tr.Processes.Default.Command = (
+    f"curl --verbose --proxy 127.0.0.1:{ts0.Variables.port} 
http://ts0/tests/{asset_name} -H \'{pf_header}\'"
+)
+tr.Processes.Default.ReturnCode = 0
+
+# fetch the prefetched asset (only cached on ts1)
+tr = Test.AddTestRun()
+tr.DelayStart = 1
+tr.Processes.Default.Command = (
+    f"curl --verbose --proxy 127.0.0.1:{ts0.Variables.port} 
http://ts0/tests/{pf_name}";
+)
+tr.Processes.Default.ReturnCode = 0
+
+# attempt to prefetch again
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+    f"curl --verbose --proxy 127.0.0.1:{ts0.Variables.port} 
http://ts0/tests/{asset_name} -H \'{pf_header}\'"
+)
+tr.Processes.Default.ReturnCode = 0
+
+# request the prefetched asset
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+    f"curl --verbose --proxy 127.0.0.1:{ts0.Variables.port} 
http://ts0/tests/{pf_name}";
+)
+tr.Processes.Default.ReturnCode = 0
+
+# prefetch using query params with query prefetch perc encoded
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+    f"curl --verbose --proxy 127.0.0.1:{ts0.Variables.port} 
\'http://ts0/tests/{query_name}\' -H \'{query_pf_perc_header}\'"
+)
+tr.Processes.Default.ReturnCode = 0
+
+# request the prefetched asset without perc encoding
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+    f"curl --verbose --proxy 127.0.0.1:{ts0.Variables.port} 
\'http://ts0/tests/{query_pf_name}\'"
+)
+tr.Processes.Default.ReturnCode = 0
+
+# ensure root path prefetch works
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+    f"curl --verbose --proxy 127.0.0.1:{ts0.Variables.port} 
\'http://ts0/{root_name}\' -H \'{root_header}\'"
+)
+tr.Processes.Default.ReturnCode = 0
+
+# ensure request with nrr= field is skipped
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+    f"curl --verbose --proxy 127.0.0.1:{ts0.Variables.port} 
\'http://ts0/{crr_name}\' -H \'{crr_header}\'"
+)
+tr.Processes.Default.ReturnCode = 0
+
+condwaitpath = os.path.join(Test.Variables.AtsTestToolsDir, 'condwait')
+
+# look for ts transaction log
+ts0log = os.path.join(ts0.Variables.LOGDIR, 'transaction.log')
+tr = Test.AddTestRun()
+ps = tr.Processes.Default
+ps.Command = (
+    condwaitpath + ' 60 1 -f ' + ts0log
+)
+
+# look for ts1 transaction log
+ts1log = os.path.join(ts1.Variables.LOGDIR, 'transaction.log')
+tr = Test.AddTestRun()
+ps = tr.Processes.Default
+ps.Command = (
+    condwaitpath + ' 60 1 -f ' + ts1log
+)
+
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+    f"cat {ts0log}"
+)
+tr.Streams.stdout = "prefetch_cmcd0.gold"
+tr.Processes.Default.ReturnCode = 0
+
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+    f"cat {ts1log}"
+)
+tr.Streams.stdout = "prefetch_cmcd1.gold"
+tr.Processes.Default.ReturnCode = 0
diff --git a/tests/gold_tests/pluginTest/prefetch/prefetch_cmcd0.gold 
b/tests/gold_tests/pluginTest/prefetch/prefetch_cmcd0.gold
new file mode 100644
index 000000000..84aeaf45f
--- /dev/null
+++ b/tests/gold_tests/pluginTest/prefetch/prefetch_cmcd0.gold
@@ -0,0 +1,13 @@
+http://ts0/tests/request.txt 200 TCP_MISS FIN 33 -
+http://ts0/tests/request.txt 200 TCP_HIT - 33 -
+http://ts0/tests/prefetch.txt 200 TCP_MISS - 16 tests/request.txt
+http://ts0/tests/prefetch.txt 200 TCP_MISS FIN 34 -
+http://ts0/tests/request.txt 200 ``_HIT - 33 -
+http://ts0/tests/prefetch.txt 208 TCP_HIT - 20 tests/request.txt
+http://ts0/tests/prefetch.txt 200 ``_HIT - 34 -
+http://ts0/tests/query?this=foo&that 200 TCP_MISS FIN 41 -
+http://ts0/tests/query?bar=baz 200 TCP_MISS - 16 tests/query
+http://ts0/tests/query?bar=baz 200 TCP_MISS FIN 35 -
+http://ts0/root.txt 200 TCP_MISS FIN 30 -
+http://ts0/rooted 200 TCP_MISS - 16 root.txt
+http://ts0/crr.txt 404 TCP_MISS - 5 -
diff --git a/tests/gold_tests/pluginTest/prefetch/prefetch_cmcd1.gold 
b/tests/gold_tests/pluginTest/prefetch/prefetch_cmcd1.gold
new file mode 100644
index 000000000..1d2b7fcea
--- /dev/null
+++ b/tests/gold_tests/pluginTest/prefetch/prefetch_cmcd1.gold
@@ -0,0 +1,12 @@
+http://ts1:``/tests/request.txt 200 TCP_MISS FIN 33 -
+http://ts1:``/tests/prefetch.txt 200 TCP_MISS - 16 tests/request.txt
+http://ts1:``/tests/prefetch.txt 200 TCP_MISS FIN 34 -
+http://ts1:``/tests/prefetch.txt 200 TCP_HIT - 34 -
+http://ts1:``/tests/query?this=foo&that 200 TCP_MISS FIN 41 -
+http://ts1:``/tests/query?bar=baz 200 TCP_MISS - 16 tests/query
+http://ts1:``/tests/query?bar=baz 200 TCP_MISS FIN 35 -
+http://ts1:``/tests/query?bar=baz 200 TCP_HIT - 35 -
+http://ts1:``/root.txt 200 TCP_MISS FIN 30 -
+http://ts1:``/rooted 200 TCP_MISS - 16 root.txt
+http://ts1:``/rooted 200 TCP_MISS FIN 28 -
+http://ts1:``/crr.txt 404 TCP_MISS - 5 -
diff --git a/tests/gold_tests/pluginTest/prefetch_simple/prefetch_simple.gold 
b/tests/gold_tests/pluginTest/prefetch/prefetch_simple.gold
similarity index 100%
rename from tests/gold_tests/pluginTest/prefetch_simple/prefetch_simple.gold
rename to tests/gold_tests/pluginTest/prefetch/prefetch_simple.gold
diff --git 
a/tests/gold_tests/pluginTest/prefetch_simple/prefetch_simple.test.py 
b/tests/gold_tests/pluginTest/prefetch/prefetch_simple.test.py
similarity index 100%
rename from tests/gold_tests/pluginTest/prefetch_simple/prefetch_simple.test.py
rename to tests/gold_tests/pluginTest/prefetch/prefetch_simple.test.py


Reply via email to