diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst b/doc/admin-guide/plugins/header_rewrite.en.rst index 2d57f64f973..7ba3fd6d223 100644 --- a/doc/admin-guide/plugins/header_rewrite.en.rst +++ b/doc/admin-guide/plugins/header_rewrite.en.rst @@ -1734,6 +1734,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 9a43a73813b..b8c4f3c6dbf 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 6f6026e3df3..4f4c69c75ed 100644 --- a/plugins/header_rewrite/header_rewrite_test.cc +++ b/plugins/header_rewrite/header_rewrite_test.cc @@ -126,6 +126,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 745f7d0882e..c1f467d39a8 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 ed8d4ffe6a9..5ccabde62f5 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 351f5d572e3..0c699af09ca 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 00000000000..d3cf64c259d --- /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 00000000000..3ea824b4b1e --- /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 00000000000..2788617372b --- /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}"