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

bneradt 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 ae4e19e994 header_rewrite: add POST_REMAP_HOOK support (#13426)
ae4e19e994 is described below

commit ae4e19e99416762c3887d483bb7866800995a521
Author: Juan Posadas <[email protected]>
AuthorDate: Tue Aug 4 09:30:03 2026 -0600

    header_rewrite: add POST_REMAP_HOOK support (#13426)
    
    header_rewrite can attach rulesets to most transaction hooks, but not to
    TS_HTTP_POST_REMAP_HOOK. For a global (plugin.config) configuration, that
    leaves no hook that sees the remapped request before the cache lookup:
    
    READ_REQUEST_HDR_HOOK / READ_REQUEST_PRE_REMAP_HOOK run before remapping,
    so they only ever see the pristine request.
    REMAP_PSEUDO_HOOK sees the remapped request before the lookup, but is valid
    only in a remap context.
    SEND_REQUEST_HDR_HOOK sees the remapped request, but fires after the lookup
    and only when the request is forwarded to an origin. It cannot influence the
    lookup, and it never runs on a cache hit.
    That window is where anything feeding the cache lookup has to run. The 
cachekey
    plugin registers TS_HTTP_POST_REMAP_HOOK for exactly this reason
    (plugins/cachekey/plugin.cc:114): its remap-time path (TSRemapDoRemap)
    covers the per-remap case, and a global instance needs the remapped request
    before TSCacheUrlSet is consumed by the lookup.
    
    This is the hook a planned header_rewrite cache-key operator needs, for the
    same reason: setting the cache key is only meaningful between remapping and 
the
    cache lookup, and SEND_REQUEST_HDR_HOOK is already too late.
    
    This PR adds a POST_REMAP_HOOK hook condition and wires it end to end:
    
    Recognize the POST_REMAP_HOOK keyword in the parser.
    Handle the POST_REMAP event so rulesets run at that hook.
    Gather the post-remap request headers for the hook.
    Allow operators and conditions on the hook.
    Covered by a parser unit test and an end-to-end autest.
---
 doc/admin-guide/plugins/header_rewrite.en.rst      |  16 +++
 plugins/header_rewrite/header_rewrite.cc           |   3 +
 plugins/header_rewrite/header_rewrite_test.cc      |  13 +++
 plugins/header_rewrite/parser.cc                   |   4 +
 plugins/header_rewrite/resources.cc                |   3 +-
 plugins/header_rewrite/statement.cc                |   1 +
 .../header_rewrite_post_remap.replay.yaml          | 123 +++++++++++++++++++++
 .../header_rewrite_post_remap.test.py              |  24 ++++
 .../pluginTest/header_rewrite/post_remap.conf      |  27 +++++
 9 files changed, 213 insertions(+), 1 deletion(-)

diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst 
b/doc/admin-guide/plugins/header_rewrite.en.rst
index da44687320..cb3d53c341 100644
--- a/doc/admin-guide/plugins/header_rewrite.en.rst
+++ b/doc/admin-guide/plugins/header_rewrite.en.rst
@@ -1740,6 +1740,22 @@ files shared by both the global :file:`plugin.config` 
and individual remapping
 entries in :file:`remap.config`, this hook condition will force the subsequent
 ruleset(s) to be valid only for remapped transactions.
 
+POST_REMAP_HOOK
+~~~~~~~~~~~~~~~
+
+Forces evaluation of the ruleset immediately after remapping has completed, but
+before |TS| looks the request up in the cache. There is no response data yet, 
so
+context-adapting conditions and operators match against the request, which at
+this point is the remapped request.
+
+For rulesets in :file:`remap.config`, `REMAP_PSEUDO_HOOK`_ already covers this
+window. This hook exists for globally-configured rulesets, which otherwise have
+no hook that sees the remapped request before the cache lookup:
+`READ_REQUEST_HDR_HOOK`_ and `READ_REQUEST_PRE_REMAP_HOOK`_ run before
+remapping, and `SEND_REQUEST_HDR_HOOK`_ runs after the lookup, only when the
+request is forwarded to an origin. Anything that has to influence the lookup
+itself belongs at this hook.
+
 SEND_REQUEST_HDR_HOOK
 ~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/plugins/header_rewrite/header_rewrite.cc 
b/plugins/header_rewrite/header_rewrite.cc
index 9a43a73813..b8c4f3c6db 100644
--- a/plugins/header_rewrite/header_rewrite.cc
+++ b/plugins/header_rewrite/header_rewrite.cc
@@ -494,6 +494,9 @@ cont_rewrite_headers(TSCont contp, TSEvent event, void 
*edata)
   case TS_EVENT_HTTP_READ_REQUEST_PRE_REMAP:
     hook = TS_HTTP_PRE_REMAP_HOOK;
     break;
+  case TS_EVENT_HTTP_POST_REMAP:
+    hook = TS_HTTP_POST_REMAP_HOOK;
+    break;
   case TS_EVENT_HTTP_SEND_REQUEST_HDR:
     hook = TS_HTTP_SEND_REQUEST_HDR_HOOK;
     break;
diff --git a/plugins/header_rewrite/header_rewrite_test.cc 
b/plugins/header_rewrite/header_rewrite_test.cc
index 68997debd0..cb166fd704 100644
--- a/plugins/header_rewrite/header_rewrite_test.cc
+++ b/plugins/header_rewrite/header_rewrite_test.cc
@@ -129,6 +129,19 @@ test_parsing()
     END_TEST();
   }
 
+  {
+    ParserTest   p("cond %{POST_REMAP_HOOK}");
+    TSHttpHookID hook = TS_HTTP_LAST_HOOK;
+
+    CHECK_EQ(p.getTokens().size(), 2U);
+    CHECK_EQ(p.getTokens()[0], "cond");
+    CHECK_EQ(p.getTokens()[1], "%{POST_REMAP_HOOK}");
+    CHECK_EQ(p.cond_is_hook(hook), true);
+    CHECK_EQ(hook, TS_HTTP_POST_REMAP_HOOK);
+
+    END_TEST();
+  }
+
   {
     ParserTest p("cond %{CLIENT-HEADER:Host}    =a");
 
diff --git a/plugins/header_rewrite/parser.cc b/plugins/header_rewrite/parser.cc
index 745f7d0882..c1f467d39a 100644
--- a/plugins/header_rewrite/parser.cc
+++ b/plugins/header_rewrite/parser.cc
@@ -296,6 +296,10 @@ Parser::cond_is_hook(TSHttpHookID &hook) const
     hook = TS_REMAP_PSEUDO_HOOK;
     return true;
   }
+  if ("POST_REMAP_HOOK" == _op) {
+    hook = TS_HTTP_POST_REMAP_HOOK;
+    return true;
+  }
   if ("TXN_START_HOOK" == _op) {
     hook = TS_HTTP_TXN_START_HOOK;
     return true;
diff --git a/plugins/header_rewrite/resources.cc 
b/plugins/header_rewrite/resources.cc
index ed8d4ffe6a..5ccabde62f 100644
--- a/plugins/header_rewrite/resources.cc
+++ b/plugins/header_rewrite/resources.cc
@@ -87,7 +87,8 @@ Resources::gather(const ResourceIDs ids, TSHttpHookID hook)
 
   case TS_HTTP_READ_REQUEST_HDR_HOOK:
   case TS_HTTP_PRE_REMAP_HOOK:
-    // Read request from client
+  case TS_HTTP_POST_REMAP_HOOK:
+    // Read request from client (post-remap this is the remapped request)
     if (ids & RSRC_CLIENT_REQUEST_HEADERS) {
       bufp    = client_bufp;
       hdr_loc = client_hdr_loc;
diff --git a/plugins/header_rewrite/statement.cc 
b/plugins/header_rewrite/statement.cc
index 351f5d572e..0c699af09c 100644
--- a/plugins/header_rewrite/statement.cc
+++ b/plugins/header_rewrite/statement.cc
@@ -81,6 +81,7 @@ Statement::initialize_hooks()
   add_allowed_hook(TS_HTTP_SEND_REQUEST_HDR_HOOK);
   add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK);
   add_allowed_hook(TS_REMAP_PSEUDO_HOOK);
+  add_allowed_hook(TS_HTTP_POST_REMAP_HOOK);
   add_allowed_hook(TS_HTTP_TXN_START_HOOK);
   add_allowed_hook(TS_HTTP_TXN_CLOSE_HOOK);
 }
diff --git 
a/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_post_remap.replay.yaml
 
b/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_post_remap.replay.yaml
new file mode 100644
index 0000000000..d3cf64c259
--- /dev/null
+++ 
b/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_post_remap.replay.yaml
@@ -0,0 +1,123 @@
+#  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.
+
+meta:
+  version: "1.0"
+
+autest:
+  description: 'Test header_rewrite POST_REMAP_HOOK support (global plugin)'
+
+  dns:
+    name: 'dns'
+
+  server:
+    name: 'server'
+
+  client:
+    name: 'client'
+
+  ats:
+    name: 'ts'
+
+    process_config:
+      enable_cache: true
+
+    copy_to_config_dir:
+      - 'post_remap.conf'
+
+    records_config:
+      proxy.config.diags.debug.enabled: 1
+      proxy.config.diags.debug.tags: 'header_rewrite'
+
+    # header_rewrite loaded as a GLOBAL plugin. The conf resolves relative to
+    # the ATS config dir, where copy_to_config_dir places it.
+    plugin_config:
+      - 'header_rewrite.so post_remap.conf'
+      - 'xdebug.so --enable=x-cache'
+
+    remap_config:
+      - from: "http://www.example.com/";
+        to: "http://backend.ex:{SERVER_HTTP_PORT}/";
+
+sessions:
+- transactions:
+
+  #############################################################################
+  # Cache miss: the header set at POST_REMAP reaches the origin, and the echo
+  # rule reports it on the response.
+  #############################################################################
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      url: /post_remap/
+      headers:
+        fields:
+        - [ Host, www.example.com ]
+        - [ x-debug, "x-cache" ]
+        - [ uuid, post-remap-miss ]
+
+    proxy-request:
+      headers:
+        fields:
+        - [ X-Post-Remap-Host, { value: "backend.ex", as: equal } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Type, text/plain ]
+        - [ Content-Length, "3" ]
+        - [ Cache-Control, "max-age=300" ]
+      content:
+        encoding: plain
+        data: xxx
+
+    proxy-response:
+      status: 200
+      headers:
+        fields:
+        - [ X-Cache, { value: "miss", as: equal } ]
+        - [ X-Post-Remap-Echo, { value: "backend.ex", as: equal } ]
+
+  #############################################################################
+  # Cache hit: nothing is forwarded to the origin, so SEND_REQUEST_HDR_HOOK
+  # never runs. The rule still fires, because POST_REMAP is before the lookup.
+  #############################################################################
+  - client-request:
+      delay: 100ms
+      method: "GET"
+      version: "1.1"
+      url: /post_remap/
+      headers:
+        fields:
+        - [ Host, www.example.com ]
+        - [ x-debug, "x-cache" ]
+        - [ uuid, post-remap-hit ]
+
+    proxy-request:
+      expect: absent
+
+    server-response:
+      status: 404
+      reason: Not Found
+
+    proxy-response:
+      status: 200
+      headers:
+        fields:
+        - [ X-Cache, { value: "hit-fresh", as: equal } ]
+        - [ X-Post-Remap-Echo, { value: "backend.ex", as: equal } ]
diff --git 
a/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_post_remap.test.py 
b/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_post_remap.test.py
new file mode 100644
index 0000000000..3ea824b4b1
--- /dev/null
+++ 
b/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_post_remap.test.py
@@ -0,0 +1,24 @@
+'''
+Test header_rewrite POST_REMAP_HOOK support.
+'''
+#  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.
+
+Test.Summary = '''
+Test header_rewrite attaching a ruleset to the POST_REMAP_HOOK.
+'''
+
+Test.ATSReplayTest(replay_file="header_rewrite_post_remap.replay.yaml",)
diff --git a/tests/gold_tests/pluginTest/header_rewrite/post_remap.conf 
b/tests/gold_tests/pluginTest/header_rewrite/post_remap.conf
new file mode 100644
index 0000000000..2788617372
--- /dev/null
+++ b/tests/gold_tests/pluginTest/header_rewrite/post_remap.conf
@@ -0,0 +1,27 @@
+#
+# 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.
+
+# Global header_rewrite ruleset that fires after remapping, on the remapped
+# request, before the cache lookup. The value is the remapped host, so an
+# earlier hook would record the pristine host instead.
+cond %{POST_REMAP_HOOK}
+  set-header X-Post-Remap-Host "%{URL:HOST}"
+
+# Echo the post-remap header into the client response so the rule above can be
+# observed on a cache hit, where no request is forwarded to the origin.
+cond %{SEND_RESPONSE_HDR_HOOK}
+  set-header X-Post-Remap-Echo "%{CLIENT-HEADER:X-Post-Remap-Host}"

Reply via email to