Skip to content

Commit e319f59

Browse files
authored
Hrw: Supports nested If (#12562)
* HRW: Adds support for nested If operators * HRW docs additions and cleanup for new operator * Adds hrw4u / u4wrh support for nested If's * HRW4U: Allow for explicit state variable slot decl
1 parent 26574c5 commit e319f59

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1030
-225
lines changed

doc/admin-guide/configuration/hrw4u.en.rst

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,29 @@ TXN_CLOSE_HOOK TXN_CLOSE End of transaction
323323
A special section `VARS` is used to declare variables. There is no equivalent in
324324
`header_rewrite`, where you managed the variables manually.
325325

326-
.. note::
327-
The section name is always required in HRW4U, there are no implicit or default hooks. There
328-
can be several if/else block per section block.
326+
Variables and State Slots
327+
^^^^^^^^^^^^^^^^^^^^^^^^^^
328+
329+
Each variable type has a limited number of slots available:
330+
331+
- ``bool`` - 16 slots (0-15)
332+
- ``int8`` - 4 slots (0-3)
333+
- ``int16`` - 1 slot (0)
334+
335+
By default, slots are assigned automatically in declaration order. You can explicitly assign
336+
a slot number using the ``@`` syntax::
337+
338+
VARS {
339+
priority: bool @7; # Explicitly use slot 7
340+
active: bool; # Auto-assigned to slot 0
341+
config: bool @12; # Explicitly use slot 12
342+
counter: int8 @2; # Explicitly use int8 slot 2
343+
}
344+
345+
Explicit slot assignment is useful when you need predictable slot numbers across configurations
346+
or when integrating with existing header_rewrite rules that reference specific slot numbers. In
347+
addition, a remap configuration can use ``@PPARAM`` to set one of these slot variables explicitly
348+
as part of the configuration.
329349

330350
Groups
331351
------

doc/admin-guide/plugins/header_rewrite.en.rst

Lines changed: 135 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,19 @@ like the following::
153153
Which converts any 4xx HTTP status code from the origin server to a 404. A
154154
response from the origin with a status of 200 would be unaffected by this rule.
155155

156+
Advanced Conditionals
157+
---------------------
158+
159+
The header_rewrite plugin supports advanced conditional logic that allows
160+
for more sophisticated rule construction, including branching logic, nested
161+
conditionals, and complex boolean expressions.
162+
163+
else and elif Clauses
164+
~~~~~~~~~~~~~~~~~~~~~
165+
156166
An optional ``else`` clause may be specified, which will be executed if the
157-
conditions are not met. The ``else`` clause is specified by starting a new line
158-
with the word ``else``. The following example illustrates this::
167+
conditions are not met. The ``else`` clause is specified by starting a new
168+
line with the word ``else``. The following example illustrates this::
159169

160170
cond %{STATUS} >399 [AND]
161171
cond %{STATUS} <500
@@ -164,10 +174,12 @@ with the word ``else``. The following example illustrates this::
164174
set-status 503
165175

166176
The ``else`` clause is not a condition, and does not take any flags, it is
167-
of course optional, but when specified must be followed by at least one operator.
177+
of course optional, but when specified must be followed by at least one
178+
operator.
168179

169-
You can also do an ``elif`` (else if) clause, which is specified by starting a new line
170-
with the word ``elif``. The following example illustrates this::
180+
You can also do an ``elif`` (else if) clause, which is specified by
181+
starting a new line with the word ``elif``. The following example
182+
illustrates this::
171183

172184
cond %{STATUS} >399 [AND]
173185
cond %{STATUS} <500
@@ -178,14 +190,105 @@ with the word ``elif``. The following example illustrates this::
178190
else
179191
set-status 503
180192

181-
Keep in mind that nesting the ``else`` and ``elif`` clauses is not allowed, but any
182-
number of ``elif`` clauses can be specified. We can consider these clauses are more
183-
powerful and flexible ``switch`` statement. In an ``if-elif-else`` rule, only one
184-
will evaluate its operators.
193+
Any number of ``elif`` clauses can be specified. We can consider these
194+
clauses are more powerful and flexible ``switch`` statement. In an
195+
``if-elif-else`` rule, only one will evaluate its operators.
196+
197+
Note that while ``else`` and ``elif`` themselves cannot be directly nested,
198+
you can use ``if``/``endif`` blocks within ``else`` or ``elif`` operator
199+
sections to achieve nested conditional logic (see `Nested Conditionals with
200+
if/endif`_).
185201

186202
Similarly, each ``else`` and ``elif`` have the same implied
187203
:ref:`Hook Condition <hook_conditions>` as the initial condition.
188204

205+
Nested Conditionals with if/endif
206+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
207+
208+
For more complex logic requiring nested conditionals, the ``if`` and
209+
``endif`` pseudo-operators can be used. While ``else`` and ``elif``
210+
themselves cannot be directly nested, you can use ``if``/``endif`` blocks
211+
within any operator section (including inside ``else`` or ``elif`` blocks)
212+
to achieve arbitrary nesting depth.
213+
214+
The ``if`` operator starts a new conditional block, and ``endif`` closes
215+
it. Each ``if`` must have a matching ``endif``. Here's an example::
216+
217+
cond %{READ_RESPONSE_HDR_HOOK} [AND]
218+
cond %{STATUS} >399
219+
if
220+
cond %{HEADER:X-Custom-Error} ="true"
221+
set-header X-Error-Handled "yes"
222+
else
223+
set-header X-Error-Handled "no"
224+
endif
225+
set-status 500
226+
227+
In this example, the nested ``if``/``endif`` block is only evaluated when
228+
the status is greater than 399. The nested block itself can contain
229+
``else`` or ``elif`` clauses, and you can nest multiple levels deep::
230+
231+
cond %{READ_RESPONSE_HDR_HOOK}
232+
if
233+
cond %{STATUS} =404
234+
if
235+
cond %{CLIENT-HEADER:User-Agent} /mobile/
236+
set-header X-Error-Type "mobile-404"
237+
else
238+
set-header X-Error-Type "desktop-404"
239+
endif
240+
elif
241+
cond %{STATUS} =500
242+
set-header X-Error-Type "server-error"
243+
endif
244+
245+
GROUP Conditions in Advanced Conditionals
246+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
247+
248+
The `GROUP`_ condition can be combined with advanced conditionals to
249+
create very sophisticated boolean expressions. ``GROUP`` conditions act as
250+
parentheses in your conditional logic, allowing you to mix AND, OR, and NOT
251+
operators in complex ways.
252+
253+
Here's an example combining ``GROUP`` with ``if``/``endif``::
254+
255+
cond %{READ_RESPONSE_HDR_HOOK} [AND]
256+
cond %{STATUS} >399
257+
if
258+
cond %{GROUP} [OR]
259+
cond %{CLIENT-HEADER:X-Retry} ="true" [AND]
260+
cond %{METHOD} =GET
261+
cond %{GROUP:END}
262+
cond %{CLIENT-HEADER:X-Force-Cache} ="" [NOT]
263+
set-header X-Can-Retry "yes"
264+
else
265+
set-header X-Can-Retry "no"
266+
endif
267+
set-status 500
268+
269+
This creates the logic: if error status, then set retry header when
270+
``((X-Retry=true AND METHOD=GET) OR X-Force-Cache header exists)``.
271+
The GROUP is necessary here to properly combine the two conditions with OR.
272+
273+
You can also use ``GROUP`` with ``else`` and ``elif`` inside nested conditionals::
274+
275+
cond %{SEND_RESPONSE_HDR_HOOK} [AND]
276+
cond %{STATUS} >399
277+
if
278+
cond %{GROUP} [OR]
279+
cond %{HEADER:X-Custom} ="retry" [AND]
280+
cond %{METHOD} =POST
281+
cond %{GROUP:END}
282+
cond %{HEADER:Content-Type} /json/
283+
set-header X-Error-Handler "json-retry"
284+
elif
285+
cond %{METHOD} =GET
286+
set-header X-Error-Handler "get-error"
287+
else
288+
set-header X-Error-Handler "standard"
289+
endif
290+
set-status 500
291+
189292
State variables
190293
---------------
191294

@@ -923,6 +1026,29 @@ no facility to increment by other amounts, nor is it possible to initialize the
9231026
counter with any value other than ``0``. Additionally, the counter will reset
9241027
whenever |TS| is restarted.
9251028

1029+
if
1030+
~~
1031+
::
1032+
1033+
if
1034+
<conditions>
1035+
<operators>
1036+
endif
1037+
1038+
This is a pseudo-operator that enables nested conditional blocks within
1039+
the operator section of a rule. While ``else`` and ``elif`` themselves
1040+
cannot be directly nested, you can use ``if``/``endif`` blocks within any
1041+
operator section (including inside ``else`` or ``elif`` blocks) to create
1042+
arbitrary nesting depth for complex conditional logic.
1043+
1044+
The ``if`` operator must be preceded by conditions and followed by at
1045+
least one condition or operator. Each ``if`` must have a matching
1046+
``endif`` to close the block. Within an ``if``/``endif`` block, you can
1047+
use regular conditions, operators, and even ``else`` and ``elif`` clauses.
1048+
1049+
For detailed usage and examples, see `Nested Conditionals with if/endif`_
1050+
in the `Advanced Conditionals`_ section.
1051+
9261052
no-op
9271053
~~~~~
9281054
::

plugins/header_rewrite/factory.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ operator_factory(const std::string &op)
9292
} else if (op == "set-next-hop-strategy") {
9393
o = new OperatorSetNextHopStrategy();
9494
} else {
95+
// Note that we don't support the OperatorIf() pseudo-operator here!
9596
TSError("[%s] Unknown operator: %s", PLUGIN_NAME, op.c_str());
9697
return nullptr;
9798
}

0 commit comments

Comments
 (0)